arcade-x 0.1.2__py3-none-any.whl → 0.1.3__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
@@ -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 ToolExecutionError
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: https://developer.x.com/en/docs/x-api/tweets/manage-tweets/api-reference
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 = {"Authorization": f"Bearer {context.authorization.token}"}
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. Includes replies and reposts."""
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
- "Authorization": f"Bearer {context.authorization.token}",
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 = "https://api.x.com/2/tweets/search/recent?expansions=author_id&user.fields=id,name,username,entities&tweet.fields=entities"
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
- if response.status_code != 200:
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 urls that are in the tweets
99
- expand_urls_in_tweets(response_data.get("data", []), delete_entities=True)
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
- parse_search_recent_tweets_response(response_data)
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. Includes replies and reposts
121
- One of the following input parametersMUST be provided: keywords, phrases
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 ValueError(
126
- "At least one of keywords or phrases must be provided to the '{search_recent_tweets_by_keywords.__name__}' tool."
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
- "Authorization": f"Bearer {context.authorization.token}",
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 = "https://api.x.com/2/tweets/search/recent?expansions=author_id&user.fields=id,name,username,entities&tweet.fields=entities"
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
- if response.status_code != 200:
147
- raise ToolExecutionError(
148
- f"Failed to search recent tweets during execution of '{search_recent_tweets_by_keywords.__name__}' tool. Request returned an error: {response.status_code} {response.text}"
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
- # Expand the urls that are in the tweets
154
- expand_urls_in_tweets(response_data.get("data", []), delete_entities=True)
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
- parse_search_recent_tweets_response(response_data)
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 ToolExecutionError
8
- from arcade_x.tools.utils import expand_urls_in_user_description, expand_urls_in_user_url
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
- "Authorization": f"Bearer {context.authorization.token}",
21
- }
22
- url = f"https://api.x.com/2/users/by/username/{username}?user.fields=created_at,description,id,location,most_recent_tweet_id,name,pinned_tweet_id,profile_image_url,protected,public_metrics,url,username,verified,verified_type,withheld,entities"
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
- if response.status_code != 200:
28
- raise ToolExecutionError(
29
- f"Failed to look up user during execution of '{lookup_single_user_by_username.__name__}' tool. Request returned an error: {response.status_code} {response.text}"
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 parse_search_recent_tweets_response(response_data: Any) -> dict:
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 a JSON string with the tweets data.
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 tweets_data.get("includes", {}).get("users", [])
55
+ return True
51
56
 
52
57
 
53
- def expand_urls_in_tweets(tweets_data: list[dict], delete_entities: bool = True) -> None:
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
- Expands the urls in the test of the provided tweets.
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
- if "entities" in tweet_data and "urls" in tweet_data["entities"]:
61
- for url_entity in tweet_data["entities"]["urls"]:
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
- tweet_data["text"] = tweet_data["text"].replace(short_url, expanded_url)
71
+ new_tweet["text"] = new_tweet["text"].replace(short_url, expanded_url)
65
72
 
66
73
  if delete_entities:
67
- tweet_data.pop(
68
- "entities", None
69
- ) # Now that we've expanded the urls in the tweet, we no longer need the entities
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) -> None:
79
+ def expand_urls_in_user_description(user_data: dict, delete_entities: bool = True) -> dict:
73
80
  """
74
- Expands the urls in the description of the provided user.
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
- description_urls = user_data.get("entities", {}).get("description", {}).get("urls", [])
79
- description = user_data.get("description", "")
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
- user_data["description"] = description
90
+ new_user_data["description"] = description
85
91
 
86
92
  if delete_entities:
87
- # Entities is no longer needed now that we have expanded the t.co links
88
- user_data.pop("entities", None)
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) -> None:
97
+ def expand_urls_in_user_url(user_data: dict, delete_entities: bool = True) -> dict:
92
98
  """
93
- Expands the urls in the url section of the provided user.
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
- url_urls = user_data.get("entities", {}).get("url", {}).get("urls", [])
98
- url = user_data.get("url", "")
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
- user_data["url"] = url
108
+ new_user_data["url"] = url
104
109
 
105
110
  if delete_entities:
106
- # Entities is no longer needed now that we have expanded the t.co links
107
- user_data.pop("entities", None)
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.2
3
+ Version: 0.1.3
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.2)
13
+ Requires-Dist: arcade-ai (==0.1.3)
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.3.dist-info/LICENSE,sha256=SphQPbiNmBD1J6yJ7oxG9bIZDbj8lNHKSv5Kl86zA40,1066
7
+ arcade_x-0.1.3.dist-info/METADATA,sha256=ApdteSqGUDAmg1_Wy70XPdfvFLuA5xx_foKjbtRnw1c,510
8
+ arcade_x-0.1.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
9
+ arcade_x-0.1.3.dist-info/RECORD,,
@@ -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,,