arcade-x 0.1.2__py3-none-any.whl → 0.1.5__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 +86 -56
- arcade_x/tools/users.py +42 -42
- arcade_x/tools/utils.py +54 -49
- arcade_x-0.1.5.dist-info/LICENSE +21 -0
- {arcade_x-0.1.2.dist-info → arcade_x-0.1.5.dist-info}/METADATA +2 -2
- arcade_x-0.1.5.dist-info/RECORD +9 -0
- arcade_x-0.1.2.dist-info/RECORD +0 -8
- {arcade_x-0.1.2.dist-info → arcade_x-0.1.5.dist-info}/WHEEL +0 -0
arcade_x/tools/tweets.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
from typing import Annotated
|
|
1
|
+
from typing import Annotated, Any
|
|
2
2
|
|
|
3
3
|
import httpx
|
|
4
|
-
|
|
5
4
|
from arcade.sdk import ToolContext, tool
|
|
6
5
|
from arcade.sdk.auth import X
|
|
7
|
-
from arcade.sdk.errors import
|
|
6
|
+
from arcade.sdk.errors import RetryableToolError
|
|
7
|
+
|
|
8
8
|
from arcade_x.tools.utils import (
|
|
9
9
|
expand_urls_in_tweets,
|
|
10
|
+
get_headers_with_token,
|
|
10
11
|
get_tweet_url,
|
|
11
12
|
parse_search_recent_tweets_response,
|
|
12
13
|
)
|
|
@@ -14,7 +15,10 @@ from arcade_x.tools.utils import (
|
|
|
14
15
|
TWEETS_URL = "https://api.x.com/2/tweets"
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
# Manage Tweets Tools. See developer docs for additional available parameters:
|
|
18
|
+
# Manage Tweets Tools. See developer docs for additional available parameters:
|
|
19
|
+
# https://developer.x.com/en/docs/x-api/tweets/manage-tweets/api-reference
|
|
20
|
+
|
|
21
|
+
|
|
18
22
|
@tool(
|
|
19
23
|
requires_auth=X(
|
|
20
24
|
scopes=["tweet.read", "tweet.write", "users.read"],
|
|
@@ -26,19 +30,12 @@ async def post_tweet(
|
|
|
26
30
|
) -> Annotated[str, "Success string and the URL of the tweet"]:
|
|
27
31
|
"""Post a tweet to X (Twitter)."""
|
|
28
32
|
|
|
29
|
-
headers =
|
|
30
|
-
"Authorization": f"Bearer {context.authorization.token}",
|
|
31
|
-
"Content-Type": "application/json",
|
|
32
|
-
}
|
|
33
|
+
headers = get_headers_with_token(context)
|
|
33
34
|
payload = {"text": tweet_text}
|
|
34
35
|
|
|
35
36
|
async with httpx.AsyncClient() as client:
|
|
36
37
|
response = await client.post(TWEETS_URL, headers=headers, json=payload, timeout=10)
|
|
37
|
-
|
|
38
|
-
if response.status_code != 201:
|
|
39
|
-
raise ToolExecutionError(
|
|
40
|
-
f"Failed to post a tweet during execution of '{post_tweet.__name__}' tool. Request returned an error: {response.status_code} {response.text}"
|
|
41
|
-
)
|
|
38
|
+
response.raise_for_status()
|
|
42
39
|
|
|
43
40
|
tweet_id = response.json()["data"]["id"]
|
|
44
41
|
return f"Tweet with id {tweet_id} posted successfully. URL: {get_tweet_url(tweet_id)}"
|
|
@@ -51,16 +48,12 @@ async def delete_tweet_by_id(
|
|
|
51
48
|
) -> Annotated[str, "Success string confirming the tweet deletion"]:
|
|
52
49
|
"""Delete a tweet on X (Twitter)."""
|
|
53
50
|
|
|
54
|
-
headers =
|
|
51
|
+
headers = get_headers_with_token(context)
|
|
55
52
|
url = f"{TWEETS_URL}/{tweet_id}"
|
|
56
53
|
|
|
57
54
|
async with httpx.AsyncClient() as client:
|
|
58
55
|
response = await client.delete(url, headers=headers, timeout=10)
|
|
59
|
-
|
|
60
|
-
if response.status_code != 200:
|
|
61
|
-
raise ToolExecutionError(
|
|
62
|
-
f"Failed to delete the tweet during execution of '{delete_tweet_by_id.__name__}' tool. Request returned an error: {response.status_code} {response.text}"
|
|
63
|
-
)
|
|
56
|
+
response.raise_for_status()
|
|
64
57
|
|
|
65
58
|
return f"Tweet with id {tweet_id} deleted successfully."
|
|
66
59
|
|
|
@@ -72,33 +65,33 @@ async def search_recent_tweets_by_username(
|
|
|
72
65
|
max_results: Annotated[
|
|
73
66
|
int, "The maximum number of results to return. Cannot be less than 10"
|
|
74
67
|
] = 10,
|
|
75
|
-
) -> Annotated[dict, "Dictionary containing the search results"]:
|
|
76
|
-
"""Search for recent tweets (last 7 days) on X (Twitter) by username.
|
|
68
|
+
) -> Annotated[dict[str, Any], "Dictionary containing the search results"]:
|
|
69
|
+
"""Search for recent tweets (last 7 days) on X (Twitter) by username.
|
|
70
|
+
Includes replies and reposts."""
|
|
77
71
|
|
|
78
|
-
headers =
|
|
79
|
-
|
|
80
|
-
"Content-Type": "application/json",
|
|
81
|
-
}
|
|
82
|
-
params = {
|
|
72
|
+
headers = get_headers_with_token(context)
|
|
73
|
+
params: dict[str, int | str] = {
|
|
83
74
|
"query": f"from:{username}",
|
|
84
75
|
"max_results": max(max_results, 10), # X API does not allow 'max_results' less than 10
|
|
85
76
|
}
|
|
86
|
-
url =
|
|
77
|
+
url = (
|
|
78
|
+
"https://api.x.com/2/tweets/search/recent?"
|
|
79
|
+
"expansions=author_id&user.fields=id,name,username,entities&tweet.fields=entities"
|
|
80
|
+
)
|
|
87
81
|
|
|
88
82
|
async with httpx.AsyncClient() as client:
|
|
89
83
|
response = await client.get(url, headers=headers, params=params, timeout=10)
|
|
84
|
+
response.raise_for_status()
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
raise ToolExecutionError(
|
|
93
|
-
f"Failed to search recent tweets during execution of '{search_recent_tweets_by_username.__name__}' tool. Request returned an error: {response.status_code} {response.text}"
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
response_data = response.json()
|
|
86
|
+
response_data: dict[str, Any] = response.json()
|
|
97
87
|
|
|
98
|
-
# Expand the
|
|
99
|
-
|
|
88
|
+
# Expand the URLs that are in the tweets
|
|
89
|
+
response_data["data"] = expand_urls_in_tweets(
|
|
90
|
+
response_data.get("data", []), delete_entities=True
|
|
91
|
+
)
|
|
100
92
|
|
|
101
|
-
|
|
93
|
+
# Parse the response data
|
|
94
|
+
response_data = parse_search_recent_tweets_response(response_data)
|
|
102
95
|
|
|
103
96
|
return response_data
|
|
104
97
|
|
|
@@ -115,44 +108,81 @@ async def search_recent_tweets_by_keywords(
|
|
|
115
108
|
max_results: Annotated[
|
|
116
109
|
int, "The maximum number of results to return. Cannot be less than 10"
|
|
117
110
|
] = 10,
|
|
118
|
-
) -> Annotated[dict, "Dictionary containing the search results"]:
|
|
111
|
+
) -> Annotated[dict[str, Any], "Dictionary containing the search results"]:
|
|
119
112
|
"""
|
|
120
|
-
Search for recent tweets (last 7 days) on X (Twitter) by required keywords and phrases.
|
|
121
|
-
|
|
113
|
+
Search for recent tweets (last 7 days) on X (Twitter) by required keywords and phrases.
|
|
114
|
+
Includes replies and reposts.
|
|
115
|
+
One of the following input parameters MUST be provided: keywords, phrases
|
|
122
116
|
"""
|
|
123
117
|
|
|
124
118
|
if not any([keywords, phrases]):
|
|
125
|
-
raise
|
|
126
|
-
"
|
|
119
|
+
raise RetryableToolError( # noqa: TRY003
|
|
120
|
+
"No keywords or phrases provided",
|
|
121
|
+
developer_message="Predicted inputs didn't contain any keywords or phrases",
|
|
122
|
+
additional_prompt_content="Please provide at least one keyword or phrase for search",
|
|
123
|
+
retry_after_ms=500, # Play nice with X API rate limits
|
|
127
124
|
)
|
|
128
125
|
|
|
129
|
-
headers =
|
|
130
|
-
|
|
131
|
-
"Content-Type": "application/json",
|
|
132
|
-
}
|
|
126
|
+
headers = get_headers_with_token(context)
|
|
127
|
+
|
|
133
128
|
query = "".join([f'"{phrase}" ' for phrase in (phrases or [])])
|
|
134
129
|
if keywords:
|
|
135
130
|
query += " ".join(keywords or [])
|
|
136
131
|
|
|
137
|
-
params = {
|
|
138
|
-
"query": query,
|
|
132
|
+
params: dict[str, int | str] = {
|
|
133
|
+
"query": query.strip(),
|
|
139
134
|
"max_results": max(max_results, 10), # X API does not allow 'max_results' less than 10
|
|
140
135
|
}
|
|
141
|
-
url =
|
|
136
|
+
url = (
|
|
137
|
+
"https://api.x.com/2/tweets/search/recent?"
|
|
138
|
+
"expansions=author_id&user.fields=id,name,username,entities&tweet.fields=entities"
|
|
139
|
+
)
|
|
142
140
|
|
|
143
141
|
async with httpx.AsyncClient() as client:
|
|
144
142
|
response = await client.get(url, headers=headers, params=params, timeout=10)
|
|
143
|
+
response.raise_for_status()
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
response_data: dict[str, Any] = response.json()
|
|
146
|
+
|
|
147
|
+
# Expand the URLs that are in the tweets
|
|
148
|
+
response_data["data"] = expand_urls_in_tweets(
|
|
149
|
+
response_data.get("data", []), delete_entities=True
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Parse the response data
|
|
153
|
+
response_data = parse_search_recent_tweets_response(response_data)
|
|
154
|
+
|
|
155
|
+
return response_data
|
|
150
156
|
|
|
151
|
-
response_data = response.json()
|
|
152
157
|
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
@tool(requires_auth=X(scopes=["tweet.read", "users.read"]))
|
|
159
|
+
async def lookup_tweet_by_id(
|
|
160
|
+
context: ToolContext,
|
|
161
|
+
tweet_id: Annotated[str, "The ID of the tweet you want to look up"],
|
|
162
|
+
) -> Annotated[dict[str, Any], "Dictionary containing the tweet data"]:
|
|
163
|
+
"""Look up a tweet on X (Twitter) by tweet ID."""
|
|
164
|
+
|
|
165
|
+
headers = get_headers_with_token(context)
|
|
166
|
+
params = {
|
|
167
|
+
"expansions": "author_id",
|
|
168
|
+
"user.fields": "id,name,username,entities",
|
|
169
|
+
"tweet.fields": "entities",
|
|
170
|
+
}
|
|
171
|
+
url = f"{TWEETS_URL}/{tweet_id}"
|
|
155
172
|
|
|
156
|
-
|
|
173
|
+
async with httpx.AsyncClient() as client:
|
|
174
|
+
response = await client.get(url, headers=headers, params=params, timeout=10)
|
|
175
|
+
response.raise_for_status()
|
|
176
|
+
|
|
177
|
+
response_data: dict[str, Any] = response.json()
|
|
178
|
+
|
|
179
|
+
# Get the tweet data
|
|
180
|
+
tweet_data = response_data.get("data")
|
|
181
|
+
if tweet_data:
|
|
182
|
+
# Expand the URLs that are in the tweet
|
|
183
|
+
expanded_tweet_list = expand_urls_in_tweets([tweet_data], delete_entities=True)
|
|
184
|
+
response_data["data"] = expanded_tweet_list[0]
|
|
185
|
+
else:
|
|
186
|
+
response_data["data"] = {}
|
|
157
187
|
|
|
158
188
|
return response_data
|
arcade_x/tools/users.py
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
from typing import Annotated
|
|
2
2
|
|
|
3
3
|
import httpx
|
|
4
|
-
|
|
5
4
|
from arcade.sdk import ToolContext, tool
|
|
6
5
|
from arcade.sdk.auth import X
|
|
7
|
-
from arcade.sdk.errors import
|
|
8
|
-
|
|
6
|
+
from arcade.sdk.errors import RetryableToolError
|
|
7
|
+
|
|
8
|
+
from arcade_x.tools.utils import (
|
|
9
|
+
expand_urls_in_user_description,
|
|
10
|
+
expand_urls_in_user_url,
|
|
11
|
+
get_headers_with_token,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Users Lookup Tools. See developer docs for additional available query parameters:
|
|
15
|
+
# https://developer.x.com/en/docs/x-api/users/lookup/api-reference
|
|
9
16
|
|
|
10
17
|
|
|
11
|
-
# Users Lookup Tools. See developer docs for additional available query parameters: https://developer.x.com/en/docs/x-api/users/lookup/api-reference
|
|
12
18
|
@tool(requires_auth=X(scopes=["users.read", "tweet.read"]))
|
|
13
19
|
async def lookup_single_user_by_username(
|
|
14
20
|
context: ToolContext,
|
|
@@ -16,49 +22,43 @@ async def lookup_single_user_by_username(
|
|
|
16
22
|
) -> Annotated[dict, "User information including id, name, username, and description"]:
|
|
17
23
|
"""Look up a user on X (Twitter) by their username."""
|
|
18
24
|
|
|
19
|
-
headers =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
headers = get_headers_with_token(context)
|
|
26
|
+
|
|
27
|
+
user_fields = ",".join([
|
|
28
|
+
"created_at",
|
|
29
|
+
"description",
|
|
30
|
+
"id",
|
|
31
|
+
"location",
|
|
32
|
+
"most_recent_tweet_id",
|
|
33
|
+
"name",
|
|
34
|
+
"pinned_tweet_id",
|
|
35
|
+
"profile_image_url",
|
|
36
|
+
"protected",
|
|
37
|
+
"public_metrics",
|
|
38
|
+
"url",
|
|
39
|
+
"username",
|
|
40
|
+
"verified",
|
|
41
|
+
"verified_type",
|
|
42
|
+
"withheld",
|
|
43
|
+
"entities",
|
|
44
|
+
])
|
|
45
|
+
url = f"https://api.x.com/2/users/by/username/{username}?user.fields={user_fields}"
|
|
23
46
|
|
|
24
47
|
async with httpx.AsyncClient() as client:
|
|
25
48
|
response = await client.get(url, headers=headers, timeout=10)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
if response.status_code == 404:
|
|
50
|
+
# User not found
|
|
51
|
+
raise RetryableToolError( # noqa: TRY003
|
|
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
|
+
response.raise_for_status()
|
|
32
58
|
# Parse the response JSON
|
|
33
59
|
user_data = response.json()["data"]
|
|
34
60
|
|
|
35
|
-
expand_urls_in_user_description(user_data, delete_entities=False)
|
|
36
|
-
expand_urls_in_user_url(user_data, delete_entities=True)
|
|
61
|
+
user_data = expand_urls_in_user_description(user_data, delete_entities=False)
|
|
62
|
+
user_data = expand_urls_in_user_url(user_data, delete_entities=True)
|
|
37
63
|
|
|
38
|
-
"""
|
|
39
|
-
Example response["data"] structure:
|
|
40
|
-
{
|
|
41
|
-
"data": {
|
|
42
|
-
"verified_type": str,
|
|
43
|
-
"public_metrics": {
|
|
44
|
-
"followers_count": int,
|
|
45
|
-
"following_count": int,
|
|
46
|
-
"tweet_count": int,
|
|
47
|
-
"listed_count": int,
|
|
48
|
-
"like_count": int
|
|
49
|
-
},
|
|
50
|
-
"id": str,
|
|
51
|
-
"most_recent_tweet_id": str,
|
|
52
|
-
"url": str,
|
|
53
|
-
"verified": bool,
|
|
54
|
-
"location": str,
|
|
55
|
-
"description": str,
|
|
56
|
-
"name": str,
|
|
57
|
-
"username": str,
|
|
58
|
-
"profile_image_url": str,
|
|
59
|
-
"created_at": str,
|
|
60
|
-
"protected": bool
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
"""
|
|
64
64
|
return {"data": user_data}
|
arcade_x/tools/utils.py
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
+
from arcade.sdk import ToolContext
|
|
4
|
+
from arcade.sdk.errors import ToolExecutionError
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
def get_tweet_url(tweet_id: str) -> str:
|
|
5
8
|
"""Get the URL of a tweet given its ID."""
|
|
6
9
|
return f"https://x.com/x/status/{tweet_id}"
|
|
7
10
|
|
|
8
11
|
|
|
9
|
-
def
|
|
12
|
+
def get_headers_with_token(context: ToolContext) -> dict[str, str]:
|
|
13
|
+
"""Get the headers for a request to the X API."""
|
|
14
|
+
if context.authorization is None or context.authorization.token is None:
|
|
15
|
+
raise ToolExecutionError( # noqa: TRY003
|
|
16
|
+
"Missing Token. Authorization is required to post a tweet.",
|
|
17
|
+
developer_message="Token is not set in the ToolContext.",
|
|
18
|
+
)
|
|
19
|
+
return {
|
|
20
|
+
"Authorization": f"Bearer {context.authorization.token}",
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_search_recent_tweets_response(response_data: dict[str, Any]) -> dict[str, Any]:
|
|
10
26
|
"""
|
|
11
27
|
Parses response from the X API search recent tweets endpoint.
|
|
12
|
-
Returns
|
|
13
|
-
|
|
14
|
-
Example parsed response:
|
|
15
|
-
"tweets": [
|
|
16
|
-
{
|
|
17
|
-
"author_id": "558248927",
|
|
18
|
-
"id": "1838272933141319832",
|
|
19
|
-
"edit_history_tweet_ids": [
|
|
20
|
-
"1838272933141319832"
|
|
21
|
-
],
|
|
22
|
-
"text": "PR pending on @LangChainAI, will be integrated there soon! https://t.co/DPWd4lccQo",
|
|
23
|
-
"tweet_url": "https://x.com/x/status/1838272933141319832",
|
|
24
|
-
"author_username": "tomas_hk",
|
|
25
|
-
"author_name": "Tomas Hernando Kofman"
|
|
26
|
-
},
|
|
27
|
-
]
|
|
28
|
+
Returns the modified response data with added 'tweet_url', 'author_username', and 'author_name'.
|
|
28
29
|
"""
|
|
29
|
-
|
|
30
30
|
if not sanity_check_tweets_data(response_data):
|
|
31
31
|
return {"data": []}
|
|
32
32
|
|
|
33
|
+
# Add 'tweet_url' to each tweet
|
|
33
34
|
for tweet in response_data["data"]:
|
|
34
35
|
tweet["tweet_url"] = get_tweet_url(tweet["id"])
|
|
35
36
|
|
|
37
|
+
# Add 'author_username' and 'author_name' to each tweet
|
|
36
38
|
for tweet_data, user_data in zip(response_data["data"], response_data["includes"]["users"]):
|
|
37
39
|
tweet_data["author_username"] = user_data["username"]
|
|
38
40
|
tweet_data["author_name"] = user_data["name"]
|
|
@@ -40,68 +42,71 @@ def parse_search_recent_tweets_response(response_data: Any) -> dict:
|
|
|
40
42
|
return response_data
|
|
41
43
|
|
|
42
44
|
|
|
43
|
-
def sanity_check_tweets_data(tweets_data: dict) -> bool:
|
|
45
|
+
def sanity_check_tweets_data(tweets_data: dict[str, Any]) -> bool:
|
|
44
46
|
"""
|
|
45
47
|
Sanity check the tweets data.
|
|
46
48
|
Returns True if the tweets data is valid and contains tweets, False otherwise.
|
|
47
49
|
"""
|
|
48
|
-
if not tweets_data.get("data"
|
|
50
|
+
if not tweets_data.get("data"):
|
|
51
|
+
return False
|
|
52
|
+
# prefer clarity over appeasing linter here
|
|
53
|
+
if not tweets_data.get("includes", {}).get("users"): # noqa: SIM103
|
|
49
54
|
return False
|
|
50
|
-
return
|
|
55
|
+
return True
|
|
51
56
|
|
|
52
57
|
|
|
53
|
-
def expand_urls_in_tweets(
|
|
58
|
+
def expand_urls_in_tweets(
|
|
59
|
+
tweets_data: list[dict[str, Any]], delete_entities: bool = True
|
|
60
|
+
) -> list[dict[str, Any]]:
|
|
54
61
|
"""
|
|
55
|
-
|
|
56
|
-
X shortens urls, and consequently, this can cause language models to hallucinate.
|
|
57
|
-
See more about X's link shortner at https://help.x.com/en/using-x/url-shortener
|
|
62
|
+
Returns a new list of tweets with expanded URLs.
|
|
58
63
|
"""
|
|
64
|
+
new_tweets = []
|
|
59
65
|
for tweet_data in tweets_data:
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
new_tweet = tweet_data.copy()
|
|
67
|
+
if "entities" in new_tweet and "urls" in new_tweet["entities"]:
|
|
68
|
+
for url_entity in new_tweet["entities"]["urls"]:
|
|
62
69
|
short_url = url_entity["url"]
|
|
63
70
|
expanded_url = url_entity["expanded_url"]
|
|
64
|
-
|
|
71
|
+
new_tweet["text"] = new_tweet["text"].replace(short_url, expanded_url)
|
|
65
72
|
|
|
66
73
|
if delete_entities:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
new_tweet.pop("entities", None)
|
|
75
|
+
new_tweets.append(new_tweet)
|
|
76
|
+
return new_tweets
|
|
70
77
|
|
|
71
78
|
|
|
72
|
-
def expand_urls_in_user_description(user_data: dict, delete_entities: bool = True) ->
|
|
79
|
+
def expand_urls_in_user_description(user_data: dict, delete_entities: bool = True) -> dict:
|
|
73
80
|
"""
|
|
74
|
-
|
|
75
|
-
X shortens urls, and consequently, this can cause language models to hallucinate.
|
|
76
|
-
See more about X's link shortner at https://help.x.com/en/using-x/url-shortener
|
|
81
|
+
Returns a new user data dict with expanded URLs in the description.
|
|
77
82
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
new_user_data = user_data.copy()
|
|
84
|
+
description_urls = new_user_data.get("entities", {}).get("description", {}).get("urls", [])
|
|
85
|
+
description = new_user_data.get("description", "")
|
|
80
86
|
for url_info in description_urls:
|
|
81
87
|
t_co_link = url_info["url"]
|
|
82
88
|
expanded_url = url_info["expanded_url"]
|
|
83
89
|
description = description.replace(t_co_link, expanded_url)
|
|
84
|
-
|
|
90
|
+
new_user_data["description"] = description
|
|
85
91
|
|
|
86
92
|
if delete_entities:
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
new_user_data.pop("entities", None)
|
|
94
|
+
return new_user_data
|
|
89
95
|
|
|
90
96
|
|
|
91
|
-
def expand_urls_in_user_url(user_data: dict, delete_entities: bool = True) ->
|
|
97
|
+
def expand_urls_in_user_url(user_data: dict, delete_entities: bool = True) -> dict:
|
|
92
98
|
"""
|
|
93
|
-
|
|
94
|
-
X shortens urls, and consequently, this can cause language models to hallucinate.
|
|
95
|
-
See more about X's link shortner at https://help.x.com/en/using-x/url-shortener
|
|
99
|
+
Returns a new user data dict with expanded URLs in the URL field.
|
|
96
100
|
"""
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
new_user_data = user_data.copy()
|
|
102
|
+
url_urls = new_user_data.get("entities", {}).get("url", {}).get("urls", [])
|
|
103
|
+
url = new_user_data.get("url", "")
|
|
99
104
|
for url_info in url_urls:
|
|
100
105
|
t_co_link = url_info["url"]
|
|
101
106
|
expanded_url = url_info["expanded_url"]
|
|
102
107
|
url = url.replace(t_co_link, expanded_url)
|
|
103
|
-
|
|
108
|
+
new_user_data["url"] = url
|
|
104
109
|
|
|
105
110
|
if delete_entities:
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
new_user_data.pop("entities", None)
|
|
112
|
+
return new_user_data
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, arcadeai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: arcade_x
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: LLM tools for interacting with X (Twitter)
|
|
5
5
|
Author: Arcade AI
|
|
6
6
|
Author-email: dev@arcade-ai.com
|
|
@@ -10,5 +10,5 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
-
Requires-Dist: arcade-ai (==0.1.
|
|
13
|
+
Requires-Dist: arcade-ai (==0.1.5)
|
|
14
14
|
Requires-Dist: httpx (>=0.27.2,<0.28.0)
|
|
@@ -0,0 +1,9 @@
|
|
|
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/tweets.py,sha256=IDAjx1j_XLoc7ILyuRKVyjTPwQeNtD47lnyKUMO-WpU,6594
|
|
4
|
+
arcade_x/tools/users.py,sha256=zNACU9UhQJkGNnrei-E6GWvFnbDzg0YG9pkdplu8TzM,2148
|
|
5
|
+
arcade_x/tools/utils.py,sha256=VEeUo715m6l6X9Syszy2E72rdvv4h_tc-P7wMaQEbGo,4044
|
|
6
|
+
arcade_x-0.1.5.dist-info/LICENSE,sha256=SphQPbiNmBD1J6yJ7oxG9bIZDbj8lNHKSv5Kl86zA40,1066
|
|
7
|
+
arcade_x-0.1.5.dist-info/METADATA,sha256=pa4GiYU352AShVYdd2rDjVa3AP5b1sFtJpi1gUihQmM,510
|
|
8
|
+
arcade_x-0.1.5.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
9
|
+
arcade_x-0.1.5.dist-info/RECORD,,
|
arcade_x-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,8 +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/tweets.py,sha256=jSpaY0soU-ncouf6mVUoWSgbf_1R88xlgSDdS7dvKr4,6057
|
|
4
|
-
arcade_x/tools/users.py,sha256=P72TE7Pncfjr1CePiaRB0nyT9XEn4As01tRKtKHQ6QY,2401
|
|
5
|
-
arcade_x/tools/utils.py,sha256=VwssFULxj0zysJdZHnbs9NE4h3JLGSm6SCVhfu-lx-Q,4137
|
|
6
|
-
arcade_x-0.1.2.dist-info/METADATA,sha256=9V_QlVdZpirA75y2_uqUTUwxAXk1_ovau3Q0boL9lU0,510
|
|
7
|
-
arcade_x-0.1.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
8
|
-
arcade_x-0.1.2.dist-info/RECORD,,
|
|
File without changes
|