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 +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
|