arcade-x 1.2.0__py3-none-any.whl → 1.3.1__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.
- arcade_x/tools/tweets.py +23 -6
- arcade_x/tools/users.py +13 -10
- arcade_x/tools/utils.py +14 -2
- {arcade_x-1.2.0.dist-info → arcade_x-1.3.1.dist-info}/METADATA +1 -1
- arcade_x-1.3.1.dist-info/RECORD +11 -0
- arcade_x-1.2.0.dist-info/RECORD +0 -11
- {arcade_x-1.2.0.dist-info → arcade_x-1.3.1.dist-info}/WHEEL +0 -0
- {arcade_x-1.2.0.dist-info → arcade_x-1.3.1.dist-info}/licenses/LICENSE +0 -0
arcade_x/tools/tweets.py
CHANGED
|
@@ -26,7 +26,24 @@ async def _post_tweet(context: ToolContext, payload: dict) -> str:
|
|
|
26
26
|
async with httpx.AsyncClient() as client:
|
|
27
27
|
response = await client.post(TWEETS_URL, headers=headers, json=payload, timeout=10)
|
|
28
28
|
response.raise_for_status()
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
response_json = response.json()
|
|
31
|
+
|
|
32
|
+
# Check if data exists in response
|
|
33
|
+
if "data" not in response_json or response_json.get("data") is None:
|
|
34
|
+
return (
|
|
35
|
+
"The post was successfully created, but the X API returned an unexpected response. "
|
|
36
|
+
f"The X API returned:\n {response_json}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
tweet_data = response_json["data"]
|
|
40
|
+
if "id" not in tweet_data:
|
|
41
|
+
return (
|
|
42
|
+
"The post was successfully created, but the X API returned an unexpected response. "
|
|
43
|
+
f"The X API returned:\n {response_json}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
tweet_id = tweet_data["id"]
|
|
30
47
|
return f"Tweet with id {tweet_id} posted successfully. URL: {get_tweet_url(tweet_id)}"
|
|
31
48
|
|
|
32
49
|
|
|
@@ -40,8 +57,8 @@ async def post_tweet(
|
|
|
40
57
|
tweet_text: Annotated[str, "The text content of the tweet you want to post"],
|
|
41
58
|
quote_tweet_id: Annotated[
|
|
42
59
|
str | None,
|
|
43
|
-
"The ID of the tweet you want to quote."
|
|
44
|
-
"
|
|
60
|
+
"The ID of the tweet you want to quote. "
|
|
61
|
+
"It must be a valid integer as a string. Default is None.",
|
|
45
62
|
] = None,
|
|
46
63
|
) -> Annotated[str, "Success string and the URL of the tweet"]:
|
|
47
64
|
"""Post a tweet to X (Twitter).
|
|
@@ -134,7 +151,7 @@ async def search_recent_tweets_by_username(
|
|
|
134
151
|
"next_token": next_token,
|
|
135
152
|
"expansions": "author_id",
|
|
136
153
|
"user.fields": "id,name,username,entities",
|
|
137
|
-
"tweet.fields": "entities,note_tweet",
|
|
154
|
+
"tweet.fields": "entities,note_tweet,public_metrics",
|
|
138
155
|
}
|
|
139
156
|
params = expand_attached_media(remove_none_values(params))
|
|
140
157
|
|
|
@@ -204,7 +221,7 @@ async def search_recent_tweets_by_keywords(
|
|
|
204
221
|
"next_token": next_token,
|
|
205
222
|
"expansions": "author_id",
|
|
206
223
|
"user.fields": "id,name,username,entities",
|
|
207
|
-
"tweet.fields": "entities,note_tweet",
|
|
224
|
+
"tweet.fields": "entities,note_tweet,public_metrics",
|
|
208
225
|
}
|
|
209
226
|
params = expand_attached_media(remove_none_values(params))
|
|
210
227
|
|
|
@@ -242,7 +259,7 @@ async def lookup_tweet_by_id(
|
|
|
242
259
|
params = {
|
|
243
260
|
"expansions": "author_id",
|
|
244
261
|
"user.fields": "id,name,username,entities",
|
|
245
|
-
"tweet.fields": "entities,note_tweet",
|
|
262
|
+
"tweet.fields": "entities,note_tweet,public_metrics",
|
|
246
263
|
}
|
|
247
264
|
params = expand_attached_media(params)
|
|
248
265
|
|
arcade_x/tools/users.py
CHANGED
|
@@ -3,7 +3,6 @@ from typing import Annotated
|
|
|
3
3
|
import httpx
|
|
4
4
|
from arcade_tdk import ToolContext, tool
|
|
5
5
|
from arcade_tdk.auth import X
|
|
6
|
-
from arcade_tdk.errors import RetryableToolError
|
|
7
6
|
|
|
8
7
|
from arcade_x.tools.utils import (
|
|
9
8
|
expand_urls_in_user_description,
|
|
@@ -46,18 +45,22 @@ async def lookup_single_user_by_username(
|
|
|
46
45
|
|
|
47
46
|
async with httpx.AsyncClient() as client:
|
|
48
47
|
response = await client.get(url, headers=headers, timeout=10)
|
|
49
|
-
if response.status_code == 404:
|
|
50
|
-
# User not found
|
|
51
|
-
raise RetryableToolError(
|
|
52
|
-
"User not found",
|
|
53
|
-
developer_message=f"User with username '{username}' not found.",
|
|
54
|
-
additional_prompt_content="Please check the username and try again.",
|
|
55
|
-
retry_after_ms=500, # Play nice with X API rate limits
|
|
56
|
-
)
|
|
57
48
|
response.raise_for_status()
|
|
49
|
+
|
|
58
50
|
# Parse the response JSON
|
|
59
|
-
|
|
51
|
+
response_json = response.json()
|
|
52
|
+
|
|
53
|
+
# Check if data exists in response
|
|
54
|
+
if response_json.get("data") is None:
|
|
55
|
+
return {
|
|
56
|
+
"data": None,
|
|
57
|
+
"message": (
|
|
58
|
+
f"No user found with username '{username}'. "
|
|
59
|
+
"The account may not exist or may have been suspended."
|
|
60
|
+
),
|
|
61
|
+
}
|
|
60
62
|
|
|
63
|
+
user_data = response_json["data"]
|
|
61
64
|
user_data = expand_urls_in_user_description(user_data, delete_entities=False)
|
|
62
65
|
user_data = expand_urls_in_user_url(user_data, delete_entities=True)
|
|
63
66
|
|
arcade_x/tools/utils.py
CHANGED
|
@@ -35,14 +35,20 @@ def parse_search_recent_tweets_response(response_data: dict[str, Any]) -> dict[s
|
|
|
35
35
|
|
|
36
36
|
# Add 'tweet_url' to each tweet
|
|
37
37
|
for tweet in response_data["data"]:
|
|
38
|
+
# Skip tweets without an id
|
|
39
|
+
if "id" not in tweet:
|
|
40
|
+
continue
|
|
38
41
|
tweet["tweet_url"] = get_tweet_url(tweet["id"])
|
|
39
42
|
|
|
40
43
|
# Add 'author_username' and 'author_name' to each tweet
|
|
41
44
|
for tweet_data, user_data in zip(
|
|
42
45
|
response_data["data"], response_data["includes"]["users"], strict=False
|
|
43
46
|
):
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
# Skip if user data is missing required fields
|
|
48
|
+
if "username" in user_data:
|
|
49
|
+
tweet_data["author_username"] = user_data["username"]
|
|
50
|
+
if "name" in user_data:
|
|
51
|
+
tweet_data["author_name"] = user_data["name"]
|
|
46
52
|
|
|
47
53
|
return response_data
|
|
48
54
|
|
|
@@ -101,6 +107,9 @@ def expand_urls_in_user_description(user_data: dict, delete_entities: bool = Tru
|
|
|
101
107
|
description_urls = new_user_data.get("entities", {}).get("description", {}).get("urls", [])
|
|
102
108
|
description = new_user_data.get("description", "")
|
|
103
109
|
for url_info in description_urls:
|
|
110
|
+
# Skip URL entities that don't have both url and expanded_url
|
|
111
|
+
if "url" not in url_info or "expanded_url" not in url_info:
|
|
112
|
+
continue
|
|
104
113
|
t_co_link = url_info["url"]
|
|
105
114
|
expanded_url = url_info["expanded_url"]
|
|
106
115
|
description = description.replace(t_co_link, expanded_url)
|
|
@@ -119,6 +128,9 @@ def expand_urls_in_user_url(user_data: dict, delete_entities: bool = True) -> di
|
|
|
119
128
|
url_urls = new_user_data.get("entities", {}).get("url", {}).get("urls", [])
|
|
120
129
|
url = new_user_data.get("url", "")
|
|
121
130
|
for url_info in url_urls:
|
|
131
|
+
# Skip URL entities that don't have both url and expanded_url
|
|
132
|
+
if "url" not in url_info or "expanded_url" not in url_info:
|
|
133
|
+
continue
|
|
122
134
|
t_co_link = url_info["url"]
|
|
123
135
|
expanded_url = url_info["expanded_url"]
|
|
124
136
|
url = url.replace(t_co_link, expanded_url)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
arcade_x/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
arcade_x/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
arcade_x/tools/constants.py,sha256=d-OJK5Qx05JRUcpK5G1DcYB91wT37hFSzGmIYfwlEtA,42
|
|
4
|
+
arcade_x/tools/tweets.py,sha256=pN3V_B3P6genFI1Wh6FXfGw1ggp3GOLk4W2pgZaAtZ0,10031
|
|
5
|
+
arcade_x/tools/user_context.py,sha256=nKlsM2LGdoIPl8FQ7FKqd6hkbPHVSJUiZdShZ7ZCsIk,2470
|
|
6
|
+
arcade_x/tools/users.py,sha256=YObl3_3aBZpW-GqdSrR2Gmw3BHzdq075GWSxDd1Ery0,2033
|
|
7
|
+
arcade_x/tools/utils.py,sha256=2s3hxpXplxIQs4rO1IJdO-1KbUj-Tm3f9P65n4drw1s,6259
|
|
8
|
+
arcade_x-1.3.1.dist-info/METADATA,sha256=39055VcWrVEvQ9w0-5d4o8XgYdJBJhd_Xox4jMELGg0,897
|
|
9
|
+
arcade_x-1.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
10
|
+
arcade_x-1.3.1.dist-info/licenses/LICENSE,sha256=ixeE7aL9b2B-_ZYHTY1vQcJB4NufKeo-LWwKNObGDN0,1960
|
|
11
|
+
arcade_x-1.3.1.dist-info/RECORD,,
|
arcade_x-1.2.0.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
arcade_x/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
arcade_x/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
arcade_x/tools/constants.py,sha256=d-OJK5Qx05JRUcpK5G1DcYB91wT37hFSzGmIYfwlEtA,42
|
|
4
|
-
arcade_x/tools/tweets.py,sha256=xfg31_Jh7sUuBfFjRIY_TwbUFCcYbm_xyUE_s35HB-8,9416
|
|
5
|
-
arcade_x/tools/user_context.py,sha256=nKlsM2LGdoIPl8FQ7FKqd6hkbPHVSJUiZdShZ7ZCsIk,2470
|
|
6
|
-
arcade_x/tools/users.py,sha256=gopYKTBOwbZAeVdL_T3vwkepbpv1bdPbqcU_2oPC3SY,2132
|
|
7
|
-
arcade_x/tools/utils.py,sha256=lR_shws08LWQD-4XU9r3xYmasZ2j2i-cUaCCDnuN8UE,5723
|
|
8
|
-
arcade_x-1.2.0.dist-info/METADATA,sha256=JJjv675epNksYQsVM50LV1jT6RuO3PwdafIcs2hpuOM,897
|
|
9
|
-
arcade_x-1.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
10
|
-
arcade_x-1.2.0.dist-info/licenses/LICENSE,sha256=ixeE7aL9b2B-_ZYHTY1vQcJB4NufKeo-LWwKNObGDN0,1960
|
|
11
|
-
arcade_x-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|