xselfbot 4.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.
- xselfbot/__init__.py +34 -0
- xselfbot/_captcha/__init__.py +2 -0
- xselfbot/_captcha/base.py +111 -0
- xselfbot/_captcha/capsolver.py +95 -0
- xselfbot/bookmark.py +64 -0
- xselfbot/client/client.py +4527 -0
- xselfbot/client/gql.py +730 -0
- xselfbot/client/v11.py +512 -0
- xselfbot/community.py +282 -0
- xselfbot/constants.py +268 -0
- xselfbot/content_disclosure.py +104 -0
- xselfbot/errors.py +110 -0
- xselfbot/geo.py +82 -0
- xselfbot/group.py +259 -0
- xselfbot/guest/__init__.py +3 -0
- xselfbot/guest/client.py +411 -0
- xselfbot/guest/tweet.py +235 -0
- xselfbot/guest/user.py +196 -0
- xselfbot/list.py +255 -0
- xselfbot/media.py +346 -0
- xselfbot/message.py +143 -0
- xselfbot/notification.py +47 -0
- xselfbot/py.typed +0 -0
- xselfbot/streaming.py +266 -0
- xselfbot/trend.py +93 -0
- xselfbot/tweet.py +790 -0
- xselfbot/ui_metrics/__init__.py +25 -0
- xselfbot/ui_metrics/dom.py +52 -0
- xselfbot/user.py +532 -0
- xselfbot/utils.py +427 -0
- xselfbot/x_client_transaction/__init__.py +8 -0
- xselfbot/x_client_transaction/cubic_curve.py +48 -0
- xselfbot/x_client_transaction/interpolate.py +23 -0
- xselfbot/x_client_transaction/rotation.py +27 -0
- xselfbot/x_client_transaction/transaction.py +170 -0
- xselfbot/x_client_transaction/utils.py +84 -0
- xselfbot-4.0.0.dist-info/METADATA +138 -0
- xselfbot-4.0.0.dist-info/RECORD +41 -0
- xselfbot-4.0.0.dist-info/WHEEL +5 -0
- xselfbot-4.0.0.dist-info/licenses/LICENSE +21 -0
- xselfbot-4.0.0.dist-info/top_level.txt +1 -0
xselfbot/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
=========
|
|
3
|
+
XSelfBot
|
|
4
|
+
=========
|
|
5
|
+
|
|
6
|
+
A Python library for interacting with x.com: posting, reading, searching and
|
|
7
|
+
streaming over a cookie session, with no API key.
|
|
8
|
+
|
|
9
|
+
https://github.com/Tky567/xselfbot
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = '4.0.0'
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
if os.name == 'nt':
|
|
18
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
19
|
+
|
|
20
|
+
from ._captcha import Capsolver
|
|
21
|
+
from .bookmark import BookmarkFolder
|
|
22
|
+
from .errors import *
|
|
23
|
+
from .utils import build_query
|
|
24
|
+
from .client.client import Client
|
|
25
|
+
from .community import Community, CommunityCreator, CommunityMember, CommunityRule
|
|
26
|
+
from .geo import Place
|
|
27
|
+
from .group import Group, GroupMessage
|
|
28
|
+
from .list import List
|
|
29
|
+
from .message import Message
|
|
30
|
+
from .notification import Notification
|
|
31
|
+
from .trend import Trend
|
|
32
|
+
from .tweet import CommunityNote, Poll, ScheduledTweet, Tweet
|
|
33
|
+
from .content_disclosure import build_content_disclosure, applied_disclosures
|
|
34
|
+
from .user import User
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
5
|
+
|
|
6
|
+
from bs4 import BeautifulSoup
|
|
7
|
+
from httpx import Response
|
|
8
|
+
from ..constants import DOMAIN
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..client.client import Client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UnlockHTML(NamedTuple):
|
|
15
|
+
authenticity_token: str
|
|
16
|
+
assignment_token: str
|
|
17
|
+
needs_unlock: bool
|
|
18
|
+
start_button: bool
|
|
19
|
+
finish_button: bool
|
|
20
|
+
delete_button: bool
|
|
21
|
+
blob: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CaptchaSolver:
|
|
25
|
+
client: Client
|
|
26
|
+
max_attempts: int
|
|
27
|
+
|
|
28
|
+
CAPTCHA_URL = f'https://{DOMAIN}/account/access'
|
|
29
|
+
CAPTCHA_SITE_KEY = '0152B4EB-D2DC-460A-89A1-629838B529C9'
|
|
30
|
+
|
|
31
|
+
async def get_unlock_html(self) -> tuple[Response, UnlockHTML]:
|
|
32
|
+
headers = {
|
|
33
|
+
'X-Twitter-Client-Language': 'en-US',
|
|
34
|
+
'User-Agent': self.client._user_agent,
|
|
35
|
+
'Upgrade-Insecure-Requests': '1'
|
|
36
|
+
}
|
|
37
|
+
_, response = await self.client.get(
|
|
38
|
+
self.CAPTCHA_URL, headers=headers
|
|
39
|
+
)
|
|
40
|
+
return response, parse_unlock_html(response.text)
|
|
41
|
+
|
|
42
|
+
async def ui_metrix(self) -> str:
|
|
43
|
+
js, _ = await self.client.get(
|
|
44
|
+
f'https://{DOMAIN}/i/js_inst?c_name=ui_metrics'
|
|
45
|
+
)
|
|
46
|
+
return re.findall(r'return ({.*?});', js, re.DOTALL)[0]
|
|
47
|
+
|
|
48
|
+
async def confirm_unlock(
|
|
49
|
+
self,
|
|
50
|
+
authenticity_token: str,
|
|
51
|
+
assignment_token: str,
|
|
52
|
+
verification_string: str = None,
|
|
53
|
+
ui_metrics: bool = False
|
|
54
|
+
) -> tuple[Response, UnlockHTML]:
|
|
55
|
+
data = {
|
|
56
|
+
'authenticity_token': authenticity_token,
|
|
57
|
+
'assignment_token': assignment_token,
|
|
58
|
+
'lang': 'en',
|
|
59
|
+
'flow': '',
|
|
60
|
+
}
|
|
61
|
+
params = {}
|
|
62
|
+
if verification_string:
|
|
63
|
+
data['verification_string'] = verification_string
|
|
64
|
+
data['language_code'] = 'en'
|
|
65
|
+
params['lang'] = 'en'
|
|
66
|
+
if ui_metrics:
|
|
67
|
+
data['ui_metrics'] = await self.client._ui_metrix()
|
|
68
|
+
headers = {
|
|
69
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
70
|
+
'Upgrade-Insecure-Requests': '1',
|
|
71
|
+
'Referer': self.CAPTCHA_URL
|
|
72
|
+
}
|
|
73
|
+
_, response = await self.client.post(
|
|
74
|
+
self.CAPTCHA_URL, params=params, data=data, headers=headers
|
|
75
|
+
)
|
|
76
|
+
return response, parse_unlock_html(response.text)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def parse_unlock_html(html: str) -> UnlockHTML:
|
|
80
|
+
soup = BeautifulSoup(html, 'lxml')
|
|
81
|
+
|
|
82
|
+
authenticity_token = None
|
|
83
|
+
authenticity_token_element = soup.find(
|
|
84
|
+
'input', {'name': 'authenticity_token'}
|
|
85
|
+
)
|
|
86
|
+
if authenticity_token_element is not None:
|
|
87
|
+
authenticity_token: str = authenticity_token_element.get('value')
|
|
88
|
+
|
|
89
|
+
assignment_token = None
|
|
90
|
+
assignment_token_element = soup.find('input', {'name': 'assignment_token'})
|
|
91
|
+
if assignment_token_element is not None:
|
|
92
|
+
assignment_token = assignment_token_element.get('value')
|
|
93
|
+
|
|
94
|
+
verification_string = soup.find('input', id='verification_string')
|
|
95
|
+
needs_unlock = bool(verification_string)
|
|
96
|
+
start_button = bool(soup.find('input', value='Start'))
|
|
97
|
+
finish_button = bool(soup.find('input', value='Continue to X'))
|
|
98
|
+
delete_button = bool(soup.find('input', value='Delete'))
|
|
99
|
+
|
|
100
|
+
iframe = soup.find(id='arkose_iframe')
|
|
101
|
+
blob = re.findall(r'data=(.+)', iframe['src'])[0] if iframe else None
|
|
102
|
+
|
|
103
|
+
return UnlockHTML(
|
|
104
|
+
authenticity_token,
|
|
105
|
+
assignment_token,
|
|
106
|
+
needs_unlock,
|
|
107
|
+
start_button,
|
|
108
|
+
finish_button,
|
|
109
|
+
delete_button,
|
|
110
|
+
blob
|
|
111
|
+
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from time import sleep
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from .base import CaptchaSolver
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Capsolver(CaptchaSolver):
|
|
11
|
+
"""
|
|
12
|
+
You can automatically unlock the account by passing the `captcha_solver`
|
|
13
|
+
argument when initialising the :class:`.Client`.
|
|
14
|
+
|
|
15
|
+
First, visit https://capsolver.com and obtain your Capsolver API key.
|
|
16
|
+
Next, pass the Capsolver instance to the client as shown in the example.
|
|
17
|
+
|
|
18
|
+
.. code-block:: python
|
|
19
|
+
|
|
20
|
+
from xselfbot import Capsolver, Client
|
|
21
|
+
solver = Capsolver(
|
|
22
|
+
api_key='your_api_key',
|
|
23
|
+
max_attempts=10
|
|
24
|
+
)
|
|
25
|
+
client = Client(captcha_solver=solver)
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
api_key : :class:`str`
|
|
30
|
+
Capsolver API key.
|
|
31
|
+
max_attempts : :class:`int`, default=3
|
|
32
|
+
The maximum number of attempts to solve the captcha.
|
|
33
|
+
get_result_interval : :class:`float`, default=1.0
|
|
34
|
+
|
|
35
|
+
use_blob_data : :class:`bool`, default=False
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
api_key: str,
|
|
41
|
+
max_attempts: int = 3,
|
|
42
|
+
get_result_interval: float = 1.0,
|
|
43
|
+
use_blob_data: bool = False
|
|
44
|
+
) -> None:
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
self.get_result_interval = get_result_interval
|
|
47
|
+
self.max_attempts = max_attempts
|
|
48
|
+
self.use_blob_data = use_blob_data
|
|
49
|
+
|
|
50
|
+
def create_task(self, task_data: dict) -> dict:
|
|
51
|
+
data = {
|
|
52
|
+
'clientKey': self.api_key,
|
|
53
|
+
'task': task_data
|
|
54
|
+
}
|
|
55
|
+
response = httpx.post(
|
|
56
|
+
'https://api.capsolver.com/createTask',
|
|
57
|
+
json=data,
|
|
58
|
+
headers={'content-type': 'application/json'}
|
|
59
|
+
).json()
|
|
60
|
+
return response
|
|
61
|
+
|
|
62
|
+
def get_task_result(self, task_id: str) -> dict:
|
|
63
|
+
data = {
|
|
64
|
+
'clientKey': self.api_key,
|
|
65
|
+
'taskId': task_id
|
|
66
|
+
}
|
|
67
|
+
response = httpx.post(
|
|
68
|
+
'https://api.capsolver.com/getTaskResult',
|
|
69
|
+
json=data,
|
|
70
|
+
headers={'content-type': 'application/json'}
|
|
71
|
+
).json()
|
|
72
|
+
return response
|
|
73
|
+
|
|
74
|
+
def solve_funcaptcha(self, blob: str) -> dict:
|
|
75
|
+
if self.client.proxy is None:
|
|
76
|
+
captcha_type = 'FunCaptchaTaskProxyLess'
|
|
77
|
+
else:
|
|
78
|
+
captcha_type = 'FunCaptchaTask'
|
|
79
|
+
|
|
80
|
+
task_data = {
|
|
81
|
+
'type': captcha_type,
|
|
82
|
+
'websiteURL': 'https://iframe.arkoselabs.com',
|
|
83
|
+
'websitePublicKey': self.CAPTCHA_SITE_KEY,
|
|
84
|
+
'funcaptchaApiJSSubdomain': 'https://client-api.arkoselabs.com',
|
|
85
|
+
'proxy': self.client.proxy
|
|
86
|
+
}
|
|
87
|
+
if self.use_blob_data:
|
|
88
|
+
task_data['data'] = '{"blob":"%s"}' % blob
|
|
89
|
+
task_data['userAgent'] = self.client._user_agent
|
|
90
|
+
task = self.create_task(task_data)
|
|
91
|
+
while True:
|
|
92
|
+
sleep(self.get_result_interval)
|
|
93
|
+
result = self.get_task_result(task['taskId'])
|
|
94
|
+
if result['status'] in ('ready', 'failed'):
|
|
95
|
+
return result
|
xselfbot/bookmark.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from httpx import Response
|
|
7
|
+
|
|
8
|
+
from .client.client import Client
|
|
9
|
+
from .tweet import Tweet
|
|
10
|
+
from .utils import Result
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BookmarkFolder:
|
|
14
|
+
"""
|
|
15
|
+
Attributes
|
|
16
|
+
----------
|
|
17
|
+
id : :class:`str`
|
|
18
|
+
The ID of the folder.
|
|
19
|
+
name : :class:`str`
|
|
20
|
+
The name of the folder
|
|
21
|
+
media : :class:`str`
|
|
22
|
+
Icon image data.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, client: Client, data: dict) -> None:
|
|
25
|
+
self._client = client
|
|
26
|
+
|
|
27
|
+
self.id: str = data['id']
|
|
28
|
+
self.name: str = data['name']
|
|
29
|
+
self.media: dict = data['media']
|
|
30
|
+
|
|
31
|
+
async def get_tweets(self, cursor: str | None = None) -> Result[Tweet]:
|
|
32
|
+
"""
|
|
33
|
+
Retrieves tweets from the folder.
|
|
34
|
+
"""
|
|
35
|
+
return await self._client.get_bookmarks(
|
|
36
|
+
cursor=cursor, folder_id=self.id
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
async def edit(self, name: str) -> BookmarkFolder:
|
|
40
|
+
"""
|
|
41
|
+
Edits the folder.
|
|
42
|
+
"""
|
|
43
|
+
return await self._client.edit_bookmark_folder(self.id, name)
|
|
44
|
+
|
|
45
|
+
async def delete(self) -> Response:
|
|
46
|
+
"""
|
|
47
|
+
Deletes the folder.
|
|
48
|
+
"""
|
|
49
|
+
return await self._client.delete_bookmark_folder(self.id)
|
|
50
|
+
|
|
51
|
+
async def add(self, tweet_id: str) -> Response:
|
|
52
|
+
"""
|
|
53
|
+
Adds a tweet to the folder.
|
|
54
|
+
"""
|
|
55
|
+
return await self._client.bookmark_tweet(tweet_id, self.id)
|
|
56
|
+
|
|
57
|
+
def __eq__(self, __value: object) -> bool:
|
|
58
|
+
return isinstance(__value, BookmarkFolder) and self.id == __value.id
|
|
59
|
+
|
|
60
|
+
def __ne__(self, __value: object) -> bool:
|
|
61
|
+
return not self == __value
|
|
62
|
+
|
|
63
|
+
def __repr__(self) -> str:
|
|
64
|
+
return f'<BookmarkFolder id="{self.id}">'
|