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 +4 -4
- data/adspower-client.gemspec +2 -2
- data/lib/adspower-client.rb +132 -146
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f1a3cd88b7f76b1514641e9fbc54b2c1b7dd15945da9f07533be8bb9782a812
|
4
|
+
data.tar.gz: b6d7708277cbaeae8cad513362a29b2260b4ee516b88a698894a0d0284cc32e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da5102f80bac0665d4117ab96d76ebfc6a82e8421ef8b61f0c5bed11429a9d11d218e1711b6e99e14b8778c54b139ab5ba4d84c4d0733b3f69ebea83a6e6d0fc
|
7
|
+
data.tar.gz: 9ec0a6920213f25c472a749b91231c2a33241d00093c5a32e166f3ee3fee3c40ff22189ec5f6bcf2b8b40a436b5ee6cfee3fe14bb17ca703e792e528fbe3f915
|
data/adspower-client.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'adspower-client'
|
3
|
-
s.version = '1.0.
|
4
|
-
s.date = '2024-
|
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"]
|
data/lib/adspower-client.rb
CHANGED
@@ -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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
68
|
+
# Kill all the adspower_global processes running on the local computer.
|
50
69
|
def server_stop
|
51
|
-
|
52
|
-
|
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
|
-
#
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
#
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
#
|
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
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
#
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
140
|
+
with_lock do
|
141
|
+
if @@drivers[id] && self.check(id)
|
142
|
+
@@drivers[id].quit
|
143
|
+
@@drivers[id] = nil
|
144
|
+
end
|
145
145
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
#
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
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
|
-
#
|
186
|
-
|
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
|
-
#
|
182
|
+
# Save the driver
|
190
183
|
@@drivers[id] = driver
|
191
184
|
|
192
|
-
#
|
185
|
+
# Return the driver
|
193
186
|
driver
|
194
|
-
end
|
187
|
+
end
|
195
188
|
|
196
|
-
#
|
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
|
-
#
|
214
|
-
sleep(1)
|
199
|
+
# Create the profile
|
200
|
+
sleep(1)
|
215
201
|
id = self.create
|
216
202
|
|
217
|
-
#
|
203
|
+
# Update the result
|
218
204
|
ret[:profile_id] = id
|
219
205
|
|
220
|
-
#
|
206
|
+
# Start the profile and attach the driver
|
221
207
|
driver = self.driver(id)
|
222
208
|
|
223
|
-
#
|
209
|
+
# Get HTML
|
224
210
|
driver.get(url)
|
225
211
|
html = driver.page_source
|
226
212
|
|
227
|
-
#
|
213
|
+
# Update the result
|
228
214
|
ret[:html] = html
|
229
215
|
|
230
|
-
#
|
231
|
-
sleep(1)
|
216
|
+
# Stop the profile
|
217
|
+
sleep(1)
|
232
218
|
driver.quit
|
233
219
|
self.stop(id)
|
234
220
|
|
235
|
-
#
|
236
|
-
sleep(1)
|
221
|
+
# Delete the profile
|
222
|
+
sleep(1)
|
237
223
|
self.delete(id)
|
238
224
|
|
239
|
-
#
|
225
|
+
# Reset ID
|
240
226
|
id = nil
|
241
227
|
rescue => e
|
242
|
-
#
|
228
|
+
# Stop and delete current profile if an error occurs
|
243
229
|
if id
|
244
|
-
sleep(1)
|
230
|
+
sleep(1)
|
245
231
|
self.stop(id)
|
246
|
-
sleep(1)
|
247
|
-
driver.quit
|
232
|
+
sleep(1)
|
233
|
+
driver.quit if driver
|
248
234
|
self.delete(id) if id
|
249
|
-
end
|
250
|
-
#
|
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
|
-
#
|
248
|
+
# Return
|
263
249
|
ret
|
264
|
-
end
|
265
|
-
end
|
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.
|
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-
|
11
|
+
date: 2024-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uri
|