Class: AdsPowerClient
- Inherits:
-
Object
- Object
- AdsPowerClient
- Defined in:
- lib/adspower-client.rb
Constant Summary collapse
- CLOUD_API_BASE =
'https://api.adspower.com/v1'- 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.
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) ⇒ Object
Create a new desktop profile with custom name, proxy, and fingerprint settings.
-
#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
Attach to the existing browser session with Selenium WebDriver.
-
#html(url) ⇒ Object
DEPRECATED - Use Zyte instead of this method.
-
#initialize(h = {}) ⇒ AdsPowerClient
constructor
A new instance of AdsPowerClient.
-
#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.
22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/adspower-client.rb', line 22 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
14 15 16 |
# File 'lib/adspower-client.rb', line 14 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
14 15 16 |
# File 'lib/adspower-client.rb', line 14 def adspower_listener @adspower_listener end |
#cloud_token ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
14 15 16 |
# File 'lib/adspower-client.rb', line 14 def cloud_token @cloud_token end |
#key ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
14 15 16 |
# File 'lib/adspower-client.rb', line 14 def key @key end |
#port ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
14 15 16 |
# File 'lib/adspower-client.rb', line 14 def port @port end |
#server_log ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
14 15 16 |
# File 'lib/adspower-client.rb', line 14 def server_log @server_log end |
Instance Method Details
#acquire_lock ⇒ Object
Acquire the lock
36 37 38 39 |
# File 'lib/adspower-client.rb', line 36 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.
264 265 266 267 268 269 270 271 272 |
# File 'lib/adspower-client.rb', line 264 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
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/adspower-client.rb', line 127 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.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/adspower-client.rb', line 148 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) ⇒ Object
Create a new desktop profile with custom name, proxy, and fingerprint settings
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/adspower-client.rb', line 173 def create2(name:, proxy_config:, group_id: '0', browser_version: nil) browser_version ||= adspower_default_browser_version with_lock do url = "#{adspower_listener}:#{port}/api/v2/browser-profile/create" body = { 'name' => name, 'group_id' => group_id, 'user_proxy_config' => { 'proxy_soft' => proxy_config[:proxy_soft] || 'other', 'proxy_type' => proxy_config[:proxy_type] || 'http', 'proxy_host' => proxy_config[:ip], 'proxy_port' => proxy_config[:port].to_s, 'proxy_user' => proxy_config[:user], 'proxy_password' => proxy_config[:password] }, 'fingerprint_config' => { # 1) Chrome kernel version → must match your Chromedriver 'browser_kernel_config' => { 'version' => browser_version, 'type' => 'chrome' }, # 2) Auto‐detect timezone (and locale) from proxy IP 'automatic_timezone' => '1', 'timezone' => '', 'language' => [], # 3) Force desktop UA (no mobile): empty random_ua & default UA settings 'ua' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "\ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/#{browser_version}.0.0.0 Safari/537.36", 'ua_category' => 'desktop', #'screen_resolution' => '1920*1080', 'is_mobile' => false, # standard desktop fingerprints 'webrtc' => 'disabled', # hide real IP via WebRTC 'flash' => 'allow', 'fonts' => [], # default fonts } } 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.
221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/adspower-client.rb', line 221 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.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/adspower-client.rb', line 275 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
Attach to the existing browser session with Selenium WebDriver.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/adspower-client.rb', line 299 def driver2(id, headless: false, read_timeout: 180) # 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) # Set up the custom HTTP client with a longer timeout client = Selenium::WebDriver::Remote::Http::Default.new client.read_timeout = read_timeout # Set this to the desired timeout in seconds # Connect to the existing browser driver = Selenium::WebDriver.for(:chrome, options: opts, http_client: client) # Save the driver @@drivers[id] = driver # Return the driver driver 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.
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 |
# File 'lib/adspower-client.rb', line 329 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 |
#online? ⇒ Boolean
Send a GET request to “#url/status” and return true if it responded successfully.
86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/adspower-client.rb', line 86 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)
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/adspower-client.rb', line 100 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
42 43 44 |
# File 'lib/adspower-client.rb', line 42 def release_lock @lockfile.flock(File::LOCK_UN) if @lockfile end |
#server_pids ⇒ Object
Return an array of PIDs of all the adspower_global processes running on the local computer.
55 56 57 |
# File 'lib/adspower-client.rb', line 55 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.
61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/adspower-client.rb', line 61 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.
76 77 78 79 80 81 82 83 |
# File 'lib/adspower-client.rb', line 76 def server_stop with_lock do self.server_pids.each { |pid| `kill -9 #{pid}` } end return end |
#start(id, headless = false) ⇒ Object
Start the browser with the given user profile and return the connection details.
236 237 238 239 240 241 242 243 244 245 |
# File 'lib/adspower-client.rb', line 236 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.
248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/adspower-client.rb', line 248 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
47 48 49 50 51 52 |
# File 'lib/adspower-client.rb', line 47 def with_lock acquire_lock yield ensure release_lock end |