adspower-client 1.0.11 → 1.0.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eba877730ea885f44264bdc0e35bbbd1d671546981a87d59c4879cadaf947a60
4
- data.tar.gz: 7844d32cd5fc80d3b4af8e28c07e19ed626694f2788ab3a300c56df04f99af8c
3
+ metadata.gz: 4f1a3cd88b7f76b1514641e9fbc54b2c1b7dd15945da9f07533be8bb9782a812
4
+ data.tar.gz: b6d7708277cbaeae8cad513362a29b2260b4ee516b88a698894a0d0284cc32e0
5
5
  SHA512:
6
- metadata.gz: 96503c9412c992bac4ecce3e83604de3156f2c449168261bcdc999e49dbfafb2be6d4de26e9532fd69bc42c30fd3e581054c2b00b7a3b600a79d1e0a173a811b
7
- data.tar.gz: c2436a9e78785c2b764c14e080b099661726153af1a0f4701d570577117ebb312ab76ec90e356869186ded272924c0d5faea2341af66ddde1145ce8585eec560
6
+ metadata.gz: da5102f80bac0665d4117ab96d76ebfc6a82e8421ef8b61f0c5bed11429a9d11d218e1711b6e99e14b8778c54b139ab5ba4d84c4d0733b3f69ebea83a6e6d0fc
7
+ data.tar.gz: 9ec0a6920213f25c472a749b91231c2a33241d00093c5a32e166f3ee3fee3c40ff22189ec5f6bcf2b8b40a436b5ee6cfee3fe14bb17ca703e792e528fbe3f915
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'adspower-client'
3
- s.version = '1.0.11'
4
- s.date = '2024-03-31'
3
+ s.version = '1.0.12'
4
+ s.date = '2024-10-07'
5
5
  s.summary = "Ruby library for operating AdsPower API."
6
6
  s.description = "Ruby library for operating AdsPower API."
7
7
  s.authors = ["Leandro Daniel Sardi"]
@@ -4,34 +4,53 @@ require 'json'
4
4
  require 'blackstack-core'
5
5
  require 'selenium-webdriver'
6
6
  require 'watir'
7
+ require 'fileutils'
7
8
 
8
9
  class AdsPowerClient
9
10
  # reference: https://localapi-doc-en.adspower.com/
10
11
  # reference: https://localapi-doc-en.adspower.com/docs/Rdw7Iu
11
12
  attr_accessor :key, :port, :server_log, :adspower_listener, :adspower_default_browser_version
12
-
13
- # control over the drivers created, in order to don't create the same driver twice and don't generate memory leaks.
13
+
14
+ # control over the drivers created, in order to not create the same driver twice and not generate memory leaks.
14
15
  # reference: https://github.com/leandrosardi/adspower-client/issues/4
15
16
  @@drivers = {}
16
17
 
18
+ LOCK_FILE = '/tmp/adspower_api_lock'
19
+
17
20
  def initialize(h={})
18
21
  self.key = h[:key] # mandatory
19
22
  self.port = h[:port] || '50325'
20
23
  self.server_log = h[:server_log] || '~/adspower-client.log'
21
24
  self.adspower_listener = h[:adspower_listener] || 'http://127.0.0.1'
22
25
  self.adspower_default_browser_version = h[:adspower_default_browser_version] || '116'
23
- # self.profiles_created = []
24
26
  end
25
27
 
26
- # return an array of PIDs of all the adspower_global processes running in the local computer.
28
+ # Acquire the lock
29
+ def acquire_lock
30
+ @lockfile ||= File.open(LOCK_FILE, File::CREAT | File::RDWR)
31
+ @lockfile.flock(File::LOCK_EX)
32
+ end
33
+
34
+ # Release the lock
35
+ def release_lock
36
+ @lockfile.flock(File::LOCK_UN) if @lockfile
37
+ end
38
+
39
+ # Wrapper method for critical sections
40
+ def with_lock
41
+ acquire_lock
42
+ yield
43
+ ensure
44
+ release_lock
45
+ end
46
+
47
+ # Return an array of PIDs of all the adspower_global processes running on the local computer.
27
48
  def server_pids
28
49
  `ps aux | grep "adspower_global" | grep -v grep | awk '{print $2}'`.split("\n")
29
50
  end
30
51
 
31
- # return true if there is any adspower_global process running in the local computer.
32
-
33
- # run async command to start adspower server in headless mode.
34
- # wait up to 10 seconds to start the server, or raise an exception.
52
+ # Run async command to start AdsPower server in headless mode.
53
+ # Wait up to 10 seconds to start the server, or raise an exception.
35
54
  def server_start(timeout=30)
36
55
  `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 &`
37
56
  # wait up to 10 seconds to start the server
@@ -46,161 +65,128 @@ class AdsPowerClient
46
65
  return
47
66
  end
48
67
 
49
- # kill all the adspower_global processes running in the local computer.
68
+ # Kill all the adspower_global processes running on the local computer.
50
69
  def server_stop
51
- self.server_pids.each { |pid|
52
- `kill -9 #{pid}`
53
- }
70
+ with_lock do
71
+ self.server_pids.each { |pid|
72
+ `kill -9 #{pid}`
73
+ }
74
+ end
54
75
  return
55
76
  end
56
-
57
- # send an GET request to "#{url}/status".
58
- # Return true if it responded successfully.
59
- #
60
- # reference: https://localapi-doc-en.adspower.com/docs/6DSiws
61
- #
77
+
78
+ # Send a GET request to "#{url}/status" and return true if it responded successfully.
62
79
  def online?
63
- begin
64
- url = "#{self.adspower_listener}:#{port}/status"
65
- uri = URI.parse(url)
66
- res = Net::HTTP.get(uri)
67
- # show respose body
68
- return JSON.parse(res)['msg'] == 'success'
69
- rescue => e
70
- return false
80
+ with_lock do
81
+ begin
82
+ url = "#{self.adspower_listener}:#{port}/status"
83
+ uri = URI.parse(url)
84
+ res = Net::HTTP.get(uri)
85
+ return JSON.parse(res)['msg'] == 'success'
86
+ rescue => e
87
+ return false
88
+ end
71
89
  end
72
90
  end
73
91
 
74
- # send a post request to "#{url}/api/v1/user/create"
75
- # and return the response body.
76
- #
77
- # return id of the created user
78
- #
79
- # reference: https://localapi-doc-en.adspower.com/docs/6DSiws
80
- # reference: https://localapi-doc-en.adspower.com/docs/Lb8pOg
81
- # reference: https://localapi-doc-en.adspower.com/docs/Awy6Dg
82
- #
92
+ # Create a new user profile via API call and return the ID of the created user.
83
93
  def create
84
- url = "#{self.adspower_listener}:#{port}/api/v1/user/create"
85
- body = {
86
- #'api_key' => self.key,
87
- 'group_id' => '0',
88
- 'proxyid' => '1',
89
- 'fingerprint_config' => {
90
- 'browser_kernel_config' => {"version": self.adspower_default_browser_version, "type":"chrome"}
94
+ with_lock do
95
+ url = "#{self.adspower_listener}:#{port}/api/v1/user/create"
96
+ body = {
97
+ 'group_id' => '0',
98
+ 'proxyid' => '1',
99
+ 'fingerprint_config' => {
100
+ 'browser_kernel_config' => {"version": self.adspower_default_browser_version, "type": "chrome"}
101
+ }
91
102
  }
92
- }
93
- # api call
94
- res = BlackStack::Netting.call_post(url, body)
95
- # show respose body
96
- ret = JSON.parse(res.body)
97
- raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success'
98
- # add to array of profiles created
99
- # self.profiles_created << ret
100
- # return id of the created user
101
- ret['data']['id']
103
+ # API call
104
+ res = BlackStack::Netting.call_post(url, body)
105
+ ret = JSON.parse(res.body)
106
+ raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success'
107
+ ret['data']['id']
108
+ end
102
109
  end
103
110
 
111
+ # Delete a user profile via API call.
104
112
  def delete(id)
105
- url = "#{self.adspower_listener}:#{port}/api/v1/user/delete"
106
- body = {
107
- 'api_key' => self.key,
108
- 'user_ids' => [id],
109
- }
110
- # api call
111
- res = BlackStack::Netting.call_post(url, body)
112
- # show respose body
113
- ret = JSON.parse(res.body)
114
- # validation
115
- raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success'
113
+ with_lock do
114
+ url = "#{self.adspower_listener}:#{port}/api/v1/user/delete"
115
+ body = {
116
+ 'api_key' => self.key,
117
+ 'user_ids' => [id],
118
+ }
119
+ # API call
120
+ res = BlackStack::Netting.call_post(url, body)
121
+ ret = JSON.parse(res.body)
122
+ raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success'
123
+ end
116
124
  end
117
125
 
118
- # run the browser
119
- # return the URL to operate the browser thru selenium
120
- #
121
- # reference: https://localapi-doc-en.adspower.com/docs/FFMFMf
122
- #
126
+ # Start the browser with the given user profile and return the connection details.
123
127
  def start(id, headless=false)
124
- url = "#{self.adspower_listener}:#{port}/api/v1/browser/start?user_id=#{id}&headless=#{headless ? '1' : '0'}"
125
- uri = URI.parse(url)
126
- res = Net::HTTP.get(uri)
127
- # show respose bo
128
- ret = JSON.parse(res)
129
- raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success'
130
- # return id of the created user
131
- ret
128
+ with_lock do
129
+ url = "#{self.adspower_listener}:#{port}/api/v1/browser/start?user_id=#{id}&headless=#{headless ? '1' : '0'}"
130
+ uri = URI.parse(url)
131
+ res = Net::HTTP.get(uri)
132
+ ret = JSON.parse(res)
133
+ raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success'
134
+ ret
135
+ end
132
136
  end
133
137
 
134
- # run the browser
135
- # return the URL to operate the browser thru selenium
136
- #
137
- # reference: https://localapi-doc-en.adspower.com/docs/DXam94
138
- #
138
+ # Stop the browser session for the given user profile.
139
139
  def stop(id)
140
- # if the profile is running with driver, kill chromedriver
141
- if @@drivers[id] && self.check(id)
142
- @@drivers[id].quit
143
- @@drivers[id] = nil
144
- end
140
+ with_lock do
141
+ if @@drivers[id] && self.check(id)
142
+ @@drivers[id].quit
143
+ @@drivers[id] = nil
144
+ end
145
145
 
146
- uri = URI.parse("#{self.adspower_listener}:#{port}/api/v1/browser/stop?user_id=#{id}")
147
- res = Net::HTTP.get(uri)
148
- # show respose body
149
- ret = JSON.parse(res)
150
- raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success'
151
- # return id of the created user
152
- ret
146
+ uri = URI.parse("#{self.adspower_listener}:#{port}/api/v1/browser/stop?user_id=#{id}")
147
+ res = Net::HTTP.get(uri)
148
+ ret = JSON.parse(res)
149
+ raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success'
150
+ ret
151
+ end
153
152
  end
154
153
 
155
- # send an GET request to "#{url}/status"
156
- # and return if I get the json response['data']['status'] == 'Active'.
157
- # Otherwise, return false.
158
- #
159
- # reference: https://localapi-doc-en.adspower.com/docs/YjFggL
160
- #
154
+ # Check if the browser session for the given user profile is active.
161
155
  def check(id)
162
- url = "#{self.adspower_listener}:#{port}/api/v1/browser/active?user_id=#{id}"
163
- uri = URI.parse(url)
164
- res = Net::HTTP.get(uri)
165
- # show respose body
166
- return false if JSON.parse(res)['msg'] != 'success'
167
- # return
168
- JSON.parse(res)['data']['status'] == 'Active'
156
+ with_lock do
157
+ url = "#{self.adspower_listener}:#{port}/api/v1/browser/active?user_id=#{id}"
158
+ uri = URI.parse(url)
159
+ res = Net::HTTP.get(uri)
160
+ return false if JSON.parse(res)['msg'] != 'success'
161
+ JSON.parse(res)['data']['status'] == 'Active'
162
+ end
169
163
  end
170
164
 
171
- #
165
+ # Attach to the existing browser session with Selenium WebDriver.
172
166
  def driver(id, headless=false)
173
- ret = self.start(id, headless)
167
+ # Return the existing driver if it's still active.
174
168
  old = @@drivers[id]
169
+ return old if old
170
+
171
+ # Otherwise, start the driver
172
+ ret = self.start(id, headless)
175
173
 
176
- # si este driver sigue activo, lo devuelvo
177
- return old if old && self.check(id)
178
-
179
174
  # Attach test execution to the existing browser
180
- # reference: https://zhiminzhan.medium.com/my-innovative-solution-to-test-automation-attach-test-execution-to-the-existing-browser-b90cda3b7d4a
181
175
  url = ret['data']['ws']['selenium']
182
176
  opts = Selenium::WebDriver::Chrome::Options.new
183
177
  opts.add_option("debuggerAddress", url)
184
178
 
185
- # connect to the existing browser
186
- # reference: https://localapi-doc-en.adspower.com/docs/K4IsTq
187
- driver = Selenium::WebDriver.for(:chrome, :options=>opts)
179
+ # Connect to the existing browser
180
+ driver = Selenium::WebDriver.for(:chrome, options: opts)
188
181
 
189
- # save the driver
182
+ # Save the driver
190
183
  @@drivers[id] = driver
191
184
 
192
- # return
185
+ # Return the driver
193
186
  driver
194
- end # def driver
187
+ end
195
188
 
196
- # create a new profile
197
- # start the browser
198
- # visit the page
199
- # grab the html
200
- # quit the browser from webdriver
201
- # stop the broser from adspower
202
- # delete the profile
203
- # return the html
189
+ # Create a new profile, start the browser, visit a page, grab the HTML, and clean up.
204
190
  def html(url)
205
191
  ret = {
206
192
  :profile_id => nil,
@@ -210,44 +196,44 @@ class AdsPowerClient
210
196
  id = nil
211
197
  html = nil
212
198
  begin
213
- # create the profile
214
- sleep(1) # Avoid the "Too many request per second" error
199
+ # Create the profile
200
+ sleep(1)
215
201
  id = self.create
216
202
 
217
- # update the result
203
+ # Update the result
218
204
  ret[:profile_id] = id
219
205
 
220
- # start the profile and attach the driver
206
+ # Start the profile and attach the driver
221
207
  driver = self.driver(id)
222
208
 
223
- # get html
209
+ # Get HTML
224
210
  driver.get(url)
225
211
  html = driver.page_source
226
212
 
227
- # update the result
213
+ # Update the result
228
214
  ret[:html] = html
229
215
 
230
- # stop the profile
231
- sleep(1) # Avoid the "Too many request per second" error
216
+ # Stop the profile
217
+ sleep(1)
232
218
  driver.quit
233
219
  self.stop(id)
234
220
 
235
- # delete the profile
236
- sleep(1) # Avoid the "Too many request per second" error
221
+ # Delete the profile
222
+ sleep(1)
237
223
  self.delete(id)
238
224
 
239
- # reset id
225
+ # Reset ID
240
226
  id = nil
241
227
  rescue => e
242
- # stop and delete current profile
228
+ # Stop and delete current profile if an error occurs
243
229
  if id
244
- sleep(1) # Avoid the "Too many request per second" error
230
+ sleep(1)
245
231
  self.stop(id)
246
- sleep(1) # Avoid the "Too many request per second" error
247
- driver.quit
232
+ sleep(1)
233
+ driver.quit if driver
248
234
  self.delete(id) if id
249
- end # if id
250
- # inform the exception
235
+ end
236
+ # Inform the exception
251
237
  ret[:status] = e.to_s
252
238
  # # process interruption
253
239
  # rescue SignalException, SystemExit, Interrupt => e
@@ -259,7 +245,7 @@ class AdsPowerClient
259
245
  # self.delete(id) if id
260
246
  # end # if id
261
247
  end
262
- # return
248
+ # Return
263
249
  ret
264
- end # def html
265
- end # class AdsPowerClient
250
+ end
251
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adspower-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.11
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leandro Daniel Sardi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-31 00:00:00.000000000 Z
11
+ date: 2024-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uri