twitter-openapi-python 0.0.23__tar.gz → 0.0.24__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.
Files changed (34) hide show
  1. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/PKG-INFO +1 -1
  2. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/README.md +27 -2
  3. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/pyproject.toml +2 -2
  4. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/setup.py +2 -2
  5. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/post_api.py +25 -6
  6. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/client.py +56 -46
  7. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python.egg-info/PKG-INFO +1 -1
  8. twitter_openapi_python-0.0.24/twitter_openapi_python.egg-info/requires.txt +2 -0
  9. twitter_openapi_python-0.0.23/twitter_openapi_python.egg-info/requires.txt +0 -2
  10. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/LICENSE +0 -0
  11. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/LICENSE.AGPL +0 -0
  12. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/setup.cfg +0 -0
  13. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/__init__.py +0 -0
  14. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/__init__.py +0 -0
  15. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/default_api.py +0 -0
  16. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/initial_state_api.py +0 -0
  17. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/tweet_api.py +0 -0
  18. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/user_api.py +0 -0
  19. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/user_list_api.py +0 -0
  20. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/users_api.py +0 -0
  21. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/v11_get_api.py +0 -0
  22. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/api/v11_post_api.py +0 -0
  23. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/models/__init__.py +0 -0
  24. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/models/base.py +0 -0
  25. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/models/header.py +0 -0
  26. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/models/initial_state.py +0 -0
  27. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/models/response.py +0 -0
  28. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/models/timeline.py +0 -0
  29. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/py.typed +0 -0
  30. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/utils/__init__.py +0 -0
  31. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python/utils/api.py +0 -0
  32. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python.egg-info/SOURCES.txt +0 -0
  33. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python.egg-info/dependency_links.txt +0 -0
  34. {twitter_openapi_python-0.0.23 → twitter_openapi_python-0.0.24}/twitter_openapi_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: twitter_openapi_python
3
- Version: 0.0.23
3
+ Version: 0.0.24
4
4
  Summary: Twitter OpenAPI
5
5
  Home-page: https://github.com/fa0311/twitter_openapi_python
6
6
  Author: fa0311
@@ -1,14 +1,17 @@
1
-
2
1
  # twitter_openapi_python
3
2
 
4
3
  Twitter scraping with data validation by pydantic.
5
4
 
6
5
  ![1691572735537](docs/image/README/1691572735537.png)
7
6
 
7
+ ## Setup
8
+
8
9
  ```shell
9
10
  pip install twitter-openapi-python
10
11
  ```
11
12
 
13
+ ## Usage
14
+
12
15
  ```python
13
16
  import json
14
17
  import datetime
@@ -41,6 +44,8 @@ time = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
41
44
  response = 𝕏.get_user_api().get_user_by_screen_name("elonmusk")
42
45
  ```
43
46
 
47
+ ### Login
48
+
44
49
  ```python
45
50
  from twitter_openapi_python import TwitterOpenapiPython
46
51
 
@@ -50,4 +55,24 @@ tweet = 𝕏.get_default_api().get_tweet_result_by_rest_id("1349129669258448897"
50
55
  assert tweet.data is not None
51
56
  assert tweet.data.tweet.legacy is not None
52
57
  print(tweet.data.tweet.legacy.full_text)
53
- ``````
58
+ ```
59
+
60
+ ### Multiple OS
61
+
62
+ The Token can only be used on the same OS that issued the Token
63
+ In other words, if the sec-ch-ua-platform does not match, the Token cannot be used.
64
+ This library uses the Linux Chrome header by default.
65
+ To use Token issued by Windows, do the following.
66
+
67
+ ```typescript
68
+ TwitterOpenapiPython.additional_api_headers = {
69
+ "sec-ch-ua-platform": '"Windows"',
70
+ };
71
+ ```
72
+
73
+ ## License
74
+
75
+ This project is dual licensed. You can choose one of the following licenses:
76
+
77
+ - [Custom License](./LICENSE)
78
+ - [GNU Affero General Public License v3.0](./LICENSE.AGPL)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "twitter_openapi_python"
3
- version = "0.0.23"
3
+ version = "0.0.24"
4
4
  description = "Twitter OpenAPI"
5
5
  authors = ["fa0311 <yuki@yuki0311.com>"]
6
6
  license = "proprietary" # or "AGPL-3.0-only"
@@ -14,7 +14,7 @@ include = ["twitter_openapi_python/py.typed"]
14
14
  python = "^3.7"
15
15
  pydantic = ">=2.6"
16
16
 
17
- twitter-openapi-python-generated = "0.0.20"
17
+ twitter-openapi-python-generated = "0.0.21"
18
18
 
19
19
  [tool.poetry.dev-dependencies]
20
20
 
@@ -3,10 +3,10 @@
3
3
  from setuptools import find_packages, setup
4
4
 
5
5
  NAME = "twitter_openapi_python"
6
- VERSION = "0.0.23"
6
+ VERSION = "0.0.24"
7
7
  PYTHON_REQUIRES = ">=3.7"
8
8
  REQUIRES = [
9
- "twitter_openapi_python_generated == 0.0.20",
9
+ "twitter_openapi_python_generated == 0.0.21",
10
10
  "pydantic >= 2.6",
11
11
  ]
12
12
 
@@ -31,6 +31,10 @@ class PostApiUtils:
31
31
  self,
32
32
  tweet_text: str,
33
33
  media_ids: Optional[list[str]] = None,
34
+ tagged_users: Optional[list[list[str]]] = None,
35
+ in_reply_to_tweet_id: Optional[str] = None,
36
+ attachment_url: Optional[str] = None,
37
+ conversation_control: Optional[str] = None,
34
38
  ) -> ResponseType[models.CreateTweetResponse]:
35
39
  variables = non_nullable(
36
40
  twitter.PostCreateTweetRequestVariables.from_dict(
@@ -43,13 +47,28 @@ class PostApiUtils:
43
47
  )
44
48
  )
45
49
  variables.tweet_text = tweet_text
46
- variables.media.media_entities = [
47
- twitter.PostCreateTweetRequestVariablesMediaMediaEntitiesInner(
48
- media_id=media_id,
49
- tagged_users=[],
50
+ if media_ids:
51
+ tagged_or = tagged_users or []
52
+ variables.media.media_entities = [
53
+ twitter.PostCreateTweetRequestVariablesMediaMediaEntitiesInner(
54
+ media_id=media_id,
55
+ tagged_users=tagged_or[idx] if len(tagged_or) > idx else [],
56
+ )
57
+ for idx, media_id in enumerate(media_ids)
58
+ ]
59
+ variables.attachment_url = attachment_url
60
+ if conversation_control:
61
+ variables.conversation_control = (
62
+ twitter.PostCreateTweetRequestVariablesConversationControl(
63
+ mode=conversation_control
64
+ )
65
+ )
66
+
67
+ if in_reply_to_tweet_id:
68
+ variables.reply = twitter.PostCreateTweetRequestVariablesReply(
69
+ exclude_reply_user_ids=[],
70
+ in_reply_to_tweet_id=in_reply_to_tweet_id,
50
71
  )
51
- for media_id in media_ids or []
52
- ]
53
72
 
54
73
  res = self.api.post_create_tweet_with_http_info(
55
74
  path_query_id=self.flag["CreateTweet"]["queryId"],
@@ -58,43 +58,54 @@ class TwitterOpenapiPythonClient:
58
58
 
59
59
 
60
60
  class TwitterOpenapiPython:
61
- hash: str = "a18a24e4fd96967314c452a6ec2fe6e54f112351"
61
+ hash: str = "0c64eebbfb93b3ed69c3aba923ec8d6894a39902"
62
62
  placeholder_url = "https://raw.githubusercontent.com/fa0311/twitter-openapi/{hash}/src/config/placeholder.json"
63
- placeholder: ParamType
64
- user_agent: str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
63
+ header = "https://raw.githubusercontent.com/fa0311/latest-user-agent/refs/heads/main/header.json"
65
64
  access_token: str = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
65
+ twitter_url: str = "https://x.com/home"
66
+ additional_browser_headers = {}
67
+ additional_api_headers = {}
68
+
69
+ def get_header(self) -> tuple[dict[str, str], dict[str, str]]:
70
+ http = urllib3.PoolManager()
71
+ res = http.request("GET", self.header)
72
+ data = json.loads(res.data)
73
+ ignore = ["host", "connection"]
66
74
 
67
- api_key = {
68
- "Accept": "*/*",
69
- # "AcceptEncoding": "gzip, deflate, br",
70
- # "AcceptEncoding": "deflate, br",
71
- "AcceptLanguage": "en-US,en;q=0.9",
72
- "CacheControl": "no-cache",
73
- "Pragma": "no-cache",
74
- "SecChUa": '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"',
75
- "SecChUaMobile": "?0",
76
- "SecChUaPlatform": '"Windows"',
77
- "SecFetchDest": "empty",
78
- "SecFetchMode": "cors",
79
- "SecFetchSite": "same-origin",
80
- "ClientLanguage": "en",
81
- "ActiveUser": "yes",
82
- }
83
-
84
- browser_headers = {
85
- "accept": "text/plain, */*; q=0.01",
86
- "accept-encoding": "gzip, deflate, br",
87
- "accept-language": "en-US,en;q=0.9",
88
- "cache-control": "no-cache",
89
- "pragma": "no-cache",
90
- "sec-ch-ua": '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"',
91
- "sec-ch-ua-mobile": "?0",
92
- "sec-ch-ua-platform": '"Windows"',
93
- "sec-fetch-dest": "empty",
94
- "sec-fetch-mode": "cors",
95
- "sec-fetch-site": "same-site",
96
- "user-agent": user_agent,
97
- }
75
+ def getHader(name: str) -> dict[str, str]:
76
+ return {
77
+ key: value for key, value in data[name].items() if key not in ignore
78
+ }
79
+
80
+ return (
81
+ {
82
+ **getHader("chrome-fetch"),
83
+ "accept-encoding": "identity",
84
+ "pragma": "no-cache",
85
+ "referer": self.twitter_url,
86
+ "priority": "u=1, i",
87
+ "x-twitter-client-language": "en",
88
+ "x-twitter-active-user": "yes",
89
+ # 'x-twitter-auth-type': 'xxxx'
90
+ # 'x-csrf-token': 'xxxx',
91
+ # 'x-guest-token': 'xxxx',
92
+ "authorization": f"Bearer {self.access_token}",
93
+ **self.additional_api_headers,
94
+ },
95
+ {
96
+ **getHader("chrome"),
97
+ **self.additional_browser_headers,
98
+ },
99
+ )
100
+
101
+ def kebab_to_upper_camel(self, text: dict[str, Any]) -> dict[str, Any]:
102
+ res = {}
103
+ for key, value in text.items():
104
+ new_key = "".join(
105
+ [x.capitalize() for x in key.replace("x-twitter-", "").split("-")]
106
+ )
107
+ res[new_key] = value
108
+ return res
98
109
 
99
110
  def cookie_normalize(self, cookie: list[str]) -> dict[str, str]:
100
111
  return {
@@ -117,7 +128,7 @@ class TwitterOpenapiPython:
117
128
  self,
118
129
  cookies: dict[str, str],
119
130
  ) -> TwitterOpenapiPythonClient:
120
- api_key = self.api_key.copy()
131
+ api_key = self.kebab_to_upper_camel(self.get_header()[0])
121
132
 
122
133
  if cookies.get("ct0"):
123
134
  api_key.update({"AuthType": "OAuth2Session"})
@@ -132,27 +143,28 @@ class TwitterOpenapiPython:
132
143
  configuration=api_conf,
133
144
  cookie=self.cookie_to_str(cookies),
134
145
  )
135
- api_client.user_agent = self.user_agent
146
+ api_client.user_agent = api_key["UserAgent"]
136
147
  return self.get_twitter_openapi_python_client(api_client)
137
148
 
138
149
  def get_guest_client(self) -> TwitterOpenapiPythonClient:
139
150
  http = urllib3.PoolManager()
140
151
  session = {}
141
152
 
153
+ header = self.get_header()
154
+
142
155
  res_1 = http.request(
143
156
  "GET",
144
- "https://x.com",
157
+ self.twitter_url,
145
158
  redirect=False,
146
- headers=self.browser_headers.copy(),
159
+ headers=header[1],
147
160
  )
148
161
  cookie = res_1.headers._container["set-cookie"][1:]
149
162
  session.update(self.cookie_normalize(cookie))
150
163
  res_2 = http.request(
151
164
  "GET",
152
- "https://x.com",
165
+ self.twitter_url,
153
166
  redirect=False,
154
- headers=self.browser_headers.copy()
155
- | {"Cookie": self.cookie_to_str(session)},
167
+ headers=header[1],
156
168
  )
157
169
 
158
170
  find = re.findall(r'document.cookie="(.*?)";', res_2.data.decode())
@@ -161,15 +173,13 @@ class TwitterOpenapiPython:
161
173
  session.pop("ct0")
162
174
 
163
175
  if not session.get("gt"):
164
- activate_header = self.browser_headers.copy() | {
165
- "authorization": "Bearer {}".format(self.access_token),
166
- "x-twitter-active-user": "yes",
167
- "x-twitter-client-language": "en",
176
+ activate_header = header[0] | {
177
+ "cookie": self.cookie_to_str(session),
168
178
  }
169
179
  res_3 = http.request(
170
180
  "POST",
171
181
  "https://api.x.com/1.1/guest/activate.json",
172
- headers=activate_header | {"Cookie": self.cookie_to_str(session)},
182
+ headers=activate_header,
173
183
  )
174
184
  gt = json.loads(res_3.data.decode())["guest_token"]
175
185
  session.update({"gt": gt})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: twitter-openapi-python
3
- Version: 0.0.23
3
+ Version: 0.0.24
4
4
  Summary: Twitter OpenAPI
5
5
  Home-page: https://github.com/fa0311/twitter_openapi_python
6
6
  Author: fa0311
@@ -0,0 +1,2 @@
1
+ twitter_openapi_python_generated==0.0.21
2
+ pydantic>=2.6
@@ -1,2 +0,0 @@
1
- twitter_openapi_python_generated==0.0.20
2
- pydantic>=2.6