tweepy-self 0.1.0__py3-none-any.whl → 1.0.0b2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- tweepy_self-1.0.0b2.dist-info/METADATA +218 -0
- tweepy_self-1.0.0b2.dist-info/RECORD +16 -0
- twitter/__init__.py +24 -0
- twitter/account.py +58 -0
- twitter/base/__init__.py +7 -0
- twitter/base/client.py +20 -0
- twitter/base/session.py +56 -0
- twitter/client.py +1218 -0
- twitter/errors.py +145 -0
- twitter/models.py +64 -0
- twitter/utils/__init__.py +42 -0
- twitter/utils/accounts.py +43 -0
- twitter/utils/file.py +41 -0
- twitter/utils/html.py +29 -0
- twitter/utils/other.py +24 -0
- tweepy-self/__init__.py +0 -0
- tweepy_self-0.1.0.dist-info/METADATA +0 -15
- tweepy_self-0.1.0.dist-info/RECORD +0 -4
- {tweepy_self-0.1.0.dist-info → tweepy_self-1.0.0b2.dist-info}/WHEEL +0 -0
twitter/errors.py
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
from curl_cffi import requests
|
2
|
+
|
3
|
+
from .account import Account
|
4
|
+
|
5
|
+
__all__ = [
|
6
|
+
"TwitterException",
|
7
|
+
"HTTPException",
|
8
|
+
"BadRequest",
|
9
|
+
"Unauthorized",
|
10
|
+
"Forbidden",
|
11
|
+
"NotFound",
|
12
|
+
"RateLimited",
|
13
|
+
"ServerError",
|
14
|
+
"BadAccount",
|
15
|
+
"BadToken",
|
16
|
+
"Locked",
|
17
|
+
"Suspended",
|
18
|
+
]
|
19
|
+
|
20
|
+
|
21
|
+
class TwitterException(Exception):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class BadAccount(TwitterException):
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
account: Account,
|
29
|
+
custom_exception_message: str = None,
|
30
|
+
):
|
31
|
+
self.account = account
|
32
|
+
exception_message = f"Bad Twitter account."
|
33
|
+
super().__init__(custom_exception_message or exception_message)
|
34
|
+
|
35
|
+
|
36
|
+
class BadToken(BadAccount):
|
37
|
+
def __init__(self, account: Account):
|
38
|
+
exception_message = f"Bad Twitter account's auth_token."
|
39
|
+
super().__init__(account, custom_exception_message=exception_message)
|
40
|
+
|
41
|
+
|
42
|
+
class Locked(BadAccount):
|
43
|
+
def __init__(self, account: Account):
|
44
|
+
exception_message = f"Twitter account is locked. Captcha required to unlock."
|
45
|
+
super().__init__(account, custom_exception_message=exception_message)
|
46
|
+
|
47
|
+
|
48
|
+
class Suspended(BadAccount):
|
49
|
+
def __init__(self, account: Account):
|
50
|
+
exception_message = f"Twitter account is suspended."
|
51
|
+
super().__init__(account, custom_exception_message=exception_message)
|
52
|
+
|
53
|
+
|
54
|
+
class HTTPException(TwitterException):
|
55
|
+
"""Exception raised when an HTTP request fails.
|
56
|
+
"""
|
57
|
+
|
58
|
+
def __init__(
|
59
|
+
self,
|
60
|
+
response: requests.Response,
|
61
|
+
data: dict | str,
|
62
|
+
custom_exception_message: str = None,
|
63
|
+
):
|
64
|
+
self.response = response
|
65
|
+
self.api_errors: list[dict[str, int | str]] = []
|
66
|
+
self.api_codes: list[int] = []
|
67
|
+
self.api_messages: list[str] = []
|
68
|
+
|
69
|
+
# Если ответ — строка, то это html
|
70
|
+
if isinstance(data, str):
|
71
|
+
exception_message = f"{response.status_code}"
|
72
|
+
if response.status_code == 429:
|
73
|
+
exception_message = (f"{response.status_code} Rate limit exceeded."
|
74
|
+
f"\nSet twitter.Client(wait_on_rate_limit=True) to ignore this exception.")
|
75
|
+
super().__init__(exception_message)
|
76
|
+
return
|
77
|
+
|
78
|
+
errors = data.get("errors", [])
|
79
|
+
|
80
|
+
if "error" in data:
|
81
|
+
errors.append(data["error"])
|
82
|
+
else:
|
83
|
+
errors.append(data)
|
84
|
+
|
85
|
+
error_text = ""
|
86
|
+
|
87
|
+
for error in errors:
|
88
|
+
self.api_errors.append(error)
|
89
|
+
|
90
|
+
if isinstance(error, str):
|
91
|
+
self.api_messages.append(error)
|
92
|
+
error_text += '\n' + error
|
93
|
+
continue
|
94
|
+
|
95
|
+
if "code" in error:
|
96
|
+
self.api_codes.append(error["code"])
|
97
|
+
if "message" in error:
|
98
|
+
self.api_messages.append(error["message"])
|
99
|
+
|
100
|
+
if "code" in error and "message" in error:
|
101
|
+
error_text += f"\n{error['code']} - {error['message']}"
|
102
|
+
elif "message" in error:
|
103
|
+
error_text += '\n' + error["message"]
|
104
|
+
|
105
|
+
if not error_text and "detail" in data:
|
106
|
+
self.api_messages.append(data["detail"])
|
107
|
+
error_text = '\n' + data["detail"]
|
108
|
+
exception_message = f"{response.status_code} {error_text}"
|
109
|
+
super().__init__(custom_exception_message or exception_message)
|
110
|
+
|
111
|
+
|
112
|
+
class BadRequest(HTTPException):
|
113
|
+
"""Exception raised for a 400 HTTP status code.
|
114
|
+
"""
|
115
|
+
pass
|
116
|
+
|
117
|
+
|
118
|
+
class Unauthorized(HTTPException):
|
119
|
+
"""Exception raised for a 401 HTTP status code.
|
120
|
+
"""
|
121
|
+
pass
|
122
|
+
|
123
|
+
|
124
|
+
class Forbidden(HTTPException):
|
125
|
+
"""Exception raised for a 403 HTTP status code.
|
126
|
+
"""
|
127
|
+
pass
|
128
|
+
|
129
|
+
|
130
|
+
class NotFound(HTTPException):
|
131
|
+
"""Exception raised for a 404 HTTP status code.
|
132
|
+
"""
|
133
|
+
pass
|
134
|
+
|
135
|
+
|
136
|
+
class RateLimited(HTTPException):
|
137
|
+
"""Exception raised for a 429 HTTP status code.
|
138
|
+
"""
|
139
|
+
pass
|
140
|
+
|
141
|
+
|
142
|
+
class ServerError(HTTPException):
|
143
|
+
"""Exception raised for a 5xx HTTP status code.
|
144
|
+
"""
|
145
|
+
pass
|
twitter/models.py
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from .utils import to_datetime
|
6
|
+
|
7
|
+
|
8
|
+
class UserData(BaseModel):
|
9
|
+
id: int
|
10
|
+
username: str
|
11
|
+
name: str
|
12
|
+
created_at: datetime
|
13
|
+
description: str
|
14
|
+
location: str
|
15
|
+
followers_count: int
|
16
|
+
friends_count: int
|
17
|
+
raw_data: dict
|
18
|
+
|
19
|
+
def __str__(self):
|
20
|
+
return f"({self.id}) @{self.username}"
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
def from_raw_user_data(cls, data: dict):
|
24
|
+
legacy = data["legacy"]
|
25
|
+
keys = ("name", "description", "location", "followers_count", "friends_count")
|
26
|
+
values = {key: legacy[key] for key in keys}
|
27
|
+
values.update({
|
28
|
+
"id": int(data["rest_id"]),
|
29
|
+
"username": legacy["screen_name"],
|
30
|
+
"created_at": to_datetime(legacy["created_at"]),
|
31
|
+
"raw_data": data,
|
32
|
+
})
|
33
|
+
return cls(**values)
|
34
|
+
|
35
|
+
|
36
|
+
class Tweet(BaseModel):
|
37
|
+
user_id: int
|
38
|
+
id: int
|
39
|
+
created_at: datetime
|
40
|
+
full_text: str
|
41
|
+
lang: str
|
42
|
+
favorite_count: int
|
43
|
+
quote_count: int
|
44
|
+
reply_count: int
|
45
|
+
retweet_count: int
|
46
|
+
retweeted: bool
|
47
|
+
raw_data: dict
|
48
|
+
|
49
|
+
def __str__(self):
|
50
|
+
short_text = f"{self.full_text[:32]}..." if len(self.full_text) > 16 else self.full_text
|
51
|
+
return f"({self.id}) {short_text}"
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def from_raw_data(cls, data: dict):
|
55
|
+
legacy = data['legacy']
|
56
|
+
keys = ("full_text", "lang", "favorite_count", "quote_count", "reply_count", "retweet_count", "retweeted")
|
57
|
+
values = {key: legacy[key] for key in keys}
|
58
|
+
values.update({
|
59
|
+
"user_id": int(legacy["user_id_str"]),
|
60
|
+
"id": int(legacy["id_str"]),
|
61
|
+
"created_at": to_datetime(legacy["created_at"]),
|
62
|
+
"raw_data": data
|
63
|
+
})
|
64
|
+
return cls(**values)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from .file import (
|
2
|
+
copy_file,
|
3
|
+
load_lines,
|
4
|
+
load_json,
|
5
|
+
load_toml,
|
6
|
+
write_lines,
|
7
|
+
write_json,
|
8
|
+
to_json,
|
9
|
+
)
|
10
|
+
from .accounts import (
|
11
|
+
load_accounts_from_file,
|
12
|
+
extract_accounts_to_file,
|
13
|
+
)
|
14
|
+
from .html import (
|
15
|
+
parse_unlock_html,
|
16
|
+
parse_oauth_html,
|
17
|
+
)
|
18
|
+
from .other import (
|
19
|
+
remove_at_sign,
|
20
|
+
tweet_url,
|
21
|
+
to_datetime,
|
22
|
+
hidden_value,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
__all__ = [
|
27
|
+
"copy_file",
|
28
|
+
"load_lines",
|
29
|
+
"load_json",
|
30
|
+
"load_toml",
|
31
|
+
"write_lines",
|
32
|
+
"write_json",
|
33
|
+
"to_json",
|
34
|
+
"load_accounts_from_file",
|
35
|
+
"extract_accounts_to_file",
|
36
|
+
"parse_unlock_html",
|
37
|
+
"parse_oauth_html",
|
38
|
+
"remove_at_sign",
|
39
|
+
"tweet_url",
|
40
|
+
"to_datetime",
|
41
|
+
"hidden_value",
|
42
|
+
]
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Sequence, Iterable
|
3
|
+
|
4
|
+
from .file import load_lines, write_lines
|
5
|
+
from ..account import Account
|
6
|
+
|
7
|
+
|
8
|
+
def load_accounts_from_file(
|
9
|
+
filepath: Path | str,
|
10
|
+
*,
|
11
|
+
separator: str = ":",
|
12
|
+
fields: Sequence[str] = ("auth_token", "password", "email", "username"),
|
13
|
+
) -> list[Account]:
|
14
|
+
"""
|
15
|
+
:param filepath: Путь до файла с данными об аккаунтах.
|
16
|
+
:param separator: Разделитель между данными в строке.
|
17
|
+
:param fields: Кортеж, содержащий имена полей в порядке их появления в строке.
|
18
|
+
:return: Список Twitter аккаунтов.
|
19
|
+
"""
|
20
|
+
accounts = []
|
21
|
+
for line in load_lines(filepath):
|
22
|
+
data = dict(zip(fields, line.split(separator)))
|
23
|
+
data.update({key: None for key in data if not data[key]})
|
24
|
+
accounts.append(Account(**data))
|
25
|
+
return accounts
|
26
|
+
|
27
|
+
|
28
|
+
def extract_accounts_to_file(
|
29
|
+
filepath: Path | str,
|
30
|
+
accounts: Iterable[Account],
|
31
|
+
*,
|
32
|
+
separator: str = ":",
|
33
|
+
fields: Sequence[str] = ("auth_token", "password", "email", "username"),
|
34
|
+
):
|
35
|
+
lines = []
|
36
|
+
for account in accounts:
|
37
|
+
account_data = []
|
38
|
+
for field_name in fields:
|
39
|
+
field = getattr(account, field_name)
|
40
|
+
field = field if field is not None else ""
|
41
|
+
account_data.append(field)
|
42
|
+
lines.append(separator.join(account_data))
|
43
|
+
write_lines(filepath, lines)
|
twitter/utils/file.py
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
import json
|
2
|
+
import shutil
|
3
|
+
import tomllib
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Iterable
|
6
|
+
|
7
|
+
|
8
|
+
def copy_file(source_path: Path | str, destination_path: Path | str):
|
9
|
+
destination_path = Path(destination_path)
|
10
|
+
if destination_path.exists():
|
11
|
+
return
|
12
|
+
shutil.copy2(str(source_path), str(destination_path))
|
13
|
+
|
14
|
+
|
15
|
+
def load_toml(filepath: Path | str) -> dict:
|
16
|
+
with open(filepath, "rb") as file:
|
17
|
+
return tomllib.load(file)
|
18
|
+
|
19
|
+
|
20
|
+
def load_lines(filepath: Path | str) -> list[str]:
|
21
|
+
with open(filepath, "r") as file:
|
22
|
+
return [line.strip() for line in file.readlines() if line != "\n"]
|
23
|
+
|
24
|
+
|
25
|
+
def write_lines(filepath: Path | str, lines: Iterable[str]):
|
26
|
+
with open(filepath, "w") as file:
|
27
|
+
file.write("\n".join(lines))
|
28
|
+
|
29
|
+
|
30
|
+
def load_json(filepath: Path | str) -> dict:
|
31
|
+
with open(filepath, "r") as file:
|
32
|
+
return json.load(file)
|
33
|
+
|
34
|
+
|
35
|
+
def write_json(filepath: Path | str, data):
|
36
|
+
with open(filepath, "w") as file:
|
37
|
+
json.dump(data, file, indent=4)
|
38
|
+
|
39
|
+
|
40
|
+
def to_json(obj) -> str:
|
41
|
+
return json.dumps(obj, separators=(',', ':'), ensure_ascii=True)
|
twitter/utils/html.py
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
|
3
|
+
|
4
|
+
def parse_oauth_html(html: str) -> tuple[str | None, str | None, str | None]:
|
5
|
+
"""
|
6
|
+
:return: authenticity_token, redirect_url, redirect_after_login_url
|
7
|
+
"""
|
8
|
+
soup = BeautifulSoup(html, "lxml")
|
9
|
+
authenticity_token_element = soup.find("input", {"name": "authenticity_token"})
|
10
|
+
authenticity_token = authenticity_token_element.get("value") if authenticity_token_element else None
|
11
|
+
redirect_url_element = soup.find("a", text="click here to continue")
|
12
|
+
redirect_url = redirect_url_element.get("href") if redirect_url_element else None
|
13
|
+
redirect_after_login_element = soup.find("input", {"name": "redirect_after_login"})
|
14
|
+
redirect_after_login_url = redirect_after_login_element.get("value") if redirect_after_login_element else None
|
15
|
+
return authenticity_token, redirect_url, redirect_after_login_url
|
16
|
+
|
17
|
+
|
18
|
+
def parse_unlock_html(html: str) -> tuple[str | None, str | None, bool]:
|
19
|
+
"""
|
20
|
+
:return: authenticity_token, assignment_token, needs_unlock
|
21
|
+
"""
|
22
|
+
soup = BeautifulSoup(html, "lxml")
|
23
|
+
authenticity_token_element = soup.find("input", {"name": "authenticity_token"})
|
24
|
+
authenticity_token = authenticity_token_element.get("value") if authenticity_token_element else None
|
25
|
+
assignment_token_element = soup.find("input", {"name": "assignment_token"})
|
26
|
+
assignment_token = assignment_token_element.get("value") if assignment_token_element else None
|
27
|
+
verification_string = soup.find('input', id='verification_string')
|
28
|
+
needs_unlock = bool(verification_string)
|
29
|
+
return authenticity_token, assignment_token, needs_unlock
|
twitter/utils/other.py
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
|
4
|
+
def remove_at_sign(username: str) -> str:
|
5
|
+
if username.startswith("@"):
|
6
|
+
return username[1:]
|
7
|
+
return username
|
8
|
+
|
9
|
+
|
10
|
+
def tweet_url(username: str, tweet_id: int) -> str:
|
11
|
+
"""
|
12
|
+
:return: Tweet URL
|
13
|
+
"""
|
14
|
+
return f"https://x.com/{username}/status/{tweet_id}"
|
15
|
+
|
16
|
+
|
17
|
+
def to_datetime(twitter_datetime: str):
|
18
|
+
return datetime.strptime(twitter_datetime, '%a %b %d %H:%M:%S +0000 %Y')
|
19
|
+
|
20
|
+
|
21
|
+
def hidden_value(value: str) -> str:
|
22
|
+
start = value[:3]
|
23
|
+
end = value[-3:]
|
24
|
+
return f"{start}**{end}"
|
tweepy-self/__init__.py
DELETED
File without changes
|
@@ -1,15 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: tweepy-self
|
3
|
-
Version: 0.1.0
|
4
|
-
Summary: Twitter (selfbot) for Python!
|
5
|
-
Author: Alen
|
6
|
-
Author-email: alen.kimov@gmail.com
|
7
|
-
Requires-Python: >=3.11,<4.0
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
9
|
-
Classifier: Programming Language :: Python :: 3.11
|
10
|
-
Classifier: Programming Language :: Python :: 3.12
|
11
|
-
Description-Content-Type: text/markdown
|
12
|
-
|
13
|
-
# Tweepy-self
|
14
|
-
|
15
|
-
https://github.com/alenkimov/better_automation
|
@@ -1,4 +0,0 @@
|
|
1
|
-
tweepy-self/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
tweepy_self-0.1.0.dist-info/METADATA,sha256=lHOX3W_jeCYq7b1s3xGCi4d__V2v3yc9N2IUtvNzJLk,424
|
3
|
-
tweepy_self-0.1.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
4
|
-
tweepy_self-0.1.0.dist-info/RECORD,,
|
File without changes
|