adspower-client 1.0.17 → 1.0.19
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 +125 -4
- 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: 6c91e849c43700d337b826208f705deef1bb3ec6e493ac872cd7d838afc67859
|
|
4
|
+
data.tar.gz: 176aefa58b9bd965fcbab16b7714dec6ac5cf3a8e0cad23b759248ae82b6fbd3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 564d87a5328385d750e0734363a7dae2a20e56469801b4042818deb6c6f81468cdf0172d80bc47b09a432fb03b0834147771f11c6b939e10544b3d73952b450c
|
|
7
|
+
data.tar.gz: 4aa7af6322c6489d26733ff50117e3341d50fe68ecdba7846642149cbccb2874619483aa4a5bf0debacbb4d2ed93d2b5ca9eaf5507ca57afa755190c6dda2724
|
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 = '2025-
|
|
3
|
+
s.version = '1.0.19'
|
|
4
|
+
s.date = '2025-11-03'
|
|
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
|
@@ -49,9 +49,23 @@ class AdsPowerClient
|
|
|
49
49
|
|
|
50
50
|
# Release the lock
|
|
51
51
|
def release_lock
|
|
52
|
-
|
|
52
|
+
if @lockfile
|
|
53
|
+
@lockfile.flock(File::LOCK_UN)
|
|
54
|
+
# don't close while you expect further operations; close only on object shutdown
|
|
55
|
+
# @lockfile.close
|
|
56
|
+
end
|
|
53
57
|
end
|
|
54
|
-
|
|
58
|
+
=begin
|
|
59
|
+
# add a destructor to close file when object is GC'd / program shuts down
|
|
60
|
+
# Call client.close when you're done with the client instance (e.g., at program exit).
|
|
61
|
+
def close
|
|
62
|
+
if @lockfile && !@lockfile.closed?
|
|
63
|
+
@lockfile.flock(File::LOCK_UN) rescue nil
|
|
64
|
+
@lockfile.close rescue nil
|
|
65
|
+
@lockfile = nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
=end
|
|
55
69
|
# Wrapper method for critical sections
|
|
56
70
|
def with_lock
|
|
57
71
|
acquire_lock
|
|
@@ -81,12 +95,67 @@ class AdsPowerClient
|
|
|
81
95
|
return
|
|
82
96
|
end
|
|
83
97
|
|
|
98
|
+
def self.drivers
|
|
99
|
+
@@drivers
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Quit and remove all drivers (safe)
|
|
103
|
+
def self.cleanup_all
|
|
104
|
+
@@drivers.keys.each do |id|
|
|
105
|
+
drv = @@drivers[id]
|
|
106
|
+
begin
|
|
107
|
+
drv.quit if drv
|
|
108
|
+
rescue => e
|
|
109
|
+
# best-effort: ignore but log if desired
|
|
110
|
+
ensure
|
|
111
|
+
@@drivers.delete(id)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Quit and remove a specific driver (safe)
|
|
117
|
+
def self.cleanup(id)
|
|
118
|
+
drv = @@drivers[id]
|
|
119
|
+
begin
|
|
120
|
+
drv.quit if drv
|
|
121
|
+
rescue => e
|
|
122
|
+
# best-effort: ignore but log if desired
|
|
123
|
+
ensure
|
|
124
|
+
@@drivers.delete(id)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
=begin
|
|
128
|
+
# Try to kill processes matching a pattern (last resort)
|
|
129
|
+
# Call it only if quit and client.stop both failed.
|
|
130
|
+
def self.force_kill_by_ws(ws_endpoint)
|
|
131
|
+
# example ws_endpoint: "127.0.0.1:12345" -> 12345
|
|
132
|
+
port = ws_endpoint.split(':').last rescue nil
|
|
133
|
+
return unless port
|
|
134
|
+
# find and kill processes that hold that port
|
|
135
|
+
begin
|
|
136
|
+
pids = `lsof -ti tcp:#{port}`.split.map(&:to_i)
|
|
137
|
+
pids.each { |pid| Process.kill('KILL', pid) rescue nil }
|
|
138
|
+
rescue => e
|
|
139
|
+
# ignore/ log
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
=end
|
|
84
143
|
# Kill all the adspower_global processes running on the local computer.
|
|
85
144
|
def server_stop
|
|
86
145
|
with_lock do
|
|
87
146
|
self.server_pids.each { |pid|
|
|
88
147
|
`kill -9 #{pid}`
|
|
89
148
|
}
|
|
149
|
+
# clean up @@driver
|
|
150
|
+
@@drivers.each do |id, driver|
|
|
151
|
+
begin
|
|
152
|
+
driver.quit if driver
|
|
153
|
+
rescue => e
|
|
154
|
+
# Log or handle the exception if needed
|
|
155
|
+
ensure
|
|
156
|
+
@@drivers[id] = nil
|
|
157
|
+
end
|
|
158
|
+
end
|
|
90
159
|
end
|
|
91
160
|
return
|
|
92
161
|
end
|
|
@@ -104,6 +173,41 @@ class AdsPowerClient
|
|
|
104
173
|
end
|
|
105
174
|
end
|
|
106
175
|
end
|
|
176
|
+
|
|
177
|
+
# Fetch all groups
|
|
178
|
+
def list_groups
|
|
179
|
+
uri = URI("#{adspower_listener}:#{port}/api/v1/group/list?api_key=#{key}")
|
|
180
|
+
resp = Net::HTTP.get(uri)
|
|
181
|
+
data = JSON.parse(resp)
|
|
182
|
+
raise "Error listing groups: #{data['msg']}" unless data['code'] == 0
|
|
183
|
+
data['data']['list'] # => [{ "id" => 0, "name" => "Ungrouped" }, …]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Find the numeric ID for a given group name (exact match)
|
|
187
|
+
def find_group_id_by_name(name)
|
|
188
|
+
grp = list_groups.find { |g| g['group_name'].casecmp(name).zero? }
|
|
189
|
+
grp ? grp['group_id'] : nil
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# returns an Array of profile‑hashes from the local API
|
|
193
|
+
def list_profiles(group_id: nil)
|
|
194
|
+
all = []
|
|
195
|
+
page = 1
|
|
196
|
+
|
|
197
|
+
loop do
|
|
198
|
+
params = { page: page, limit: 100 }
|
|
199
|
+
params[:group_id] = group_id if group_id
|
|
200
|
+
url = "#{adspower_listener}:#{port}/api/v2/browser-profile/list"
|
|
201
|
+
resp = BlackStack::Netting.call_post(url, params)
|
|
202
|
+
data = JSON.parse(resp.body)
|
|
203
|
+
break unless data['code'] == 0 && data.dig('data','list').any?
|
|
204
|
+
batch = data['data']['list']
|
|
205
|
+
all += batch
|
|
206
|
+
page += 1
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
all
|
|
210
|
+
end
|
|
107
211
|
|
|
108
212
|
# Count current profiles (optionally filtered by group)
|
|
109
213
|
def profile_count(group_id: nil)
|
|
@@ -443,8 +547,25 @@ class AdsPowerClient
|
|
|
443
547
|
end
|
|
444
548
|
|
|
445
549
|
def driver2(id, headless: false, read_timeout: 180)
|
|
446
|
-
return @@drivers[id] if @@drivers[id]
|
|
447
|
-
|
|
550
|
+
#return @@drivers[id] if @@drivers[id]
|
|
551
|
+
# If we have a cached driver, verify it's still valid
|
|
552
|
+
if @@drivers[id]
|
|
553
|
+
begin
|
|
554
|
+
# quick, non-destructive sanity check: ask for window_handles
|
|
555
|
+
@@drivers[id].window_handles
|
|
556
|
+
return @@drivers[id]
|
|
557
|
+
rescue Selenium::WebDriver::Error::InvalidSessionIdError,
|
|
558
|
+
Selenium::WebDriver::Error::NoSuchWindowError,
|
|
559
|
+
Errno::ECONNREFUSED => e
|
|
560
|
+
# stale/broken driver: best-effort cleanup and continue to create a new one
|
|
561
|
+
#warn "detected stale driver for #{id}: #{e.class}: #{e.message}"
|
|
562
|
+
self.class.cleanup(id)
|
|
563
|
+
rescue => e
|
|
564
|
+
#warn "unexpected error checking cached driver: #{e.class}: #{e.message}"
|
|
565
|
+
self.class.cleanup(id)
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
448
569
|
# 1) start the AdsPower profile / grab its WebSocket URL
|
|
449
570
|
data = start(id, headless)['data']
|
|
450
571
|
ws = data['ws']['selenium'] # e.g. "127.0.0.1:XXXXX"
|
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.19
|
|
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: 2025-
|
|
11
|
+
date: 2025-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: uri
|