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 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
- tweet_id = response.json()["data"]["id"]
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
- " It must be a valid integer as a string. Optional.",
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
- user_data = response.json()["data"]
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
- tweet_data["author_username"] = user_data["username"]
45
- tweet_data["author_name"] = user_data["name"]
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arcade_x
3
- Version: 1.2.0
3
+ Version: 1.3.1
4
4
  Summary: Arcade.dev LLM tools for X (Twitter)
5
5
  Author-email: Arcade <dev@arcade.dev>
6
6
  License: Proprietary - Arcade Software License Agreement v1.0
@@ -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,,
@@ -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,,