tweety-temp 2.4.2__tar.gz
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.
- tweety_temp-2.4.2/PKG-INFO +6 -0
- tweety_temp-2.4.2/README.md +39 -0
- tweety_temp-2.4.2/pyproject.toml +9 -0
- tweety_temp-2.4.2/setup.cfg +20 -0
- tweety_temp-2.4.2/setup.py +20 -0
- tweety_temp-2.4.2/src/tweety/__init__.py +45 -0
- tweety_temp-2.4.2/src/tweety/auth.py +205 -0
- tweety_temp-2.4.2/src/tweety/bot.py +926 -0
- tweety_temp-2.4.2/src/tweety/builder.py +2583 -0
- tweety_temp-2.4.2/src/tweety/captcha/__init__.py +3 -0
- tweety_temp-2.4.2/src/tweety/captcha/anticaptcha.py +54 -0
- tweety_temp-2.4.2/src/tweety/captcha/base.py +144 -0
- tweety_temp-2.4.2/src/tweety/captcha/capsolver.py +43 -0
- tweety_temp-2.4.2/src/tweety/captcha/two_captcha.py +39 -0
- tweety_temp-2.4.2/src/tweety/constants.py +75 -0
- tweety_temp-2.4.2/src/tweety/events/__init__.py +2 -0
- tweety_temp-2.4.2/src/tweety/events/base.py +3 -0
- tweety_temp-2.4.2/src/tweety/events/newmessage.py +84 -0
- tweety_temp-2.4.2/src/tweety/events/stream_event.py +2 -0
- tweety_temp-2.4.2/src/tweety/exceptions.py +280 -0
- tweety_temp-2.4.2/src/tweety/exceptions_.py +5 -0
- tweety_temp-2.4.2/src/tweety/filters.py +96 -0
- tweety_temp-2.4.2/src/tweety/http.py +916 -0
- tweety_temp-2.4.2/src/tweety/session.py +90 -0
- tweety_temp-2.4.2/src/tweety/transaction.py +316 -0
- tweety_temp-2.4.2/src/tweety/types/__init__.py +72 -0
- tweety_temp-2.4.2/src/tweety/types/base.py +97 -0
- tweety_temp-2.4.2/src/tweety/types/bookmarks.py +38 -0
- tweety_temp-2.4.2/src/tweety/types/community.py +149 -0
- tweety_temp-2.4.2/src/tweety/types/follow.py +177 -0
- tweety_temp-2.4.2/src/tweety/types/gifs.py +38 -0
- tweety_temp-2.4.2/src/tweety/types/grok.py +89 -0
- tweety_temp-2.4.2/src/tweety/types/inbox.py +697 -0
- tweety_temp-2.4.2/src/tweety/types/likes.py +48 -0
- tweety_temp-2.4.2/src/tweety/types/lists.py +181 -0
- tweety_temp-2.4.2/src/tweety/types/mentions.py +45 -0
- tweety_temp-2.4.2/src/tweety/types/n_types.py +306 -0
- tweety_temp-2.4.2/src/tweety/types/notification.py +41 -0
- tweety_temp-2.4.2/src/tweety/types/places.py +42 -0
- tweety_temp-2.4.2/src/tweety/types/retweets.py +42 -0
- tweety_temp-2.4.2/src/tweety/types/search.py +134 -0
- tweety_temp-2.4.2/src/tweety/types/topic.py +44 -0
- tweety_temp-2.4.2/src/tweety/types/twDataTypes.py +2053 -0
- tweety_temp-2.4.2/src/tweety/types/usertweet.py +458 -0
- tweety_temp-2.4.2/src/tweety/updates.py +41 -0
- tweety_temp-2.4.2/src/tweety/user.py +1436 -0
- tweety_temp-2.4.2/src/tweety/utils.py +636 -0
- tweety_temp-2.4.2/src/tweety_temp.egg-info/PKG-INFO +6 -0
- tweety_temp-2.4.2/src/tweety_temp.egg-info/SOURCES.txt +54 -0
- tweety_temp-2.4.2/src/tweety_temp.egg-info/dependency_links.txt +1 -0
- tweety_temp-2.4.2/src/tweety_temp.egg-info/top_level.txt +1 -0
- tweety_temp-2.4.2/tests/test_authenticated.py +260 -0
- tweety_temp-2.4.2/tests/test_basic.py +207 -0
- tweety_temp-2.4.2/tests/test_comprehensive.py +392 -0
- tweety_temp-2.4.2/tests/test_profile_images.py +210 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# tweety
|
|
2
|
+
Reverse Engineered Twitter Frontend API.
|
|
3
|
+
|
|
4
|
+
[](https://pepy.tech/project/tweety-ns) [](https://deepwiki.com/mahrtayyab/tweety)
|
|
5
|
+
|
|
6
|
+
## Installation:
|
|
7
|
+
```bash
|
|
8
|
+
pip install tweety-ns
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Keep synced with latest fixes
|
|
12
|
+
|
|
13
|
+
##### **Pip might not be always updated , so to keep everything synced.**
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install https://github.com/mahrtayyab/tweety/archive/main.zip --upgrade
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## A Quick Example:
|
|
20
|
+
```python
|
|
21
|
+
from tweety import TwitterAsync
|
|
22
|
+
import asyncio
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
|
|
26
|
+
app = TwitterAsync("session")
|
|
27
|
+
all_tweets = await app.get_tweets("elonmusk")
|
|
28
|
+
for tweet in all_tweets:
|
|
29
|
+
print(tweet)
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> [!IMPORTANT]
|
|
35
|
+
> Even Twitter Web Client has a lot of rate limits now, Abusing tweety can lead to `read_only` Twitter account.
|
|
36
|
+
|
|
37
|
+
Do check [FAQs](https://github.com/mahrtayyab/tweety/wiki/FAQs)
|
|
38
|
+
|
|
39
|
+
Full Documentation and Changelogs are [here](https://mahrtayyab.github.io/tweety_docs/)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = tweety-temp
|
|
3
|
+
version = 2.4.2
|
|
4
|
+
|
|
5
|
+
[options]
|
|
6
|
+
package_dir =
|
|
7
|
+
= src
|
|
8
|
+
packages = find:
|
|
9
|
+
python_requires = >=3.9
|
|
10
|
+
|
|
11
|
+
[options.packages.find]
|
|
12
|
+
where = src
|
|
13
|
+
|
|
14
|
+
[project]
|
|
15
|
+
license = "MIT AND (Apache-2.0 OR BSD-2-Clause)"
|
|
16
|
+
|
|
17
|
+
[egg_info]
|
|
18
|
+
tag_build =
|
|
19
|
+
tag_date = 0
|
|
20
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from distutils.core import setup
|
|
2
|
+
|
|
3
|
+
install_requires = [
|
|
4
|
+
"beautifulsoup4[lxml]~=4.12",
|
|
5
|
+
"openpyxl",
|
|
6
|
+
"httpx[http2]",
|
|
7
|
+
"dateutils",
|
|
8
|
+
"anticaptchaofficial",
|
|
9
|
+
"capsolver",
|
|
10
|
+
"2captcha-python",
|
|
11
|
+
"python-magic",
|
|
12
|
+
"python-magic-bin; platform_system == 'Windows'"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
setup(
|
|
16
|
+
name='tweety-temp',
|
|
17
|
+
packages=['tweety', 'tweety.types', 'tweety.events', 'tweety.captcha'],
|
|
18
|
+
version='2.4.2',
|
|
19
|
+
license='MIT',
|
|
20
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
__version__ = "2.4.1"
|
|
2
|
+
__author__ = "mahrtayyab"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from .bot import BotMethods
|
|
7
|
+
from .updates import UpdateMethods
|
|
8
|
+
from .auth import AuthMethods
|
|
9
|
+
from .user import UserMethods
|
|
10
|
+
from .utils import get_running_loop
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def SyncWrap(cls):
|
|
14
|
+
def method_wrapper_decorator(method_):
|
|
15
|
+
def wrapper(self, *args, **kwargs):
|
|
16
|
+
coro = method_(self, *args, **kwargs)
|
|
17
|
+
loop = get_running_loop()
|
|
18
|
+
if loop.is_running():
|
|
19
|
+
return coro
|
|
20
|
+
else:
|
|
21
|
+
return loop.run_until_complete(coro)
|
|
22
|
+
|
|
23
|
+
return wrapper
|
|
24
|
+
|
|
25
|
+
if inspect.isclass(cls):
|
|
26
|
+
for name in dir(cls):
|
|
27
|
+
if not name.startswith('_') or name != '__init__':
|
|
28
|
+
if inspect.iscoroutinefunction(getattr(cls, name)):
|
|
29
|
+
setattr(cls, name, method_wrapper_decorator(getattr(cls, name)))
|
|
30
|
+
|
|
31
|
+
return cls
|
|
32
|
+
return method_wrapper_decorator(cls)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TwitterAsync(
|
|
36
|
+
UserMethods, BotMethods, UpdateMethods, AuthMethods
|
|
37
|
+
):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@SyncWrap
|
|
42
|
+
class Twitter(TwitterAsync):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
from http.cookiejar import MozillaCookieJar
|
|
3
|
+
from typing import Union
|
|
4
|
+
from .exceptions import InvalidCredentials, DeniedLogin, ActionRequired, ArkoseLoginRequired
|
|
5
|
+
from .builder import FlowData
|
|
6
|
+
from .types.n_types import Cookies
|
|
7
|
+
from .utils import find_objects, get_url_parts
|
|
8
|
+
from . import constants
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthMethods:
|
|
12
|
+
|
|
13
|
+
async def connect(self):
|
|
14
|
+
"""
|
|
15
|
+
This method will be used to connect to already saved session in disk
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
if not self.session.logged_in:
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
self.request.cookies = self.session.cookies_dict()
|
|
22
|
+
self.user = await self.request.verify_cookies()
|
|
23
|
+
await self.session.save_session(self.cookies, self.user)
|
|
24
|
+
self.is_user_authorized = True
|
|
25
|
+
return self.user
|
|
26
|
+
|
|
27
|
+
async def start(
|
|
28
|
+
self,
|
|
29
|
+
username=None,
|
|
30
|
+
password=None,
|
|
31
|
+
*,
|
|
32
|
+
extra=None
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Interactive Version of `sign_in` which will ask user for inputs
|
|
36
|
+
Most of the time , this would be the only method you will be working with,
|
|
37
|
+
it will check for existing sessions and login to it if available
|
|
38
|
+
|
|
39
|
+
:param username: (`str`) Username of the user
|
|
40
|
+
:param password: (`str`) Password of the user
|
|
41
|
+
:param extra: (`str`) If you have 2-Factor authentication enabled and already have a code ,
|
|
42
|
+
or any other action required for completing the login process
|
|
43
|
+
it will be passed to this parameter
|
|
44
|
+
:return: .types.twDataTypes.User (the user which is authenticated)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
username = input('Please enter the Username: ') if not username else username
|
|
48
|
+
password = getpass.getpass('Please enter your password: ') if not password else password
|
|
49
|
+
|
|
50
|
+
_extra = extra
|
|
51
|
+
_extra_once = False
|
|
52
|
+
while not self.logged_in:
|
|
53
|
+
try:
|
|
54
|
+
return await self.sign_in(username, password, extra=_extra)
|
|
55
|
+
except ActionRequired as e:
|
|
56
|
+
_extra = input(f"\rAction Required :> {str(e.message)} : ")
|
|
57
|
+
_extra_once = True
|
|
58
|
+
except InvalidCredentials as ask_info:
|
|
59
|
+
if _extra_once:
|
|
60
|
+
_extra = input(f"\rAction Required :> {str(ask_info.message)} : ")
|
|
61
|
+
else:
|
|
62
|
+
raise ask_info
|
|
63
|
+
|
|
64
|
+
async def sign_in(
|
|
65
|
+
self,
|
|
66
|
+
username,
|
|
67
|
+
password,
|
|
68
|
+
*,
|
|
69
|
+
extra=None
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
- This method can be used to sign in to Twitter using username and password
|
|
73
|
+
- It will also check for the saved session for the username in the disk
|
|
74
|
+
|
|
75
|
+
:param username: (`str`) Username of the user
|
|
76
|
+
:param password: (`str`) Password of the user
|
|
77
|
+
:param extra: (`str`) If you have 2-Factor authentication enabled and already have a code ,
|
|
78
|
+
or any other action required for completing the login process
|
|
79
|
+
it will be passed to this parameter
|
|
80
|
+
:return: .types.twDataTypes.User (the user which is authenticated)
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
if self.session.logged_in and self.session.user['username'].lower() == username.lower():
|
|
84
|
+
try:
|
|
85
|
+
return await self.connect()
|
|
86
|
+
except InvalidCredentials:
|
|
87
|
+
self.request.cookies = None
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
self._username = username
|
|
91
|
+
self._password = password
|
|
92
|
+
self._extra = extra
|
|
93
|
+
self._captcha_token = None
|
|
94
|
+
|
|
95
|
+
if not self._login_flow:
|
|
96
|
+
self._login_flow = FlowData()
|
|
97
|
+
|
|
98
|
+
if not self._login_flow_state:
|
|
99
|
+
self._login_flow_state = self._login_flow.initial_state
|
|
100
|
+
|
|
101
|
+
return await self._login()
|
|
102
|
+
|
|
103
|
+
async def load_cookies(
|
|
104
|
+
self,
|
|
105
|
+
cookies: Union[str, dict, MozillaCookieJar]
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
This method can be used to load the already authenticated cookies from Twitter
|
|
109
|
+
|
|
110
|
+
:param cookies: (`str`, `dict`, `MozillaCookieJar`) The Cookies to load
|
|
111
|
+
:return: .types.twDataTypes.User (the user which is authenticated)
|
|
112
|
+
"""
|
|
113
|
+
self.cookies = Cookies(cookies)
|
|
114
|
+
await self.session.save_session(self.cookies, None)
|
|
115
|
+
return await self.connect()
|
|
116
|
+
|
|
117
|
+
async def load_auth_token(self, auth_token):
|
|
118
|
+
URL = "https://business.x.com/en"
|
|
119
|
+
temp_cookie = {"auth_token": auth_token}
|
|
120
|
+
temp_headers = {'authorization': constants.DEFAULT_BEARER_TOKEN}
|
|
121
|
+
res = await self.request.session.get(URL, cookies=temp_cookie)
|
|
122
|
+
ct0 = res.cookies.get('ct0')
|
|
123
|
+
|
|
124
|
+
if not ct0:
|
|
125
|
+
res = await self.request.session.get(URL, cookies=temp_cookie, headers=temp_headers)
|
|
126
|
+
ct0 = res.cookies.get('ct0')
|
|
127
|
+
|
|
128
|
+
if not ct0:
|
|
129
|
+
raise DeniedLogin(response=res, message="Auth Token isn't Valid")
|
|
130
|
+
|
|
131
|
+
temp_cookie.update(dict(res.cookies))
|
|
132
|
+
return await self.load_cookies(temp_cookie)
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _get_action_text(response):
|
|
136
|
+
primary_message = find_objects(response, 'primary_text', None, none_value={})
|
|
137
|
+
secondary_message = find_objects(response, 'secondary_text', None, none_value={})
|
|
138
|
+
if primary_message:
|
|
139
|
+
if isinstance(primary_message, list):
|
|
140
|
+
primary_message = primary_message[0]
|
|
141
|
+
|
|
142
|
+
primary_message = primary_message.get('text', '')
|
|
143
|
+
|
|
144
|
+
if secondary_message:
|
|
145
|
+
if isinstance(secondary_message, list):
|
|
146
|
+
secondary_message = secondary_message[0]
|
|
147
|
+
secondary_message = secondary_message.get('text', '')
|
|
148
|
+
return f"{primary_message}. {secondary_message}"
|
|
149
|
+
|
|
150
|
+
async def _login(self):
|
|
151
|
+
|
|
152
|
+
while not self.logged_in:
|
|
153
|
+
_login_payload = self._login_flow.get(
|
|
154
|
+
self._login_flow_state,
|
|
155
|
+
json_=self._last_json,
|
|
156
|
+
username=self._username,
|
|
157
|
+
password=self._password,
|
|
158
|
+
extra=self._extra,
|
|
159
|
+
captcha_token=self._captcha_token
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Twitter now often asks for multiple verifications
|
|
163
|
+
if self._login_flow_state in constants.AUTH_ACTION_REQUIRED_KEYS:
|
|
164
|
+
self._extra = None
|
|
165
|
+
|
|
166
|
+
response = await self.request.login(self._login_url, _payload=_login_payload)
|
|
167
|
+
|
|
168
|
+
self._last_json = response.json()
|
|
169
|
+
|
|
170
|
+
if response.cookies.get("att"):
|
|
171
|
+
self.request.headers = {"att": response.cookies.get("att")}
|
|
172
|
+
|
|
173
|
+
if self._last_json.get('status') != "success":
|
|
174
|
+
raise DeniedLogin(response=response, message=response.text)
|
|
175
|
+
|
|
176
|
+
subtask = self._last_json["subtasks"][0].get("subtask_id")
|
|
177
|
+
self._login_url = self._login_url.split("?")[0]
|
|
178
|
+
self._login_flow_state = subtask
|
|
179
|
+
|
|
180
|
+
if subtask in constants.AUTH_ACTION_REQUIRED_KEYS and not self._extra:
|
|
181
|
+
message = self._get_action_text(self._last_json)
|
|
182
|
+
raise ActionRequired(0, "ActionRequired", response, message)
|
|
183
|
+
|
|
184
|
+
if subtask == "ArkoseLogin":
|
|
185
|
+
# if self._captcha_solver is None:
|
|
186
|
+
raise ArkoseLoginRequired(response=response)
|
|
187
|
+
|
|
188
|
+
# token = await self.request.solve_captcha(websiteUrl="https://iframe.arkoselabs.com")
|
|
189
|
+
# token = self.request.solve_captcha(websiteUrl="https://twitter.com/i/flow/login", blob_data=data[0])
|
|
190
|
+
# self._captcha_token = token
|
|
191
|
+
|
|
192
|
+
if subtask == "DenyLoginSubtask":
|
|
193
|
+
reason = self._get_action_text(self._last_json)
|
|
194
|
+
raise DeniedLogin(response=response, message=reason)
|
|
195
|
+
|
|
196
|
+
if subtask == "LoginSuccessSubtask":
|
|
197
|
+
self.request.remove_header("att")
|
|
198
|
+
self.cookies = Cookies(dict(response.cookies))
|
|
199
|
+
await self.session.save_session(self.cookies, None)
|
|
200
|
+
return await self.connect()
|
|
201
|
+
|
|
202
|
+
raise DeniedLogin(response=response, message="Unknown Error Occurred")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|