adspower-client 1.0.11 → 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
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