adspower 1.0.0__py3-none-any.whl

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.
adspower/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ """
2
+ adspower
3
+ ~~~~~~~~~~~~~~
4
+ The package for interacting with anti-detect browser APIs.
5
+ Author's github - https://github.com/blnkoff
6
+
7
+ Usage example:
8
+ >>> from adspower import AdsPower
9
+ >>> ads_power = AdsPower('ja54rwh')
10
+ >>> with ads_power as driver:
11
+ >>> driver.get('https://github.com/blnkoff/adspower')
12
+ :copyright: (c) 2023 by Alexey
13
+ :license: Apache 2.0, see LICENSE for more details.
14
+ """
15
+
16
+ __version__ = "1.0.0"
17
+
18
+ from .adspower import AdsPower
19
+ from .types import (UserProxyConfig, FingerprintConfig, CreateProfileParams, QueryProfilesParams, QueryGroupParams,
20
+ GroupInfo, ProfileInfo)
adspower/adspower.py ADDED
@@ -0,0 +1,498 @@
1
+ import time
2
+ import requests
3
+ from requests.exceptions import ConnectionError as RequestConnectionError, InvalidURL
4
+ from urllib3.exceptions import MaxRetryError, NewConnectionError
5
+ from .types import *
6
+ from .exceptions import *
7
+ from functools import wraps
8
+ from selenium import webdriver
9
+ from typing import Self, Optional, Any, Type, Iterable
10
+ from selenium.webdriver.chrome.options import Options
11
+ from selenium.webdriver.chrome.service import Service
12
+ from selenium.webdriver.chrome.webdriver import WebDriver
13
+ from .utils import is_typed_dict
14
+
15
+ _request_accessibility = 0
16
+
17
+ __all__ = ['AdsPower']
18
+
19
+
20
+ class AdsPower:
21
+ """Controls AdsPower overall and the instance of corresponding profile"""
22
+
23
+ def __init__(self, profile_id: Optional[str] = None, port: int = 50325):
24
+ """
25
+ :param profile_id: ID of existed profile in AdsPower
26
+ :param port: The port of AdsPower's API
27
+ """
28
+ self._driver = None
29
+ self.__profile_id = profile_id
30
+ self.__api_url = self._validate_api_url('http://local.adspower.net', port)
31
+
32
+ def __enter__(self):
33
+ driver = self.get_driver()
34
+ driver.maximize_window()
35
+ for _ in range(3):
36
+ time.sleep(1.5)
37
+ self.close_tabs()
38
+ return driver
39
+
40
+ def __exit__(self, exc_type, exc_val, exc_tb):
41
+ self.quit()
42
+
43
+ @property
44
+ def driver(self) -> WebDriver | None:
45
+ """
46
+ :return: The driver instance or None if get_driver() was not called
47
+ """
48
+ return self._driver
49
+
50
+ @property
51
+ def api_url(self) -> str:
52
+ """
53
+ :return: The URL of API of an anti-detect browser
54
+ """
55
+ return self.__api_url
56
+
57
+ @property
58
+ def profile_id(self) -> str:
59
+ """
60
+ Returns ID of existed profile in anti-detect browser
61
+ :return: The ID of existed profile in anti-detect
62
+ """
63
+ return self.__profile_id
64
+
65
+ @profile_id.setter
66
+ def profile_id(self, value: str):
67
+ self.__profile_id = value
68
+
69
+ @staticmethod
70
+ def _wait_for_delay(func):
71
+ @wraps(func)
72
+ def wrapper(*args, **kwargs) -> Any:
73
+ current_time = time.time()
74
+ global _request_accessibility
75
+
76
+ if _request_accessibility < current_time:
77
+ result = func(*args, **kwargs)
78
+ else:
79
+ time.sleep(_request_accessibility - current_time)
80
+ result = func(*args, **kwargs)
81
+
82
+ _request_accessibility = time.time() + 0.7
83
+ return result
84
+
85
+ return wrapper
86
+
87
+ @staticmethod
88
+ def _validate_response(exception: Type[Exception], request: dict[str, Any], response: dict[str, Any]):
89
+ if response['code'] != 0:
90
+ if response['msg'] == 'Too many request per second, please check':
91
+ raise ExceededQPS
92
+ else:
93
+ raise exception(request, response)
94
+
95
+ @staticmethod
96
+ def _validate_api_url(api_url: str, port: int) -> str:
97
+ url = f'{api_url}:{port}'
98
+ try:
99
+ status_url = url + '/status'
100
+ requests.get(url=status_url)
101
+ except (MaxRetryError, RequestConnectionError, NewConnectionError, ConnectionRefusedError, InvalidURL):
102
+ raise UnavailableAPI(port)
103
+ else:
104
+ return url
105
+
106
+ @_wait_for_delay
107
+ def get_driver(self, profile_id: Optional[str] = None) -> WebDriver:
108
+ """
109
+ Returns a driver associated with profile_id
110
+
111
+ :raise ProfileNotFound: Raised when profile getting or deleting is failed
112
+ :raise NoProfileFound: Raised when profile ID is not specified
113
+ :return: A WebDriver instance associated with profile_id
114
+ """
115
+ profile_id = profile_id or self.profile_id
116
+ if not profile_id:
117
+ raise NoProfileIdFound
118
+
119
+ self.profile_id = profile_id
120
+ url = self.api_url + '/api/v1/browser/start'
121
+
122
+ params = {
123
+ 'ip_tab': 0,
124
+ 'user_id': profile_id
125
+ }
126
+
127
+ response = requests.get(url=url, params=params).json()
128
+ self._validate_response(ProfileNotFound, params, response)
129
+
130
+ debugger_address = response['data']['ws']['selenium']
131
+ chrome_driver = response['data']['webdriver']
132
+
133
+ options = Options()
134
+ options.add_experimental_option('debuggerAddress', debugger_address)
135
+ options.page_load_strategy = 'none'
136
+ options.add_argument('--headless=new')
137
+
138
+ service = Service(executable_path=chrome_driver)
139
+ self._driver = webdriver.Chrome(service=service, options=options)
140
+ return self.driver
141
+
142
+ @classmethod
143
+ @_wait_for_delay
144
+ def create_profile(
145
+ cls,
146
+ data: Optional[CreateProfileParams] = None,
147
+ port: int = 50325,
148
+ *,
149
+ group_id: Optional[int] = None,
150
+ name: Optional[str] = None,
151
+ user_proxy_config: Optional[UserProxyConfig] = None,
152
+ fingerprint_config: Optional[FingerprintConfig] = None
153
+ ) -> Self:
154
+ """
155
+ Returns an instance of class referring to a new profile
156
+
157
+ :param data: Keyword arguments as dictionary to create profile
158
+ :param port: The port of API
159
+ :param name: The name of the account, no more than 100 characters
160
+ :param group_id: The group ID corresponding to the group in which an account is to be created,
161
+ :param user_proxy_config: Dictionary arguments to update a proxy
162
+ :param fingerprint_config: Dictionary arguments to update a fingerprint
163
+
164
+ :raise ProfileCreationError: Raised when profile creation is failed
165
+ :return: An AdsPower instance with the specified profile ID
166
+
167
+ Request parameters
168
+ ----------
169
+ {
170
+ 'name':
171
+ The name of the account, no more than 100 characters,
172
+ 'group_id':
173
+ The group ID corresponding to the group in which an account is to be created,
174
+ 'user_proxy_config': {
175
+ 'proxy_soft':
176
+ The proxy software. Currently, supports:
177
+ luminati, lumauto, oxylabsatuto, 922S5,ipideaauto,ipfoxyauto,ssh, other, no_proxy,
178
+ Default value - no_proxy,
179
+ 'proxy_type':
180
+ The proxy type. Currently, supports:
181
+ http, https, socks5;
182
+ For no_proxy, you may pass the parameter or not,
183
+ 'proxy_host':
184
+ Address of the proxy server, users can enter domain name or IP;
185
+ For no_proxy, you may pass the parameter or not,
186
+ 'proxy_port':
187
+ Port of the proxy server;
188
+ For no_proxy, you may pass the parameter or not,
189
+ 'proxy_user':
190
+ The username of the proxy,
191
+ 'proxy_password':
192
+ The password of the proxy
193
+ },
194
+
195
+ 'fingerprint_config': {
196
+ 'language_switch':
197
+ Configure languages based on IP country. Default value - 0
198
+ 0: Off;
199
+ 1: On.
200
+ 'ua':
201
+ String containing user agent.
202
+ When customizing please make sure that ua format and content meet
203
+ the requirement.
204
+ }
205
+ }
206
+ """
207
+ kwargs = locals()
208
+ del kwargs['port']
209
+ del kwargs['data']
210
+ del kwargs['cls']
211
+
212
+ api_url = cls._validate_api_url('http://local.adspower.net', port)
213
+
214
+ url = api_url + '/api/v1/user/create'
215
+
216
+ data = data or kwargs
217
+ if not data.get('group_id'):
218
+ raise NoGroupIdFound
219
+
220
+ if not data.get('user_proxy_config'):
221
+ data['user_proxy_config'] = {'proxy_soft': 'no_proxy'}
222
+
223
+ response = requests.post(url=url, json=data).json()
224
+ cls._validate_response(ProfileCreationError, data, response)
225
+
226
+ return cls(response['data']['id'], port)
227
+
228
+ @classmethod
229
+ @_wait_for_delay
230
+ def delete_profiles(cls, profile_ids: str | Iterable[str], port: int = 50325) -> None:
231
+ """
232
+ Deletes profiles associated with specified profile_ids in AdsPower
233
+ :param profile_ids: Iterable containing profile_ids or one profile ID
234
+ :param port: The port of AdsPower's API
235
+
236
+ :raise ProfileNotFound: Raised when a profile does not exist
237
+ :return: None
238
+ """
239
+ if not profile_ids:
240
+ raise NoProfileIdFound
241
+
242
+ api_url = cls._validate_api_url('http://local.adspower.net', port)
243
+ url = api_url + '/api/v1/user/delete'
244
+
245
+ if isinstance(profile_ids, str):
246
+ profile_ids = [profile_ids]
247
+ else:
248
+ profile_ids_ = []
249
+ for profile_id in profile_ids:
250
+ profile_ids_.append(profile_id)
251
+ profile_ids = profile_ids_
252
+
253
+ data = {'user_ids': profile_ids}
254
+
255
+ response = requests.post(url=url, json=data).json()
256
+ cls._validate_response(ProfileNotFound, data, response)
257
+
258
+ @classmethod
259
+ @_wait_for_delay
260
+ def query_group(
261
+ cls,
262
+ params: Optional[QueryGroupParams] = None,
263
+ port: int = 50325,
264
+ *,
265
+ group_name: Optional[str] = None,
266
+ profile_id: Optional[str] = None,
267
+ page_size: Optional[int] = 100
268
+ ) -> GroupResponse:
269
+ """
270
+ Returns dictionaries containing information about the groups by specified parameters. If there are no
271
+ params, excluding page_size, it returns a list of dictionaries, containing information about all groups
272
+
273
+ :param params: Keyword arguments as dictionary to query groups
274
+ :param port: The port of AdsPower's API
275
+ :param group_name: The group name corresponding to the group
276
+ :param profile_id: ID of existed profile in AdsPower
277
+ :param page_size: The maximum length of returning list. Default value - 100
278
+
279
+ :raise GroupQueryError: Raised when group query is failed
280
+ :return: List of dictionaries containing information about corresponding groups
281
+
282
+ Request parameters
283
+ ----------
284
+ {
285
+ 'group_name':
286
+ The group name corresponding to the group,
287
+ 'profile_id':
288
+ ID of existed profile in AdsPower,
289
+ 'page_size':
290
+ The maximum length of returning list. Default value - 100
291
+ }
292
+ """
293
+ kwargs = locals()
294
+ del kwargs['cls']
295
+ del kwargs['params']
296
+ del kwargs['port']
297
+
298
+ api_url = cls._validate_api_url('http://local.adspower.net', port)
299
+ url = api_url + '/api/v1/group/list'
300
+
301
+ params = params or kwargs
302
+ if not params.get('page_size'):
303
+ params['page_size'] = 100
304
+
305
+ if len(params) == 1:
306
+ response = requests.get(url=url, params=params).json()
307
+ cls._validate_response(GroupQueryError, params, response)
308
+ return response['data']['list']
309
+ else:
310
+ params['user_id'] = params.pop('profile_id')
311
+ response = requests.get(url=url, params=params).json()
312
+ cls._validate_response(GroupQueryError, params, response)
313
+ return response['data']['list'][0]
314
+
315
+ @classmethod
316
+ @_wait_for_delay
317
+ def query_profiles(
318
+ cls,
319
+ params: Optional[QueryProfilesParams] = None,
320
+ port: int = 50325,
321
+ *,
322
+ group_id: Optional[int] = None,
323
+ page_size: Optional[int] = 100
324
+ ) -> ProfileResponse:
325
+ """
326
+ Returns a list consisting of dictionaries containing information about the profiles by specified parameters
327
+
328
+ :param params: Keyword arguments as dictionary to query profiles
329
+ :param port: The port of AdsPower's API
330
+ :param group_id: The group ID corresponding to the group in which an account is to be created,
331
+ :param page_size: The maximum length of returning list. Default value - 100
332
+
333
+ :raise ProfileQueryError: Raised when profile query is failed
334
+ :return: list[dict[str, str]]: List of dictionaries containing information about corresponding profiles
335
+
336
+ Request parameters
337
+ ----------
338
+ {
339
+ 'group_id':
340
+ The group ID corresponding to the group in which an account exists,
341
+ 'page_size':
342
+ The maximum length of returning list. Default value - 100
343
+ }
344
+ """
345
+ kwargs = locals()
346
+ del kwargs['cls']
347
+ del kwargs['params']
348
+ del kwargs['port']
349
+
350
+ api_url = cls._validate_api_url('http://local.adspower.net', port)
351
+ url = api_url + '/api/v1/user/list'
352
+
353
+ params = params or kwargs
354
+ if not params.get('page_size'):
355
+ params['page_size'] = 100
356
+
357
+ response = requests.get(url=url, params=params).json()
358
+
359
+ cls._validate_response(ProfileQueryError, params, response)
360
+
361
+ return response['data']['list']
362
+
363
+ @_wait_for_delay
364
+ def update_proxy(
365
+ self,
366
+ data: Optional[UpdatingProxyParams] = None,
367
+ *,
368
+ profile_id: Optional[str] = None,
369
+ proxy_soft: Optional[str] = None,
370
+ proxy_type: Optional[str] = None,
371
+ proxy_host: Optional[str] = None,
372
+ proxy_user: Optional[str] = None,
373
+ proxy_password: Optional[str] = None
374
+ ) -> None:
375
+ """
376
+ Updates proxy in current profile
377
+
378
+ :param data: Keyword arguments as dictionary to update proxy
379
+ :param profile_id: ID of existed profile in AdsPower
380
+ :param proxy_soft: The proxy software. Currently, supports:
381
+ luminati, lumauto, oxylabsatuto, 922S5,ipideaauto,ipfoxyauto,ssh, other, noproxy,
382
+ By default you should to set 'other'.
383
+ :param proxy_type: The proxy type. Currently, supports:
384
+ http, https, socks5;
385
+ For no_proxy, you may pass the parameter or not
386
+ :param proxy_host: Address of the proxy server, users can enter domain name or IP;
387
+ For no_proxy, you may pass the parameter or not,
388
+ :param proxy_user: The username of the proxy,
389
+ :param proxy_password: The password of the proxy
390
+
391
+ :raise InvalidProxyConfig: Raised when proxy config is invalid
392
+ :raise ProxyUpdateError: Raised when proxy update is failed
393
+ :return: None
394
+
395
+ Request parameters
396
+ ----------
397
+ {
398
+ 'profile_id':
399
+ ID of existed profile in AdsPower
400
+ 'proxy_soft':
401
+ The proxy software. Currently, supports:
402
+ luminati, lumauto, oxylabsatuto, 922S5,ipideaauto,ipfoxyauto,ssh, other, noproxy,
403
+ By default you should to set 'other',
404
+ 'proxy_type':
405
+ The proxy type. Currently, supports:
406
+ http, https, socks5;
407
+ For no_proxy, you may pass the parameter or not,
408
+ 'proxy_host':
409
+ Address of the proxy server, users can enter domain name or IP;
410
+ For no_proxy, you may pass the parameter or not,
411
+ 'proxy_port':
412
+ Port of the proxy server;
413
+ For no_proxy, you may pass the parameter or not,
414
+ 'proxy_user':
415
+ The username of the proxy,
416
+ 'proxy_password':
417
+ The password of the proxy
418
+ }
419
+ """
420
+ kwargs = locals()
421
+ del kwargs['data']
422
+ del kwargs['self']
423
+
424
+ url = self.api_url + '/api/v1/user/update'
425
+
426
+ data = data or kwargs
427
+ if not data.get('profile_id'):
428
+ data['profile_id'] = self.profile_id
429
+
430
+ if not is_typed_dict(data, UpdatingProxyParams):
431
+ raise InvalidProxyConfig(data)
432
+
433
+ profile_id = data.pop('profile_id')
434
+ data_to_send = {
435
+ 'user_id': profile_id,
436
+ 'user_proxy_config': data
437
+ }
438
+
439
+ response = requests.post(url=url, json=data_to_send).json()
440
+ self._validate_response(ProxyUpdateError, data_to_send, response)
441
+
442
+ @_wait_for_delay
443
+ def update_user_agent(self, user_agent: str, profile_id: Optional[str] = None) -> None:
444
+ """
445
+ Updates user agent in current profile
446
+
447
+ :param profile_id: ID of existed profile in AdsPower
448
+ :param user_agent: String containing user agent.
449
+ When customizing please make sure that ua format and content meet
450
+ the requirement.
451
+
452
+ :raise NoUserAgentFound: Raised when user agent is not specified
453
+ :raise UserAgentUpdateError: Raised when user agent update is failed
454
+ :return: None
455
+ """
456
+ url = self.api_url + '/api/v1/user/update'
457
+ if not user_agent:
458
+ raise NoUserAgentFound
459
+
460
+ user_agent = user_agent
461
+ profile_id = self.profile_id or profile_id
462
+ data = {
463
+ 'user_id': profile_id,
464
+ 'fingerprint_config': {'ua': user_agent}
465
+ }
466
+ response = requests.post(url=url, json=data).json()
467
+
468
+ self._validate_response(UserAgentUpdateError, data, response)
469
+
470
+ def close_tabs(self) -> None:
471
+ """
472
+ Closes all tabs in anti-detect browser
473
+
474
+ :return: None
475
+ """
476
+ driver = self.driver
477
+ original_window_handle = driver.current_window_handle
478
+ windows = driver.window_handles
479
+ for window in windows:
480
+ if original_window_handle != window:
481
+ driver.switch_to.window(window)
482
+ driver.close()
483
+ driver.switch_to.window(original_window_handle)
484
+
485
+ @_wait_for_delay
486
+ def quit(self) -> None:
487
+ """
488
+ Closes current profile
489
+
490
+ :raise QuittingProfileError: Quitting profile is failed. Profile can be already closed
491
+ :return: None
492
+ """
493
+ self.driver.quit()
494
+ profile_id = self.profile_id
495
+ url = self.api_url + '/api/v1/browser/stop'
496
+ params = {'user_id': profile_id}
497
+ response = requests.get(url=url, params=params).json()
498
+ self._validate_response(QuittingProfileError, params, response)
adspower/exceptions.py ADDED
@@ -0,0 +1,93 @@
1
+ from typing import Any
2
+ from adspower.types import UpdatingProxyParams
3
+
4
+
5
+ class NoParamsFound(Exception):
6
+ """Raised when params are not specified via positional arguments or keyword arguments"""
7
+ def __init__(self):
8
+ super().__init__("Params are not specified via positional arguments or keyword arguments")
9
+
10
+
11
+ class ZeroResponse(Exception):
12
+ """Raised if response is 0"""
13
+ def __init__(self, message: str, request: dict[str, Any], response: dict[str]):
14
+ super().__init__(f"{message}. \nRequest: {request}. \nResponse: {response}")
15
+
16
+
17
+ class ProxyUpdateError(ZeroResponse):
18
+ """Raised when proxy update is failed"""
19
+ def __init__(self, request: dict[str, Any], response: dict[str]):
20
+ super().__init__("The proxy update is failed", request, response)
21
+
22
+
23
+ class UserAgentUpdateError(ZeroResponse):
24
+ """Raised when user agent update is failed"""
25
+ def __init__(self, request: dict[str, Any], response: dict[str]):
26
+ super().__init__("The user agent update is failed", request, response)
27
+
28
+
29
+ class ProfileCreationError(ZeroResponse):
30
+ """Raised when profile creation is failed"""
31
+ def __init__(self, request: dict[str, Any], response: dict[str]):
32
+ super().__init__("The profile creation is failed", request, response)
33
+
34
+
35
+ class GroupQueryError(ZeroResponse):
36
+ """Raised when group query is failed"""
37
+ def __init__(self, request: dict[str, Any], response: dict[str]):
38
+ super().__init__("The group query is failed", request, response)
39
+
40
+
41
+ class ProfileQueryError(ZeroResponse):
42
+ """Raised when profile query is failed"""
43
+ def __init__(self, request: dict[str, Any], response: dict[str]):
44
+ super().__init__("The profile query is failed", request, response)
45
+
46
+
47
+ class ProfileNotFound(ZeroResponse):
48
+ """Raised when profile getting or deleting is failed"""
49
+ def __init__(self, request: dict[str, Any], response: dict[str]):
50
+ super().__init__("Profile getting is failed", request, response)
51
+
52
+
53
+ class QuittingProfileError(ZeroResponse):
54
+ """Raised when quitting profile is failed."""
55
+ def __init__(self, request: dict[str, Any], response: dict[str]):
56
+ super().__init__("Quitting profile is failed. Profile can be already closed", request, response)
57
+
58
+
59
+ class NoGroupIdFound(Exception):
60
+ """Raised when group id is not specified"""
61
+ def __init__(self):
62
+ super().__init__("The id of a group isn't passed")
63
+
64
+
65
+ class NoProfileIdFound(Exception):
66
+ """Raised when profile id is not specified"""
67
+ def __init__(self):
68
+ super().__init__("The id of a profile isn't passed")
69
+
70
+
71
+ class NoUserAgentFound(Exception):
72
+ """Raised when user agent is not specified"""
73
+ def __init__(self):
74
+ super().__init__("The user agent isn't passed")
75
+
76
+
77
+ class InvalidProxyConfig(Exception):
78
+ """Raised when proxy config is invalid"""
79
+
80
+ def __init__(self, proxy_config: dict[str] | UpdatingProxyParams):
81
+ super().__init__(f"The proxy config is invalid. Config: {proxy_config}")
82
+
83
+
84
+ class ExceededQPS(Exception):
85
+ """Raised when amount of queries per second is exceeded"""
86
+ def __init__(self):
87
+ super().__init__("Too many request per second, please try later")
88
+
89
+
90
+ class UnavailableAPI(Exception):
91
+ """Raised when API url specified with invalid port or AdsPower is not opened"""
92
+ def __init__(self, port: int):
93
+ super().__init__(f"Your API url is specified with invalid port or AdsPower is not opened. You provided port {port}")
adspower/types.py ADDED
@@ -0,0 +1,68 @@
1
+ from typing import TypedDict, Literal, NotRequired
2
+
3
+ ProxySoft = Literal['luminati', 'lumauto', 'oxylabsatuto', '922S5', 'ipideaauto',
4
+ 'ipfoxyauto', 'ssh', 'other', 'no_proxy']
5
+
6
+ ProxyType = Literal['http', 'https', 'socks5']
7
+
8
+
9
+ class UserProxyConfig(TypedDict):
10
+ proxy_soft: ProxySoft
11
+ proxy_type: ProxyType
12
+ proxy_host: str
13
+ proxy_port: int
14
+ proxy_user: str
15
+ proxy_password: str
16
+
17
+
18
+ class UpdatingProxyParams(TypedDict):
19
+ profile_id: NotRequired[str]
20
+ proxy_config: UserProxyConfig
21
+
22
+
23
+ class FingerprintConfig(TypedDict):
24
+ ua: str
25
+ language_switch: NotRequired[bool]
26
+
27
+
28
+ class CreateProfileParams(TypedDict):
29
+ name: NotRequired[str]
30
+ group_id: int
31
+ user_proxy_config: NotRequired[UserProxyConfig]
32
+ fingerprint_config: NotRequired[FingerprintConfig]
33
+
34
+
35
+ class QueryGroupParams(TypedDict):
36
+ group_name: NotRequired[str]
37
+ profile_id: NotRequired[str]
38
+ page_size: NotRequired[int]
39
+
40
+
41
+ class QueryProfilesParams(TypedDict):
42
+ group_id: NotRequired[int]
43
+ page_size: NotRequired[int]
44
+
45
+
46
+ class GroupInfo(TypedDict):
47
+ group_id: int
48
+ group_name: str
49
+
50
+
51
+ class ProfileInfo(TypedDict):
52
+ serial_number: int
53
+ user_id: str
54
+ name: str
55
+ group_id: int
56
+ group_name: str
57
+ domain_name: str
58
+ username: str
59
+ remark: str
60
+ created_time: int
61
+ ip: str
62
+ ip_country: str
63
+ password: str
64
+ last_open_time: int
65
+
66
+
67
+ GroupResponse = list[GroupInfo] | GroupInfo
68
+ ProfileResponse = list[ProfileInfo]
adspower/utils.py ADDED
@@ -0,0 +1,12 @@
1
+ from typing import Type, Any, TypedDict
2
+
3
+
4
+ def is_typed_dict(value: Any, typed_dict: Type[TypedDict]) -> bool:
5
+ typed_dict_keys = typed_dict.__annotations__.keys()
6
+
7
+ try:
8
+ value_keys = value.keys()
9
+ except AttributeError:
10
+ return False
11
+
12
+ return typed_dict_keys == value_keys
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Alexey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.1
2
+ Name: adspower
3
+ Version: 1.0.0
4
+ Summary: The package for interacting with API of anti-detect browser AdsPower.
5
+ Home-page: https://github.com/blnkoff/adspower
6
+ License: MIT
7
+ Author: Alexey
8
+ Author-email: abelenkov2006@gmail.com
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
21
+ Requires-Dist: selenium (>=4.16.0,<5.0.0)
22
+ Project-URL: Repository, https://github.com/blnkoff/adspower
23
+ Description-Content-Type: text/markdown
24
+
25
+ # adspower
26
+
27
+ [![Croco Logo](https://i.ibb.co/G5Pjt6M/logo.png)](https://t.me/crocofactory)
28
+
29
+
30
+ The package for interacting with API of anti-detect browser AdsPower.
31
+
32
+ - **[Telegram channel](https://t.me/crocofactory)**
33
+ - **[Bug reports](https://github.com/blnkoff/ether-wallet/issues)**
34
+
35
+ adspower's source code is made available under the [MIT License](LICENSE)
36
+
37
+ # Restrictions
38
+ 1. During using the package, AdsPower must be opened.
39
+ 2. The local API is available only in paid AdsPower subscriptions
40
+ 3. AdsPower has frequency control for all APIs, max. access frequency: 1 request/second
41
+
42
+ # Quick start
43
+
44
+ For example, you can use driver by the following way.
45
+
46
+ ```python
47
+ from adspower import AdsPower
48
+ ads_power = AdsPower('ja54rwh')
49
+ with ads_power as driver:
50
+ driver.get('https://github.com/blnkoff/adspower')
51
+ ```
52
+
53
+ Here is example of usage of creating profiles from proxies
54
+
55
+ ```python
56
+ from adspower import AdsPower, CreateProfileParams, UserProxyConfig
57
+
58
+ def create_profiles(config: dict[str], proxies) -> list[AdsPower]:
59
+ group_name = config['ads_power']['group_name']
60
+
61
+ group_id = AdsPower.query_group(group_name=group_name)[0]['group_id']
62
+
63
+ data_set = []
64
+ for proxy in proxies:
65
+ proxy_config = UserProxyConfig(
66
+ proxy_soft='other',
67
+ proxy_type=proxy.type,
68
+ proxy_host=proxy.host,
69
+ proxy_port=proxy.port,
70
+ proxy_user=proxy.login,
71
+ proxy_password=proxy.password
72
+ )
73
+ data_set.append(CreateProfileParams(group_id=group_id, user_proxy_config=proxy_config))
74
+
75
+ profiles = []
76
+ for data in data_set:
77
+ profiles.append(AdsPower.create_profile(data))
78
+
79
+ return profiles
80
+ ```
81
+
82
+ Here is example of retrieving profiles from AdsPower
83
+
84
+ ```python
85
+ from adspower import AdsPower
86
+
87
+ def retrieve_profiles(config: dict[str]):
88
+ group_name = config['ads_power']['group_name']
89
+
90
+ group_id = AdsPower.query_group(group_name=group_name)['group_id']
91
+
92
+ response = AdsPower.query_profiles(group_id=group_id)
93
+
94
+ profiles = [AdsPower(profile['user_id']) for profile in response]
95
+ return profiles
96
+ ```
97
+
98
+ # Installing adspower
99
+ To install the package from GitHub you can use that:
100
+ ```sh
101
+ pip install git+https://github.com/blnkoff/adspower.git
102
+ ```
103
+
104
+ To install the package from PyPi you can use that:
105
+ ```sh
106
+ pip install adspower
107
+ ```
108
+
@@ -0,0 +1,9 @@
1
+ adspower/__init__.py,sha256=SqLfmebxYnpDqY01oqdgYc3jfi3WWJajn6OOHqiuMw4,621
2
+ adspower/adspower.py,sha256=wiTSAJwSH4ySZPJqnZ_0XwDTo6gUN77V9Idk0znKzkU,19184
3
+ adspower/exceptions.py,sha256=KWmwdcwOHVtYjgUt79jr57ezg9m30MoTdOaLOUh_SZI,3453
4
+ adspower/types.py,sha256=o11hjtALPoD05kxUJyyGhUB0z1_A-3Qh-QbE092-85A,1467
5
+ adspower/utils.py,sha256=4wRhKNNlPtDn-MjcD9IwtwMPbY1qefhFB--KvJEq340,300
6
+ adspower-1.0.0.dist-info/LICENSE,sha256=3ownyzAdj6Al-5KBJr7dgqt-9mJ1TiSyJHaVmvkT1wI,1062
7
+ adspower-1.0.0.dist-info/METADATA,sha256=W57ShiV-jLkA5EFXqN9b6-SV90xxj8gBL_wK7W1CzeA,3307
8
+ adspower-1.0.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
9
+ adspower-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.8.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any