tweepy-self 1.6.3__py3-none-any.whl → 1.10.0b1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tweepy-self
3
- Version: 1.6.3
3
+ Version: 1.10.0b1
4
4
  Summary: Twitter (selfbot) for Python!
5
5
  Home-page: https://github.com/alenkimov/tweepy-self
6
6
  Author: Alen
@@ -9,15 +9,19 @@ Requires-Python: >=3.11,<4.0
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: aiohttp (>=3.9,<4.0)
12
13
  Requires-Dist: beautifulsoup4 (>=4,<5)
13
- Requires-Dist: better-proxy (==1.1.1)
14
- Requires-Dist: curl_cffi (==0.6.0b9)
14
+ Requires-Dist: better-proxy (>=1.1,<2.0)
15
+ Requires-Dist: curl_cffi (==0.6.2)
16
+ Requires-Dist: loguru (>=0.7,<0.8)
15
17
  Requires-Dist: lxml (>=5,<6)
16
18
  Requires-Dist: pydantic (>=1)
17
19
  Requires-Dist: pyotp (>=2,<3)
18
- Requires-Dist: python3-capsolver (>=0.9,<0.10)
20
+ Requires-Dist: requests (>=2,<3)
21
+ Requires-Dist: tenacity (>=8,<9)
19
22
  Requires-Dist: yarl (>=1,<2)
20
23
  Project-URL: Repository, https://github.com/alenkimov/tweepy-self
24
+ Project-URL: Source, https://github.com/alenkimov/tweepy-self
21
25
  Description-Content-Type: text/markdown
22
26
 
23
27
  # Tweepy-self
@@ -99,9 +103,10 @@ Automating user accounts is against the Twitter ToS. This library is a proof of
99
103
 
100
104
  ### Примеры работы
101
105
  Запрос информации о пользователе:
106
+
102
107
  ```python
103
108
  # Запрос информации о текущем пользователе:
104
- me = await twitter_client.request_user_data()
109
+ me = await twitter_client.request_user()
105
110
  print(f"[{account.short_auth_token}] {me}")
106
111
  print(f"Аккаунт создан: {me.created_at}")
107
112
  print(f"Following (подписан ты): {me.followings_count}")
@@ -109,17 +114,18 @@ print(f"Followers (подписаны на тебя): {me.followers_count}")
109
114
  print(f"Прочая информация: {me.raw_data}")
110
115
 
111
116
  # Запрос информации об ином пользователе:
112
- elonmusk = await twitter.request_user_data("@elonmusk")
117
+ elonmusk = await twitter.request_user("@elonmusk")
113
118
  print(elonmusk)
114
119
  ```
115
120
 
116
121
  Смена имени пользователя и пароля:
122
+
117
123
  ```python
118
124
  account = twitter.Account("auth_token", password="password")
119
125
  ...
120
126
  await twitter_client.change_username("new_username")
121
- await twitter_client.request_user_data()
122
- print(f"New username: {account.data.username}")
127
+ await twitter_client.request_user()
128
+ print(f"New username: {account.username}")
123
129
 
124
130
  await twitter_client.change_password("new_password")
125
131
  print(f"New password: {account.password}")
@@ -165,8 +171,9 @@ bind_code = await twitter_client.oauth_2(**bind_data)
165
171
  ```
166
172
 
167
173
  Отправка сообщения:
174
+
168
175
  ```python
169
- bro = await twitter_client.request_user_data("@username")
176
+ bro = await twitter_client.request_user("@username")
170
177
  await twitter_client.send_message(bro.id, "I love you!")
171
178
  ```
172
179
 
@@ -0,0 +1,23 @@
1
+ twitter/__init__.py,sha256=-CmcPdm1z-OkG8LkJVe75PwdYKBqBfMpD9WdoXcnGuc,732
2
+ twitter/_capsolver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ twitter/_capsolver/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ twitter/_capsolver/core/base.py,sha256=In3qDLgRh1z1UZLaLFgYcDEdnqW3d62PVzgEjU2S4BU,8883
5
+ twitter/_capsolver/core/config.py,sha256=8_eXT6N2hBheN2uCMNhqk8tLZRJjLDTYLK208fqIkhM,1054
6
+ twitter/_capsolver/core/enum.py,sha256=ivfAEN6jrg3iaq5C3H7CuRqsvOloX1b8lF8cLa3zaiY,1741
7
+ twitter/_capsolver/core/serializer.py,sha256=xPEUIPgytuw2wM1ubTY3RMhJGVyp_d3bokPTx0BjF0c,2602
8
+ twitter/_capsolver/fun_captcha.py,sha256=VVbTmn08cGnvPMGdJmPxaLfAIPxyA68oTSAyEL8RWnU,10974
9
+ twitter/account.py,sha256=joAB5Zw-Le5E3kOZ-1nb4DPGlTqWYv2Vs6gJ3cwu7is,3175
10
+ twitter/base/__init__.py,sha256=Q2ko0HeOS5tiBnDVKxxaZYetwRR3YXJ67ujL3oThGd4,141
11
+ twitter/base/client.py,sha256=J_iL4ZGfwTbZ2gpjtFCbBxNgt7weJ55EeMGzYsLtjf4,500
12
+ twitter/base/session.py,sha256=JFPS-9Qae1iY3NfNcywxvWWmRDijaU_Rjs3WaQ00iFA,2071
13
+ twitter/client.py,sha256=CySQ-hTFiPGFKhPBNw4nn_xnO5hdpjmXK90QpSEzRG4,66878
14
+ twitter/enums.py,sha256=-OH6Ibxarq5qt4E2AhkProVawcEyIf5YG_h_G5xiV9Y,270
15
+ twitter/errors.py,sha256=oNa0Neos80ZK4-0FBzqgxXonH564qFnoN-kavHalfR4,5274
16
+ twitter/models.py,sha256=7yObMPUUEwJEbraHzFwmUKd91UhR2-zyfJTm4xIqrSQ,4834
17
+ twitter/utils/__init__.py,sha256=usxpfcRQ7zxTTgZ-i425tT7hIz73Pwh9FDj4t6O3dYg,663
18
+ twitter/utils/file.py,sha256=Sz2KEF9DnL04aOP1XabuMYMMF4VR8dJ_KWMEVvQ666Y,1120
19
+ twitter/utils/html.py,sha256=hVtIRFI2yRAdWEaShFNBG-_ZWxd16og8i8OVDnFy5Hc,1971
20
+ twitter/utils/other.py,sha256=9RIYF2AMdmNKIwClG3jBP7zlvxZPEgYfuHaIiOhURzM,1061
21
+ tweepy_self-1.10.0b1.dist-info/METADATA,sha256=FRxYUeZHlrxUB9Jr_nbxlhEkLc1pjO-ik_XiShkwARg,9438
22
+ tweepy_self-1.10.0b1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
23
+ tweepy_self-1.10.0b1.dist-info/RECORD,,
twitter/__init__.py CHANGED
@@ -6,14 +6,23 @@ A basic wrapper for the Twitter user API.
6
6
  """
7
7
 
8
8
  from .client import Client
9
- from .account import Account, AccountStatus, load_accounts_from_file, extract_accounts_to_file
10
- from .models import Tweet, UserData
9
+ from .account import (
10
+ Account,
11
+ AccountStatus,
12
+ load_accounts_from_file,
13
+ extract_accounts_to_file,
14
+ )
15
+ from .models import Tweet, User, Media, Image
11
16
  from . import errors, utils
12
17
 
13
18
  __all__ = [
14
19
  "Client",
15
20
  "Account",
16
21
  "AccountStatus",
22
+ "Tweet",
23
+ "User",
24
+ "Media",
25
+ "Image",
17
26
  "utils",
18
27
  "errors",
19
28
  "load_accounts_from_file",
@@ -22,9 +31,10 @@ __all__ = [
22
31
 
23
32
 
24
33
  import warnings
34
+
25
35
  # HACK: Ignore event loop warnings from curl_cffi
26
- warnings.filterwarnings('ignore', module='curl_cffi')
36
+ warnings.filterwarnings("ignore", module="curl_cffi")
27
37
 
38
+ from loguru import logger
28
39
 
29
- from python3_capsolver.core import config
30
- config.APP_ID = "6F895B2F-F454-44D1-8FE0-77ACAD3DBDC8"
40
+ logger.disable("twitter")
File without changes
File without changes
@@ -0,0 +1,227 @@
1
+ import time
2
+ import asyncio
3
+ import logging
4
+ from typing import Any, Dict, Type
5
+ from urllib import parse
6
+
7
+ import aiohttp
8
+ import requests
9
+ from pydantic import BaseModel
10
+ from requests.adapters import HTTPAdapter
11
+
12
+ from .enum import ResponseStatusEnm, EndpointPostfixEnm
13
+ from .config import RETRIES, REQUEST_URL, VALID_STATUS_CODES, attempts_generator
14
+ from .serializer import (
15
+ CaptchaOptionsSer,
16
+ CaptchaResponseSer,
17
+ RequestCreateTaskSer,
18
+ RequestGetTaskResultSer,
19
+ )
20
+
21
+
22
+ class BaseCaptcha:
23
+ """
24
+ Basic Captcha solving class
25
+
26
+ Args:
27
+ api_key: Capsolver API key
28
+ captcha_type: Captcha type name, like `ReCaptchaV2Task` and etc.
29
+ sleep_time: The waiting time between requests to get the result of the Captcha
30
+ request_url: API address for sending requests
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ api_key: str,
36
+ sleep_time: int = 5,
37
+ request_url: str = REQUEST_URL,
38
+ **kwargs,
39
+ ):
40
+ # assign args to validator
41
+ self.__params = CaptchaOptionsSer(**locals())
42
+ self.__request_url = request_url
43
+
44
+ # prepare session
45
+ self.__session = requests.Session()
46
+ self.__session.mount("http://", HTTPAdapter(max_retries=RETRIES))
47
+ self.__session.mount("https://", HTTPAdapter(max_retries=RETRIES))
48
+
49
+ def _prepare_create_task_payload(self, serializer: Type[BaseModel], create_params: Dict[str, Any] = None) -> None:
50
+ """
51
+ Method prepare `createTask` payload
52
+
53
+ Args:
54
+ serializer: Serializer for task creation
55
+ create_params: Parameters for task creation payload
56
+
57
+ Examples:
58
+
59
+ >>> self._prepare_create_task_payload(serializer=PostRequestSer, create_params={})
60
+
61
+ """
62
+ self.task_payload = serializer(clientKey=self.__params.api_key)
63
+ # added task params to payload
64
+ self.task_payload.task = {**create_params} if create_params else {}
65
+
66
+ def __enter__(self):
67
+ return self
68
+
69
+ def __exit__(self, exc_type, exc_value, traceback):
70
+ if exc_type:
71
+ return False
72
+ return True
73
+
74
+ async def __aenter__(self):
75
+ return self
76
+
77
+ async def __aexit__(self, exc_type, exc_value, traceback):
78
+ if exc_type:
79
+ return False
80
+ return True
81
+
82
+ """
83
+ Sync part
84
+ """
85
+
86
+ def _processing_captcha(
87
+ self, create_params: dict, serializer: Type[BaseModel] = RequestCreateTaskSer
88
+ ) -> CaptchaResponseSer:
89
+ self._prepare_create_task_payload(serializer=serializer, create_params=create_params)
90
+ self.created_task_data = CaptchaResponseSer(**self._create_task())
91
+
92
+ # if task created and ready - return result
93
+ if self.created_task_data.status == ResponseStatusEnm.Ready.value:
94
+ return self.created_task_data
95
+ # if captcha is not ready but task success created - waiting captcha result
96
+ elif self.created_task_data.errorId == 0:
97
+ return self._get_result()
98
+ return self.created_task_data
99
+
100
+ def _create_task(self, url_postfix: str = EndpointPostfixEnm.CREATE_TASK.value) -> dict:
101
+ """
102
+ Function send SYNC request to service and wait for result
103
+ """
104
+ try:
105
+ resp = self.__session.post(
106
+ parse.urljoin(self.__request_url, url_postfix), json=self.task_payload.dict(exclude_none=True)
107
+ )
108
+ if resp.status_code in VALID_STATUS_CODES:
109
+ return resp.json()
110
+ else:
111
+ raise ValueError(resp.raise_for_status())
112
+ except Exception as error:
113
+ logging.exception(error)
114
+ raise
115
+
116
+ def _get_result(self, url_postfix: str = EndpointPostfixEnm.GET_TASK_RESULT.value) -> CaptchaResponseSer:
117
+ """
118
+ Method send SYNC request to service and wait for result
119
+ """
120
+ # initial waiting
121
+ time.sleep(self.__params.sleep_time)
122
+
123
+ get_result_payload = RequestGetTaskResultSer(
124
+ clientKey=self.__params.api_key, taskId=self.created_task_data.taskId
125
+ )
126
+ attempts = attempts_generator()
127
+ for _ in attempts:
128
+ try:
129
+ resp = self.__session.post(
130
+ parse.urljoin(self.__request_url, url_postfix), json=get_result_payload.dict(exclude_none=True)
131
+ )
132
+ if resp.status_code in VALID_STATUS_CODES:
133
+ result_data = CaptchaResponseSer(**resp.json())
134
+ if result_data.status in (ResponseStatusEnm.Ready, ResponseStatusEnm.Failed):
135
+ # if captcha ready\failed or have unknown status - return exist data
136
+ return result_data
137
+ else:
138
+ raise ValueError(resp.raise_for_status())
139
+ except Exception as error:
140
+ logging.exception(error)
141
+ raise
142
+
143
+ # if captcha just created or in processing now - wait
144
+ time.sleep(self.__params.sleep_time)
145
+ # default response if server is silent
146
+ return CaptchaResponseSer(
147
+ errorId=1,
148
+ errorCode="ERROR_CAPTCHA_UNSOLVABLE",
149
+ errorDescription="Captcha not recognized",
150
+ taskId=self.created_task_data.taskId,
151
+ status=ResponseStatusEnm.Failed,
152
+ )
153
+
154
+ """
155
+ Async part
156
+ """
157
+
158
+ async def _aio_processing_captcha(
159
+ self, create_params: dict, serializer: Type[BaseModel] = RequestCreateTaskSer
160
+ ) -> CaptchaResponseSer:
161
+ self._prepare_create_task_payload(serializer=serializer, create_params=create_params)
162
+ self.created_task_data = CaptchaResponseSer(**await self._aio_create_task())
163
+
164
+ # if task created and already ready - return result
165
+ if self.created_task_data.status == ResponseStatusEnm.Ready.value:
166
+ return self.created_task_data
167
+ # if captcha is not ready but task success created - waiting captcha result
168
+ elif self.created_task_data.errorId == 0:
169
+ return await self._aio_get_result()
170
+ return self.created_task_data
171
+
172
+ async def _aio_create_task(self, url_postfix: str = EndpointPostfixEnm.CREATE_TASK.value) -> dict:
173
+ """
174
+ Function send the ASYNC request to service and wait for result
175
+ """
176
+ async with aiohttp.ClientSession() as session:
177
+ try:
178
+ async with session.post(
179
+ parse.urljoin(self.__request_url, url_postfix), json=self.task_payload.dict(exclude_none=True)
180
+ ) as resp:
181
+ if resp.status in VALID_STATUS_CODES:
182
+ return await resp.json()
183
+ else:
184
+ raise ValueError(resp.reason)
185
+ except Exception as error:
186
+ logging.exception(error)
187
+ raise
188
+
189
+ async def _aio_get_result(self, url_postfix: str = EndpointPostfixEnm.GET_TASK_RESULT.value) -> CaptchaResponseSer:
190
+ """
191
+ Function send the ASYNC request to service and wait for result
192
+ """
193
+ # initial waiting
194
+ await asyncio.sleep(self.__params.sleep_time)
195
+
196
+ get_result_payload = RequestGetTaskResultSer(
197
+ clientKey=self.__params.api_key, taskId=self.created_task_data.taskId
198
+ )
199
+ attempts = attempts_generator()
200
+ async with aiohttp.ClientSession() as session:
201
+ for _ in attempts:
202
+ try:
203
+ async with session.post(
204
+ parse.urljoin(self.__request_url, url_postfix), json=get_result_payload.dict(exclude_none=True)
205
+ ) as resp:
206
+ if resp.status in VALID_STATUS_CODES:
207
+ result_data = CaptchaResponseSer(**await resp.json())
208
+ if result_data.status in (ResponseStatusEnm.Ready, ResponseStatusEnm.Failed):
209
+ # if captcha ready\failed or have unknown status - return exist data
210
+ return result_data
211
+ else:
212
+ raise ValueError(resp.reason)
213
+ except Exception as error:
214
+ logging.exception(error)
215
+ raise
216
+
217
+ # if captcha just created or in processing now - wait
218
+ await asyncio.sleep(self.__params.sleep_time)
219
+
220
+ # default response if server is silent
221
+ return CaptchaResponseSer(
222
+ errorId=1,
223
+ errorCode="ERROR_CAPTCHA_UNSOLVABLE",
224
+ errorDescription="Captcha not recognized",
225
+ taskId=self.created_task_data.taskId,
226
+ status=ResponseStatusEnm.Failed,
227
+ )
@@ -0,0 +1,36 @@
1
+ from typing import Generator
2
+
3
+ from tenacity import AsyncRetrying, wait_fixed, stop_after_attempt
4
+ from requests.adapters import Retry
5
+
6
+ RETRIES = Retry(total=5, backoff_factor=0.9, status_forcelist=[500, 502, 503, 504])
7
+ ASYNC_RETRIES = AsyncRetrying(wait=wait_fixed(5), stop=stop_after_attempt(5), reraise=True)
8
+
9
+ REQUEST_URL = "https://api.capsolver.com"
10
+ VALID_STATUS_CODES = (200, 202, 400, 401, 405)
11
+
12
+ APP_ID = "6F895B2F-F454-44D1-8FE0-77ACAD3DBDC8"
13
+
14
+
15
+ # Connection retry generator
16
+ def attempts_generator(amount: int = 16) -> Generator:
17
+ """
18
+ Function generates a generator of length equal to `amount`
19
+
20
+ Args:
21
+ amount: number of attempts generated
22
+
23
+ Yields:
24
+ int: The next number in the range of 1 to ``amount`` - 1.
25
+
26
+ Examples:
27
+ Examples should be written in doctest format, and should illustrate how
28
+ to use the function.
29
+
30
+ >>> print([i for i in attempts_generator(5)])
31
+ [1, 2, 3, 4]
32
+
33
+ Returns:
34
+ Attempt number
35
+ """
36
+ yield from range(1, amount)
@@ -0,0 +1,66 @@
1
+ from enum import Enum
2
+ from types import DynamicClassAttribute
3
+ from typing import List
4
+
5
+
6
+ class MyEnum(Enum):
7
+ @classmethod
8
+ def list(cls) -> List[Enum]:
9
+ return list(map(lambda c: c, cls))
10
+
11
+ @classmethod
12
+ def list_values(cls) -> List[str]:
13
+ return list(map(lambda c: c.value, cls))
14
+
15
+ @classmethod
16
+ def list_names(cls) -> List[str]:
17
+ return list(map(lambda c: c.name, cls))
18
+
19
+ @DynamicClassAttribute
20
+ def name(self) -> str:
21
+ """
22
+ The name of the Enum member
23
+ """
24
+ return self._name_
25
+
26
+ @DynamicClassAttribute
27
+ def value(self) -> str:
28
+ """
29
+ The name of the Enum member
30
+ """
31
+ return self._value_
32
+
33
+
34
+ class EndpointPostfixEnm(str, MyEnum):
35
+ """
36
+ Enum stored URL postfixes for API endpoints
37
+ """
38
+
39
+ GET_BALANCE = "getBalance"
40
+ CREATE_TASK = "createTask"
41
+ GET_TASK_RESULT = "getTaskResult"
42
+ AKAMAI_BMP_INVOKE = "akamaibmp/invoke"
43
+ AKAMAI_WEB_INVOKE = "akamaiweb/invoke"
44
+
45
+
46
+ class FunCaptchaTypeEnm(str, MyEnum):
47
+ FunCaptchaTask = "FunCaptchaTask"
48
+ FunCaptchaTaskProxyLess = "FunCaptchaTaskProxyLess"
49
+
50
+
51
+ class FunCaptchaClassificationTypeEnm(str, MyEnum):
52
+ FunCaptchaClassification = "FunCaptchaClassification"
53
+
54
+
55
+ class ResponseStatusEnm(str, MyEnum):
56
+ """
57
+ Enum store results `status` field variants
58
+
59
+ Notes:
60
+ https://docs.capsolver.com/guide/api-createtask.html
61
+ """
62
+
63
+ Idle = "idle" # Task created
64
+ Processing = "processing" # Task is not ready yet
65
+ Ready = "ready" # Task completed, solution object can be found in solution property
66
+ Failed = "failed" # Task failed, check the errorDescription to know why failed.
@@ -0,0 +1,85 @@
1
+ from typing import Any, Dict, List, Literal, Optional
2
+
3
+ from pydantic import Field, BaseModel, conint
4
+
5
+ from .enum import ResponseStatusEnm
6
+ from .config import APP_ID
7
+
8
+ """
9
+ HTTP API Request ser
10
+ """
11
+
12
+
13
+ class PostRequestSer(BaseModel):
14
+ clientKey: str = Field(..., description="Client account key, can be found in user account")
15
+ task: dict = Field(None, description="Task object")
16
+
17
+
18
+ class TaskSer(BaseModel):
19
+ type: str = Field(..., description="Task type name", alias="captcha_type")
20
+
21
+
22
+ class RequestCreateTaskSer(PostRequestSer):
23
+ appId: Literal[APP_ID] = APP_ID
24
+
25
+
26
+ class RequestGetTaskResultSer(PostRequestSer):
27
+ taskId: Optional[str] = Field(None, description="ID created by the createTask method")
28
+
29
+
30
+ """
31
+ HTTP API Response ser
32
+ """
33
+
34
+
35
+ class ResponseSer(BaseModel):
36
+ errorId: int = Field(..., description="Error message: `False` - no error, `True` - with error")
37
+ # error info
38
+ errorCode: Optional[str] = Field(None, description="Error code")
39
+ errorDescription: Optional[str] = Field(None, description="Error description")
40
+
41
+
42
+ class CaptchaResponseSer(ResponseSer):
43
+ taskId: Optional[str] = Field(None, description="Task ID for future use in getTaskResult method.")
44
+ status: ResponseStatusEnm = Field(ResponseStatusEnm.Processing, description="Task current status")
45
+ solution: Dict[str, Any] = Field(None, description="Task result data. Different for each type of task.")
46
+
47
+ class Config:
48
+ populate_by_name = True
49
+
50
+
51
+ class ControlResponseSer(ResponseSer):
52
+ balance: Optional[float] = Field(0, description="Account balance value in USD")
53
+
54
+
55
+ """
56
+ Other ser
57
+ """
58
+
59
+
60
+ class CaptchaOptionsSer(BaseModel):
61
+ api_key: str
62
+ sleep_time: conint(ge=5) = 5
63
+
64
+
65
+ """
66
+ Captcha tasks ser
67
+ """
68
+
69
+
70
+ class FunCaptchaClassificationOptionsSer(TaskSer):
71
+ images: List[str] = Field(..., description="Base64-encoded images, do not include 'data:image/***;base64,'")
72
+ question: str = Field(
73
+ ...,
74
+ description="Question name. this param value from API response game_variant field. Exmaple: maze,maze2,flockCompass,3d_rollball_animals",
75
+ )
76
+
77
+
78
+ class FunCaptchaSer(TaskSer):
79
+ websiteURL: str = Field(..., description="Address of a webpage with Funcaptcha")
80
+ websitePublicKey: str = Field(..., description="Funcaptcha website key.")
81
+ funcaptchaApiJSSubdomain: Optional[str] = Field(
82
+ None,
83
+ description="A special subdomain of funcaptcha.com, from which the JS captcha widget should be loaded."
84
+ "Most FunCaptcha installations work from shared domains.",
85
+ )