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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fa9af194c360323a9ddb4533f0be4df21414e2a3645fdebb03a9a13eb315183
4
- data.tar.gz: 474d47d140310200db7919c293e260761e90f33bbeef72ad923b27cfdbb180e2
3
+ metadata.gz: 6c91e849c43700d337b826208f705deef1bb3ec6e493ac872cd7d838afc67859
4
+ data.tar.gz: 176aefa58b9bd965fcbab16b7714dec6ac5cf3a8e0cad23b759248ae82b6fbd3
5
5
  SHA512:
6
- metadata.gz: 3e39840a574b00918fc9365be4ee9ffde97c8d9bdbcc4017184a2b4589dd99de31c6c6cc0e7a4eb14f6506b0e8e686ffe96f256d69dd7688dd379de9bab61b0d
7
- data.tar.gz: 93957f83998d804fe74a423337fb13afa8bff70ed1eddb74a4da0f7cd9722b857eed3870797670ce71c8387e3f7cab745752c48df7f6c5b9520e9f3d8b21c152
6
+ metadata.gz: 564d87a5328385d750e0734363a7dae2a20e56469801b4042818deb6c6f81468cdf0172d80bc47b09a432fb03b0834147771f11c6b939e10544b3d73952b450c
7
+ data.tar.gz: 4aa7af6322c6489d26733ff50117e3341d50fe68ecdba7846642149cbccb2874619483aa4a5bf0debacbb4d2ed93d2b5ca9eaf5507ca57afa755190c6dda2724
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'adspower-client'
3
- s.version = '1.0.17'
4
- s.date = '2025-07-27'
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"]
@@ -49,9 +49,23 @@ class AdsPowerClient
49
49
 
50
50
  # Release the lock
51
51
  def release_lock
52
- @lockfile.flock(File::LOCK_UN) if @lockfile
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.17
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-07-27 00:00:00.000000000 Z
11
+ date: 2025-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uri