tweepy-self 0.1.0__py3-none-any.whl → 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.
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,36 @@
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 .html import (
11
+ parse_unlock_html,
12
+ parse_oauth_html,
13
+ )
14
+ from .other import (
15
+ remove_at_sign,
16
+ tweet_url,
17
+ to_datetime,
18
+ hidden_value,
19
+ )
20
+
21
+
22
+ __all__ = [
23
+ "copy_file",
24
+ "load_lines",
25
+ "load_json",
26
+ "load_toml",
27
+ "write_lines",
28
+ "write_json",
29
+ "to_json",
30
+ "parse_unlock_html",
31
+ "parse_oauth_html",
32
+ "remove_at_sign",
33
+ "tweet_url",
34
+ "to_datetime",
35
+ "hidden_value",
36
+ ]
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,,