mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat(x): add 28 new X API v2 tool integrations and expand OAuth scopes (#3365)
* feat(x): add 28 new X API v2 tool integrations and expand OAuth scopes * fix(x): add missing nextToken param to search tweets and fix XCreateTweetParams type * fix(x): correct API spec issues in retweeted_by, quote_tweets, personalized_trends, and usage tools * fix(x): add missing newestId and oldestId to error meta in get_liked_tweets and get_quote_tweets * fix(x): add missing newestId/oldestId to get_liked_tweets success branch and includes to XTweetListResponse * fix(x): add error handling to create_tweet and delete_tweet transformResponse * fix(x): add error handling and logger to all X tools * fix(x): revert block requiredScopes to match current operations * feat(x): update block to support all 28 new X API v2 tools * fix(x): add missing text output and fix hiddenResult output key mismatch * docs(x): regenerate docs for all 28 new X API v2 tools
This commit is contained in:
@@ -29,74 +29,65 @@ In Sim, the X integration enables sophisticated social media automation scenario
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate X into the workflow. Can post a new tweet, get tweet details, search tweets, and get user profile.
|
||||
Integrate X into the workflow. Search tweets, manage bookmarks, follow/block/mute users, like and retweet, view trends, and more.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `x_write`
|
||||
### `x_create_tweet`
|
||||
|
||||
Post new tweets, reply to tweets, or create polls on X (Twitter)
|
||||
Create a new tweet, reply, or quote tweet on X
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `text` | string | Yes | The text content of your tweet \(max 280 characters\) |
|
||||
| `replyTo` | string | No | ID of the tweet to reply to \(e.g., 1234567890123456789\) |
|
||||
| `mediaIds` | array | No | Array of media IDs to attach to the tweet |
|
||||
| `poll` | object | No | Poll configuration for the tweet |
|
||||
| `text` | string | Yes | The text content of the tweet \(max 280 characters\) |
|
||||
| `replyToTweetId` | string | No | Tweet ID to reply to |
|
||||
| `quoteTweetId` | string | No | Tweet ID to quote |
|
||||
| `mediaIds` | string | No | Comma-separated media IDs to attach \(up to 4\) |
|
||||
| `replySettings` | string | No | Who can reply: "mentionedUsers", "following", "subscribers", or "verified" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweet` | object | The newly created tweet data |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet content text |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | ID of the tweet author |
|
||||
| ↳ `conversationId` | string | Conversation thread ID |
|
||||
| ↳ `attachments` | object | Media or poll attachments |
|
||||
| ↳ `mediaKeys` | array | Media attachment keys |
|
||||
| ↳ `pollId` | string | Poll ID if poll attached |
|
||||
| `id` | string | The ID of the created tweet |
|
||||
| `text` | string | The text of the created tweet |
|
||||
|
||||
### `x_read`
|
||||
### `x_delete_tweet`
|
||||
|
||||
Read tweet details, including replies and conversation context
|
||||
Delete a tweet authored by the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `tweetId` | string | Yes | ID of the tweet to read \(e.g., 1234567890123456789\) |
|
||||
| `includeReplies` | boolean | No | Whether to include replies to the tweet |
|
||||
| `tweetId` | string | Yes | The ID of the tweet to delete |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweet` | object | The main tweet data |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet content text |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | ID of the tweet author |
|
||||
| `context` | object | Conversation context including parent and root tweets |
|
||||
| `deleted` | boolean | Whether the tweet was successfully deleted |
|
||||
|
||||
### `x_search`
|
||||
### `x_search_tweets`
|
||||
|
||||
Search for tweets using keywords, hashtags, or advanced queries
|
||||
Search for recent tweets using keywords, hashtags, or advanced query operators
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | Yes | Search query \(e.g., "AI news", "#technology", "from:username"\). Supports X search operators |
|
||||
| `maxResults` | number | No | Maximum number of results to return \(e.g., 10, 25, 50\). Default: 10, max: 100 |
|
||||
| `startTime` | string | No | Start time for search \(ISO 8601 format\) |
|
||||
| `endTime` | string | No | End time for search \(ISO 8601 format\) |
|
||||
| `sortOrder` | string | No | Sort order for results \(recency or relevancy\) |
|
||||
| `query` | string | Yes | Search query \(supports operators like "from:", "to:", "#hashtag", "has:images", "is:retweet", "lang:"\) |
|
||||
| `maxResults` | number | No | Maximum number of results \(10-100, default 10\) |
|
||||
| `startTime` | string | No | Oldest UTC timestamp in ISO 8601 format \(e.g., 2024-01-01T00:00:00Z\) |
|
||||
| `endTime` | string | No | Newest UTC timestamp in ISO 8601 format |
|
||||
| `sinceId` | string | No | Returns tweets with ID greater than this |
|
||||
| `untilId` | string | No | Returns tweets with ID less than this |
|
||||
| `sortOrder` | string | No | Sort order: "recency" or "relevancy" |
|
||||
| `nextToken` | string | No | Pagination token for next page of results |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -104,38 +95,748 @@ Search for tweets using keywords, hashtags, or advanced queries
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweets` | array | Array of tweets matching the search query |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet content |
|
||||
| ↳ `createdAt` | string | Creation timestamp |
|
||||
| ↳ `text` | string | Tweet text content |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | Author user ID |
|
||||
| `includes` | object | Additional data including user profiles and media |
|
||||
| ↳ `conversationId` | string | Conversation thread ID |
|
||||
| ↳ `inReplyToUserId` | string | User ID being replied to |
|
||||
| ↳ `publicMetrics` | object | Engagement metrics |
|
||||
| ↳ `retweetCount` | number | Number of retweets |
|
||||
| ↳ `replyCount` | number | Number of replies |
|
||||
| ↳ `likeCount` | number | Number of likes |
|
||||
| ↳ `quoteCount` | number | Number of quotes |
|
||||
| `includes` | object | Additional data including user profiles |
|
||||
| ↳ `users` | array | Array of user objects referenced in tweets |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Search metadata including result count and pagination tokens |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `newestId` | string | ID of the newest tweet |
|
||||
| ↳ `oldestId` | string | ID of the oldest tweet |
|
||||
| ↳ `nextToken` | string | Pagination token for next page |
|
||||
|
||||
### `x_user`
|
||||
### `x_get_tweets_by_ids`
|
||||
|
||||
Get user profile information
|
||||
Look up multiple tweets by their IDs (up to 100)
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `username` | string | Yes | Username to look up without @ symbol \(e.g., elonmusk, openai\) |
|
||||
| `ids` | string | Yes | Comma-separated tweet IDs \(up to 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `user` | object | X user profile information |
|
||||
| `tweets` | array | Array of tweets matching the provided IDs |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet text content |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | Author user ID |
|
||||
| ↳ `conversationId` | string | Conversation thread ID |
|
||||
| ↳ `inReplyToUserId` | string | User ID being replied to |
|
||||
| ↳ `publicMetrics` | object | Engagement metrics |
|
||||
| ↳ `retweetCount` | number | Number of retweets |
|
||||
| ↳ `replyCount` | number | Number of replies |
|
||||
| ↳ `likeCount` | number | Number of likes |
|
||||
| ↳ `quoteCount` | number | Number of quotes |
|
||||
| `includes` | object | Additional data including user profiles |
|
||||
| ↳ `users` | array | Array of user objects referenced in tweets |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
|
||||
### `x_get_quote_tweets`
|
||||
|
||||
Get tweets that quote a specific tweet
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `tweetId` | string | Yes | The tweet ID to get quote tweets for |
|
||||
| `maxResults` | number | No | Maximum number of results \(10-100, default 10\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweets` | array | Array of quote tweets |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet text content |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | Author user ID |
|
||||
| ↳ `conversationId` | string | Conversation thread ID |
|
||||
| ↳ `inReplyToUserId` | string | User ID being replied to |
|
||||
| ↳ `publicMetrics` | object | Engagement metrics |
|
||||
| ↳ `retweetCount` | number | Number of retweets |
|
||||
| ↳ `replyCount` | number | Number of replies |
|
||||
| ↳ `likeCount` | number | Number of likes |
|
||||
| ↳ `quoteCount` | number | Number of quotes |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
|
||||
### `x_hide_reply`
|
||||
|
||||
Hide or unhide a reply to a tweet authored by the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `tweetId` | string | Yes | The reply tweet ID to hide or unhide |
|
||||
| `hidden` | boolean | Yes | Set to true to hide the reply, false to unhide |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `hidden` | boolean | Whether the reply is now hidden |
|
||||
|
||||
### `x_get_user_tweets`
|
||||
|
||||
Get tweets authored by a specific user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The user ID whose tweets to retrieve |
|
||||
| `maxResults` | number | No | Maximum number of results \(5-100, default 10\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page of results |
|
||||
| `startTime` | string | No | Oldest UTC timestamp in ISO 8601 format |
|
||||
| `endTime` | string | No | Newest UTC timestamp in ISO 8601 format |
|
||||
| `sinceId` | string | No | Returns tweets with ID greater than this |
|
||||
| `untilId` | string | No | Returns tweets with ID less than this |
|
||||
| `exclude` | string | No | Comma-separated types to exclude: "retweets", "replies" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweets` | array | Array of tweets by the user |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet text content |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | Author user ID |
|
||||
| ↳ `conversationId` | string | Conversation thread ID |
|
||||
| ↳ `inReplyToUserId` | string | User ID being replied to |
|
||||
| ↳ `publicMetrics` | object | Engagement metrics |
|
||||
| ↳ `retweetCount` | number | Number of retweets |
|
||||
| ↳ `replyCount` | number | Number of replies |
|
||||
| ↳ `likeCount` | number | Number of likes |
|
||||
| ↳ `quoteCount` | number | Number of quotes |
|
||||
| `includes` | object | Additional data including user profiles |
|
||||
| ↳ `users` | array | Array of user objects referenced in tweets |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `newestId` | string | ID of the newest tweet |
|
||||
| ↳ `oldestId` | string | ID of the oldest tweet |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
| ↳ `previousToken` | string | Token for previous page |
|
||||
|
||||
### `x_get_user_mentions`
|
||||
|
||||
Get tweets that mention a specific user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The user ID whose mentions to retrieve |
|
||||
| `maxResults` | number | No | Maximum number of results \(5-100, default 10\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page of results |
|
||||
| `startTime` | string | No | Oldest UTC timestamp in ISO 8601 format |
|
||||
| `endTime` | string | No | Newest UTC timestamp in ISO 8601 format |
|
||||
| `sinceId` | string | No | Returns tweets with ID greater than this |
|
||||
| `untilId` | string | No | Returns tweets with ID less than this |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweets` | array | Array of tweets mentioning the user |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet text content |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | Author user ID |
|
||||
| ↳ `conversationId` | string | Conversation thread ID |
|
||||
| ↳ `inReplyToUserId` | string | User ID being replied to |
|
||||
| ↳ `publicMetrics` | object | Engagement metrics |
|
||||
| ↳ `retweetCount` | number | Number of retweets |
|
||||
| ↳ `replyCount` | number | Number of replies |
|
||||
| ↳ `likeCount` | number | Number of likes |
|
||||
| ↳ `quoteCount` | number | Number of quotes |
|
||||
| `includes` | object | Additional data including user profiles |
|
||||
| ↳ `users` | array | Array of user objects referenced in tweets |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `newestId` | string | ID of the newest tweet |
|
||||
| ↳ `oldestId` | string | ID of the oldest tweet |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
| ↳ `previousToken` | string | Token for previous page |
|
||||
|
||||
### `x_get_user_timeline`
|
||||
|
||||
Get the reverse chronological home timeline for the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `maxResults` | number | No | Maximum number of results \(1-100, default 10\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page of results |
|
||||
| `startTime` | string | No | Oldest UTC timestamp in ISO 8601 format |
|
||||
| `endTime` | string | No | Newest UTC timestamp in ISO 8601 format |
|
||||
| `sinceId` | string | No | Returns tweets with ID greater than this |
|
||||
| `untilId` | string | No | Returns tweets with ID less than this |
|
||||
| `exclude` | string | No | Comma-separated types to exclude: "retweets", "replies" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweets` | array | Array of timeline tweets |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet text content |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | Author user ID |
|
||||
| ↳ `conversationId` | string | Conversation thread ID |
|
||||
| ↳ `inReplyToUserId` | string | User ID being replied to |
|
||||
| ↳ `publicMetrics` | object | Engagement metrics |
|
||||
| ↳ `retweetCount` | number | Number of retweets |
|
||||
| ↳ `replyCount` | number | Number of replies |
|
||||
| ↳ `likeCount` | number | Number of likes |
|
||||
| ↳ `quoteCount` | number | Number of quotes |
|
||||
| `includes` | object | Additional data including user profiles |
|
||||
| ↳ `users` | array | Array of user objects referenced in tweets |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `newestId` | string | ID of the newest tweet |
|
||||
| ↳ `oldestId` | string | ID of the oldest tweet |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
| ↳ `previousToken` | string | Token for previous page |
|
||||
|
||||
### `x_manage_like`
|
||||
|
||||
Like or unlike a tweet on X
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `tweetId` | string | Yes | The tweet ID to like or unlike |
|
||||
| `action` | string | Yes | Action to perform: "like" or "unlike" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `liked` | boolean | Whether the tweet is now liked |
|
||||
|
||||
### `x_manage_retweet`
|
||||
|
||||
Retweet or unretweet a tweet on X
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `tweetId` | string | Yes | The tweet ID to retweet or unretweet |
|
||||
| `action` | string | Yes | Action to perform: "retweet" or "unretweet" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `retweeted` | boolean | Whether the tweet is now retweeted |
|
||||
|
||||
### `x_get_liked_tweets`
|
||||
|
||||
Get tweets liked by a specific user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The user ID whose liked tweets to retrieve |
|
||||
| `maxResults` | number | No | Maximum number of results \(5-100\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweets` | array | Array of liked tweets |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet content |
|
||||
| ↳ `createdAt` | string | Creation timestamp |
|
||||
| ↳ `authorId` | string | Author user ID |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
|
||||
### `x_get_liking_users`
|
||||
|
||||
Get the list of users who liked a specific tweet
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `tweetId` | string | Yes | The tweet ID to get liking users for |
|
||||
| `maxResults` | number | No | Maximum number of results \(1-100, default 100\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | Array of users who liked the tweet |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio/description |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
|
||||
### `x_get_retweeted_by`
|
||||
|
||||
Get the list of users who retweeted a specific tweet
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `tweetId` | string | Yes | The tweet ID to get retweeters for |
|
||||
| `maxResults` | number | No | Maximum number of results \(1-100, default 100\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | Array of users who retweeted the tweet |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
|
||||
### `x_get_bookmarks`
|
||||
|
||||
Get bookmarked tweets for the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `maxResults` | number | No | Maximum number of results \(1-100\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page of results |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tweets` | array | Array of bookmarked tweets |
|
||||
| ↳ `id` | string | Tweet ID |
|
||||
| ↳ `text` | string | Tweet text content |
|
||||
| ↳ `createdAt` | string | Tweet creation timestamp |
|
||||
| ↳ `authorId` | string | Author user ID |
|
||||
| ↳ `conversationId` | string | Conversation thread ID |
|
||||
| ↳ `inReplyToUserId` | string | User ID being replied to |
|
||||
| ↳ `publicMetrics` | object | Engagement metrics |
|
||||
| ↳ `retweetCount` | number | Number of retweets |
|
||||
| ↳ `replyCount` | number | Number of replies |
|
||||
| ↳ `likeCount` | number | Number of likes |
|
||||
| ↳ `quoteCount` | number | Number of quotes |
|
||||
| `includes` | object | Additional data including user profiles |
|
||||
| ↳ `users` | array | Array of user objects referenced in tweets |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `newestId` | string | ID of the newest tweet |
|
||||
| ↳ `oldestId` | string | ID of the oldest tweet |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
| ↳ `previousToken` | string | Token for previous page |
|
||||
|
||||
### `x_create_bookmark`
|
||||
|
||||
Bookmark a tweet for the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `tweetId` | string | Yes | The tweet ID to bookmark |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `bookmarked` | boolean | Whether the tweet was successfully bookmarked |
|
||||
|
||||
### `x_delete_bookmark`
|
||||
|
||||
Remove a tweet from the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `tweetId` | string | Yes | The tweet ID to remove from bookmarks |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `bookmarked` | boolean | Whether the tweet is still bookmarked \(should be false after deletion\) |
|
||||
|
||||
### `x_get_me`
|
||||
|
||||
Get the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `user` | object | Authenticated user profile |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
|
||||
### `x_search_users`
|
||||
|
||||
Search for X users by name, username, or bio
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | Yes | Search keyword \(1-50 chars, matches name, username, or bio\) |
|
||||
| `maxResults` | number | No | Maximum number of results \(1-1000, default 100\) |
|
||||
| `nextToken` | string | No | Pagination token for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | Array of users matching the search query |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Search metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `nextToken` | string | Pagination token for next page |
|
||||
|
||||
### `x_get_followers`
|
||||
|
||||
Get the list of followers for a user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The user ID whose followers to retrieve |
|
||||
| `maxResults` | number | No | Maximum number of results \(1-1000, default 100\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | Array of follower user profiles |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
|
||||
### `x_get_following`
|
||||
|
||||
Get the list of users that a user is following
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The user ID whose following list to retrieve |
|
||||
| `maxResults` | number | No | Maximum number of results \(1-1000, default 100\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | Array of users being followed |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
|
||||
### `x_manage_follow`
|
||||
|
||||
Follow or unfollow a user on X
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `targetUserId` | string | Yes | The user ID to follow or unfollow |
|
||||
| `action` | string | Yes | Action to perform: "follow" or "unfollow" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `following` | boolean | Whether you are now following the user |
|
||||
| `pendingFollow` | boolean | Whether the follow request is pending \(for protected accounts\) |
|
||||
|
||||
### `x_manage_block`
|
||||
|
||||
Block or unblock a user on X
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `targetUserId` | string | Yes | The user ID to block or unblock |
|
||||
| `action` | string | Yes | Action to perform: "block" or "unblock" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `blocking` | boolean | Whether you are now blocking the user |
|
||||
|
||||
### `x_get_blocking`
|
||||
|
||||
Get the list of users blocked by the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `maxResults` | number | No | Maximum number of results \(1-1000\) |
|
||||
| `paginationToken` | string | No | Pagination token for next page |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | Array of blocked user profiles |
|
||||
| ↳ `id` | string | User ID |
|
||||
| ↳ `username` | string | Username without @ symbol |
|
||||
| ↳ `name` | string | Display name |
|
||||
| ↳ `description` | string | User bio |
|
||||
| ↳ `profileImageUrl` | string | Profile image URL |
|
||||
| ↳ `verified` | boolean | Whether the user is verified |
|
||||
| ↳ `metrics` | object | User statistics |
|
||||
| ↳ `followersCount` | number | Number of followers |
|
||||
| ↳ `followingCount` | number | Number of users following |
|
||||
| ↳ `tweetCount` | number | Total number of tweets |
|
||||
| `meta` | object | Pagination metadata |
|
||||
| ↳ `resultCount` | number | Number of results returned |
|
||||
| ↳ `nextToken` | string | Token for next page |
|
||||
|
||||
### `x_manage_mute`
|
||||
|
||||
Mute or unmute a user on X
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `userId` | string | Yes | The authenticated user ID |
|
||||
| `targetUserId` | string | Yes | The user ID to mute or unmute |
|
||||
| `action` | string | Yes | Action to perform: "mute" or "unmute" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `muting` | boolean | Whether you are now muting the user |
|
||||
|
||||
### `x_get_trends_by_woeid`
|
||||
|
||||
Get trending topics for a specific location by WOEID (e.g., 1 for worldwide, 23424977 for US)
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `woeid` | string | Yes | Yahoo Where On Earth ID \(e.g., "1" for worldwide, "23424977" for US, "23424975" for UK\) |
|
||||
| `maxTrends` | number | No | Maximum number of trends to return \(1-50, default 20\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `trends` | array | Array of trending topics |
|
||||
| ↳ `trendName` | string | Name of the trending topic |
|
||||
| ↳ `tweetCount` | number | Number of tweets for this trend |
|
||||
|
||||
### `x_get_personalized_trends`
|
||||
|
||||
Get personalized trending topics for the authenticated user
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `trends` | array | Array of personalized trending topics |
|
||||
| ↳ `trendName` | string | Name of the trending topic |
|
||||
| ↳ `postCount` | number | Number of posts for this trend |
|
||||
| ↳ `category` | string | Category of the trend |
|
||||
| ↳ `trendingSince` | string | ISO 8601 timestamp of when the topic started trending |
|
||||
|
||||
### `x_get_usage`
|
||||
|
||||
Get the API usage data for your X project
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `days` | number | No | Number of days of usage data to return \(1-90, default 7\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `capResetDay` | number | Day of month when usage cap resets |
|
||||
| `projectId` | string | The project ID |
|
||||
| `projectCap` | number | The project tweet consumption cap |
|
||||
| `projectUsage` | number | Total tweets consumed in current period |
|
||||
| `dailyProjectUsage` | array | Daily project usage breakdown |
|
||||
| ↳ `date` | string | Usage date in ISO 8601 format |
|
||||
| ↳ `usage` | number | Number of tweets consumed |
|
||||
| `dailyClientAppUsage` | array | Daily per-app usage breakdown |
|
||||
| ↳ `clientAppId` | string | Client application ID |
|
||||
| ↳ `usage` | array | Daily usage entries for this app |
|
||||
| ↳ `date` | string | Usage date in ISO 8601 format |
|
||||
| ↳ `usage` | number | Number of tweets consumed |
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { xIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { XResponse } from '@/tools/x/types'
|
||||
|
||||
export const XBlock: BlockConfig<XResponse> = {
|
||||
export const XBlock: BlockConfig = {
|
||||
type: 'x',
|
||||
name: 'X',
|
||||
description: 'Interact with X',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrate X into the workflow. Can post a new tweet, get tweet details, search tweets, and get user profile.',
|
||||
'Integrate X into the workflow. Search tweets, manage bookmarks, follow/block/mute users, like and retweet, view trends, and more.',
|
||||
docsLink: 'https://docs.sim.ai/tools/x',
|
||||
category: 'tools',
|
||||
bgColor: '#000000', // X's black color
|
||||
bgColor: '#000000',
|
||||
icon: xIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
@@ -20,13 +19,46 @@ export const XBlock: BlockConfig<XResponse> = {
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Post a New Tweet', id: 'x_write' },
|
||||
{ label: 'Get Tweet Details', id: 'x_read' },
|
||||
{ label: 'Search Tweets', id: 'x_search' },
|
||||
{ label: 'Get User Profile', id: 'x_user' },
|
||||
// Tweet Operations
|
||||
{ label: 'Create Tweet', id: 'x_create_tweet' },
|
||||
{ label: 'Delete Tweet', id: 'x_delete_tweet' },
|
||||
{ label: 'Search Tweets', id: 'x_search_tweets' },
|
||||
{ label: 'Get Tweets by IDs', id: 'x_get_tweets_by_ids' },
|
||||
{ label: 'Get Quote Tweets', id: 'x_get_quote_tweets' },
|
||||
{ label: 'Hide Reply', id: 'x_hide_reply' },
|
||||
// User Tweet Operations
|
||||
{ label: 'Get User Tweets', id: 'x_get_user_tweets' },
|
||||
{ label: 'Get User Mentions', id: 'x_get_user_mentions' },
|
||||
{ label: 'Get User Timeline', id: 'x_get_user_timeline' },
|
||||
// Engagement Operations
|
||||
{ label: 'Like / Unlike', id: 'x_manage_like' },
|
||||
{ label: 'Retweet / Unretweet', id: 'x_manage_retweet' },
|
||||
{ label: 'Get Liked Tweets', id: 'x_get_liked_tweets' },
|
||||
{ label: 'Get Liking Users', id: 'x_get_liking_users' },
|
||||
{ label: 'Get Retweeted By', id: 'x_get_retweeted_by' },
|
||||
// Bookmark Operations
|
||||
{ label: 'Get Bookmarks', id: 'x_get_bookmarks' },
|
||||
{ label: 'Create Bookmark', id: 'x_create_bookmark' },
|
||||
{ label: 'Delete Bookmark', id: 'x_delete_bookmark' },
|
||||
// User Operations
|
||||
{ label: 'Get My Profile', id: 'x_get_me' },
|
||||
{ label: 'Search Users', id: 'x_search_users' },
|
||||
{ label: 'Get Followers', id: 'x_get_followers' },
|
||||
{ label: 'Get Following', id: 'x_get_following' },
|
||||
// User Relationship Operations
|
||||
{ label: 'Follow / Unfollow', id: 'x_manage_follow' },
|
||||
{ label: 'Block / Unblock', id: 'x_manage_block' },
|
||||
{ label: 'Get Blocked Users', id: 'x_get_blocking' },
|
||||
{ label: 'Mute / Unmute', id: 'x_manage_mute' },
|
||||
// Trends Operations
|
||||
{ label: 'Get Trends by Location', id: 'x_get_trends_by_woeid' },
|
||||
{ label: 'Get Personalized Trends', id: 'x_get_personalized_trends' },
|
||||
// Usage Operations
|
||||
{ label: 'Get API Usage', id: 'x_get_usage' },
|
||||
],
|
||||
value: () => 'x_write',
|
||||
value: () => 'x_create_tweet',
|
||||
},
|
||||
// --- OAuth Credential ---
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'X Account',
|
||||
@@ -34,7 +66,23 @@ export const XBlock: BlockConfig<XResponse> = {
|
||||
serviceId: 'x',
|
||||
canonicalParamId: 'oauthCredential',
|
||||
mode: 'basic',
|
||||
requiredScopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
|
||||
requiredScopes: [
|
||||
'tweet.read',
|
||||
'tweet.write',
|
||||
'tweet.moderate.write',
|
||||
'users.read',
|
||||
'follows.read',
|
||||
'follows.write',
|
||||
'bookmark.read',
|
||||
'bookmark.write',
|
||||
'like.read',
|
||||
'like.write',
|
||||
'block.read',
|
||||
'block.write',
|
||||
'mute.read',
|
||||
'mute.write',
|
||||
'offline.access',
|
||||
],
|
||||
placeholder: 'Select X account',
|
||||
},
|
||||
{
|
||||
@@ -45,79 +93,270 @@ export const XBlock: BlockConfig<XResponse> = {
|
||||
mode: 'advanced',
|
||||
placeholder: 'Enter credential ID',
|
||||
},
|
||||
// --- Create Tweet fields ---
|
||||
{
|
||||
id: 'text',
|
||||
title: 'Tweet Text',
|
||||
type: 'long-input',
|
||||
placeholder: "What's happening?",
|
||||
condition: { field: 'operation', value: 'x_write' },
|
||||
condition: { field: 'operation', value: 'x_create_tweet' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'replyTo',
|
||||
title: 'Reply To (Tweet ID)',
|
||||
id: 'replyToTweetId',
|
||||
title: 'Reply To Tweet ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter tweet ID to reply to',
|
||||
condition: { field: 'operation', value: 'x_write' },
|
||||
condition: { field: 'operation', value: 'x_create_tweet' },
|
||||
},
|
||||
{
|
||||
id: 'quoteTweetId',
|
||||
title: 'Quote Tweet ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter tweet ID to quote',
|
||||
condition: { field: 'operation', value: 'x_create_tweet' },
|
||||
},
|
||||
{
|
||||
id: 'mediaIds',
|
||||
title: 'Media IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter comma-separated media IDs',
|
||||
condition: { field: 'operation', value: 'x_write' },
|
||||
placeholder: 'Comma-separated media IDs (up to 4)',
|
||||
condition: { field: 'operation', value: 'x_create_tweet' },
|
||||
},
|
||||
{
|
||||
id: 'replySettings',
|
||||
title: 'Reply Settings',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Everyone', id: '' },
|
||||
{ label: 'Mentioned Users', id: 'mentionedUsers' },
|
||||
{ label: 'Following', id: 'following' },
|
||||
{ label: 'Subscribers', id: 'subscribers' },
|
||||
{ label: 'Verified', id: 'verified' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: 'x_create_tweet' },
|
||||
},
|
||||
// --- Tweet ID field (shared by multiple operations) ---
|
||||
{
|
||||
id: 'tweetId',
|
||||
title: 'Tweet ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter tweet ID to read',
|
||||
condition: { field: 'operation', value: 'x_read' },
|
||||
required: true,
|
||||
placeholder: 'Enter tweet ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_delete_tweet',
|
||||
'x_get_quote_tweets',
|
||||
'x_hide_reply',
|
||||
'x_manage_like',
|
||||
'x_manage_retweet',
|
||||
'x_get_liking_users',
|
||||
'x_get_retweeted_by',
|
||||
'x_create_bookmark',
|
||||
'x_delete_bookmark',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_delete_tweet',
|
||||
'x_get_quote_tweets',
|
||||
'x_hide_reply',
|
||||
'x_manage_like',
|
||||
'x_manage_retweet',
|
||||
'x_get_liking_users',
|
||||
'x_get_retweeted_by',
|
||||
'x_create_bookmark',
|
||||
'x_delete_bookmark',
|
||||
],
|
||||
},
|
||||
},
|
||||
// --- Hide Reply toggle ---
|
||||
{
|
||||
id: 'includeReplies',
|
||||
title: 'Include Replies',
|
||||
id: 'hidden',
|
||||
title: 'Hidden',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'true', id: 'true' },
|
||||
{ label: 'false', id: 'false' },
|
||||
{ label: 'Hide', id: 'true' },
|
||||
{ label: 'Unhide', id: 'false' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'x_read' },
|
||||
value: () => 'true',
|
||||
condition: { field: 'operation', value: 'x_hide_reply' },
|
||||
},
|
||||
// --- Tweet IDs (batch lookup) ---
|
||||
{
|
||||
id: 'ids',
|
||||
title: 'Tweet IDs',
|
||||
type: 'long-input',
|
||||
placeholder: 'Comma-separated tweet IDs (up to 100)',
|
||||
condition: { field: 'operation', value: 'x_get_tweets_by_ids' },
|
||||
required: true,
|
||||
},
|
||||
// --- Search query fields ---
|
||||
{
|
||||
id: 'query',
|
||||
title: 'Search Query',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter search terms (supports X search operators)',
|
||||
condition: { field: 'operation', value: 'x_search' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'maxResults',
|
||||
title: 'Max Results',
|
||||
type: 'short-input',
|
||||
placeholder: '10',
|
||||
condition: { field: 'operation', value: 'x_search' },
|
||||
condition: { field: 'operation', value: ['x_search_tweets', 'x_search_users'] },
|
||||
required: { field: 'operation', value: ['x_search_tweets', 'x_search_users'] },
|
||||
},
|
||||
{
|
||||
id: 'sortOrder',
|
||||
title: 'Sort Order',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'recency', id: 'recency' },
|
||||
{ label: 'relevancy', id: 'relevancy' },
|
||||
{ label: 'Recent', id: 'recency' },
|
||||
{ label: 'Relevant', id: 'relevancy' },
|
||||
],
|
||||
value: () => 'recency',
|
||||
condition: { field: 'operation', value: 'x_search' },
|
||||
condition: { field: 'operation', value: 'x_search_tweets' },
|
||||
},
|
||||
// --- User ID field (shared by many operations) ---
|
||||
{
|
||||
id: 'userId',
|
||||
title: 'User ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter user ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
'x_get_liked_tweets',
|
||||
'x_get_bookmarks',
|
||||
'x_create_bookmark',
|
||||
'x_delete_bookmark',
|
||||
'x_get_followers',
|
||||
'x_get_following',
|
||||
'x_get_blocking',
|
||||
'x_manage_follow',
|
||||
'x_manage_block',
|
||||
'x_manage_mute',
|
||||
'x_manage_like',
|
||||
'x_manage_retweet',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
'x_get_liked_tweets',
|
||||
'x_get_bookmarks',
|
||||
'x_create_bookmark',
|
||||
'x_delete_bookmark',
|
||||
'x_get_followers',
|
||||
'x_get_following',
|
||||
'x_get_blocking',
|
||||
'x_manage_follow',
|
||||
'x_manage_block',
|
||||
'x_manage_mute',
|
||||
'x_manage_like',
|
||||
'x_manage_retweet',
|
||||
],
|
||||
},
|
||||
},
|
||||
// --- Target User ID (for follow/block/mute) ---
|
||||
{
|
||||
id: 'targetUserId',
|
||||
title: 'Target User ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter target user ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['x_manage_follow', 'x_manage_block', 'x_manage_mute'],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['x_manage_follow', 'x_manage_block', 'x_manage_mute'],
|
||||
},
|
||||
},
|
||||
// --- Action dropdowns for manage operations ---
|
||||
{
|
||||
id: 'action',
|
||||
title: 'Action',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Like', id: 'like' },
|
||||
{ label: 'Unlike', id: 'unlike' },
|
||||
],
|
||||
value: () => 'like',
|
||||
condition: { field: 'operation', value: 'x_manage_like' },
|
||||
},
|
||||
{
|
||||
id: 'retweetAction',
|
||||
title: 'Action',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Retweet', id: 'retweet' },
|
||||
{ label: 'Unretweet', id: 'unretweet' },
|
||||
],
|
||||
value: () => 'retweet',
|
||||
condition: { field: 'operation', value: 'x_manage_retweet' },
|
||||
},
|
||||
{
|
||||
id: 'followAction',
|
||||
title: 'Action',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Follow', id: 'follow' },
|
||||
{ label: 'Unfollow', id: 'unfollow' },
|
||||
],
|
||||
value: () => 'follow',
|
||||
condition: { field: 'operation', value: 'x_manage_follow' },
|
||||
},
|
||||
{
|
||||
id: 'blockAction',
|
||||
title: 'Action',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Block', id: 'block' },
|
||||
{ label: 'Unblock', id: 'unblock' },
|
||||
],
|
||||
value: () => 'block',
|
||||
condition: { field: 'operation', value: 'x_manage_block' },
|
||||
},
|
||||
{
|
||||
id: 'muteAction',
|
||||
title: 'Action',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Mute', id: 'mute' },
|
||||
{ label: 'Unmute', id: 'unmute' },
|
||||
],
|
||||
value: () => 'mute',
|
||||
condition: { field: 'operation', value: 'x_manage_mute' },
|
||||
},
|
||||
// --- Exclude filter (for user tweets/timeline) ---
|
||||
{
|
||||
id: 'exclude',
|
||||
title: 'Exclude',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated: retweets, replies',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['x_get_user_tweets', 'x_get_user_timeline'],
|
||||
},
|
||||
},
|
||||
// --- Time range fields (shared by tweet search and user tweet operations) ---
|
||||
{
|
||||
id: 'startTime',
|
||||
title: 'Start Time',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||
condition: { field: 'operation', value: 'x_search' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_search_tweets',
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
],
|
||||
},
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate an ISO 8601 timestamp based on the user's description.
|
||||
@@ -138,7 +377,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
title: 'End Time',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||
condition: { field: 'operation', value: 'x_search' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_search_tweets',
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
],
|
||||
},
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate an ISO 8601 timestamp based on the user's description.
|
||||
@@ -154,55 +401,149 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
// --- Max Results (shared by many operations) ---
|
||||
{
|
||||
id: 'username',
|
||||
title: 'Username',
|
||||
id: 'maxResults',
|
||||
title: 'Max Results',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter username (without @)',
|
||||
condition: { field: 'operation', value: 'x_user' },
|
||||
placeholder: '10',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_search_tweets',
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
'x_get_liked_tweets',
|
||||
'x_get_bookmarks',
|
||||
'x_get_quote_tweets',
|
||||
'x_get_liking_users',
|
||||
'x_get_retweeted_by',
|
||||
'x_search_users',
|
||||
'x_get_followers',
|
||||
'x_get_following',
|
||||
'x_get_blocking',
|
||||
],
|
||||
},
|
||||
},
|
||||
// --- Pagination Token (shared by many operations) ---
|
||||
{
|
||||
id: 'paginationToken',
|
||||
title: 'Pagination Token',
|
||||
type: 'short-input',
|
||||
placeholder: 'Token for next page',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
'x_get_liked_tweets',
|
||||
'x_get_bookmarks',
|
||||
'x_get_quote_tweets',
|
||||
'x_get_liking_users',
|
||||
'x_get_retweeted_by',
|
||||
'x_get_followers',
|
||||
'x_get_following',
|
||||
'x_get_blocking',
|
||||
],
|
||||
},
|
||||
},
|
||||
// --- Next Token (for search operations that use nextToken instead of paginationToken) ---
|
||||
{
|
||||
id: 'nextToken',
|
||||
title: 'Pagination Token',
|
||||
type: 'short-input',
|
||||
placeholder: 'Token for next page',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['x_search_tweets', 'x_search_users'],
|
||||
},
|
||||
},
|
||||
// --- Trends fields ---
|
||||
{
|
||||
id: 'woeid',
|
||||
title: 'WOEID',
|
||||
type: 'short-input',
|
||||
placeholder: '1 (worldwide), 23424977 (US)',
|
||||
condition: { field: 'operation', value: 'x_get_trends_by_woeid' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'maxTrends',
|
||||
title: 'Max Trends',
|
||||
type: 'short-input',
|
||||
placeholder: '20',
|
||||
condition: { field: 'operation', value: 'x_get_trends_by_woeid' },
|
||||
},
|
||||
// --- Usage fields ---
|
||||
{
|
||||
id: 'days',
|
||||
title: 'Days',
|
||||
type: 'short-input',
|
||||
placeholder: '7 (1-90)',
|
||||
condition: { field: 'operation', value: 'x_get_usage' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: ['x_write', 'x_read', 'x_search', 'x_user'],
|
||||
access: [
|
||||
'x_create_tweet',
|
||||
'x_delete_tweet',
|
||||
'x_search_tweets',
|
||||
'x_get_tweets_by_ids',
|
||||
'x_get_quote_tweets',
|
||||
'x_hide_reply',
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
'x_manage_like',
|
||||
'x_manage_retweet',
|
||||
'x_get_liked_tweets',
|
||||
'x_get_liking_users',
|
||||
'x_get_retweeted_by',
|
||||
'x_get_bookmarks',
|
||||
'x_create_bookmark',
|
||||
'x_delete_bookmark',
|
||||
'x_get_me',
|
||||
'x_search_users',
|
||||
'x_get_followers',
|
||||
'x_get_following',
|
||||
'x_manage_follow',
|
||||
'x_manage_block',
|
||||
'x_get_blocking',
|
||||
'x_manage_mute',
|
||||
'x_get_trends_by_woeid',
|
||||
'x_get_personalized_trends',
|
||||
'x_get_usage',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'x_write':
|
||||
return 'x_write'
|
||||
case 'x_read':
|
||||
return 'x_read'
|
||||
case 'x_search':
|
||||
return 'x_search'
|
||||
case 'x_user':
|
||||
return 'x_user'
|
||||
default:
|
||||
return 'x_write'
|
||||
}
|
||||
},
|
||||
tool: (params) => params.operation,
|
||||
params: (params) => {
|
||||
const { oauthCredential, ...rest } = params
|
||||
|
||||
const parsedParams: Record<string, any> = {
|
||||
const parsedParams: Record<string, unknown> = {
|
||||
credential: oauthCredential,
|
||||
}
|
||||
|
||||
Object.keys(rest).forEach((key) => {
|
||||
const value = rest[key]
|
||||
for (const [key, value] of Object.entries(rest)) {
|
||||
if (value === undefined || value === null || value === '') continue
|
||||
|
||||
if (value === 'true' || value === 'false') {
|
||||
parsedParams[key] = value === 'true'
|
||||
} else if (key === 'maxResults' && value) {
|
||||
if (key === 'maxResults' || key === 'maxTrends' || key === 'days') {
|
||||
parsedParams[key] = Number.parseInt(value as string, 10)
|
||||
} else if (key === 'mediaIds' && typeof value === 'string') {
|
||||
parsedParams[key] = value
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id !== '')
|
||||
} else if (key === 'hidden') {
|
||||
parsedParams[key] = value === 'true'
|
||||
} else if (key === 'retweetAction') {
|
||||
parsedParams.action = value
|
||||
} else if (key === 'followAction') {
|
||||
parsedParams.action = value
|
||||
} else if (key === 'blockAction') {
|
||||
parsedParams.action = value
|
||||
} else if (key === 'muteAction') {
|
||||
parsedParams.action = value
|
||||
} else {
|
||||
parsedParams[key] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return parsedParams
|
||||
},
|
||||
@@ -211,64 +552,207 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
oauthCredential: { type: 'string', description: 'X account credential' },
|
||||
// Tweet fields
|
||||
text: { type: 'string', description: 'Tweet text content' },
|
||||
replyTo: { type: 'string', description: 'Reply to tweet ID' },
|
||||
mediaIds: { type: 'string', description: 'Media identifiers' },
|
||||
poll: { type: 'json', description: 'Poll configuration' },
|
||||
replyToTweetId: { type: 'string', description: 'Tweet ID to reply to' },
|
||||
quoteTweetId: { type: 'string', description: 'Tweet ID to quote' },
|
||||
mediaIds: { type: 'string', description: 'Comma-separated media IDs' },
|
||||
replySettings: { type: 'string', description: 'Reply permission setting' },
|
||||
tweetId: { type: 'string', description: 'Tweet identifier' },
|
||||
includeReplies: { type: 'boolean', description: 'Include replies' },
|
||||
query: { type: 'string', description: 'Search query terms' },
|
||||
maxResults: { type: 'number', description: 'Maximum search results' },
|
||||
startTime: { type: 'string', description: 'Search start time' },
|
||||
endTime: { type: 'string', description: 'Search end time' },
|
||||
sortOrder: { type: 'string', description: 'Result sort order' },
|
||||
username: { type: 'string', description: 'User profile name' },
|
||||
includeRecentTweets: { type: 'boolean', description: 'Include recent tweets' },
|
||||
ids: { type: 'string', description: 'Comma-separated tweet IDs' },
|
||||
hidden: { type: 'string', description: 'Hide or unhide reply' },
|
||||
// User fields
|
||||
userId: { type: 'string', description: 'User identifier' },
|
||||
targetUserId: { type: 'string', description: 'Target user identifier' },
|
||||
// Action fields
|
||||
action: { type: 'string', description: 'Action to perform (like/unlike, etc.)' },
|
||||
retweetAction: { type: 'string', description: 'Retweet action' },
|
||||
followAction: { type: 'string', description: 'Follow action' },
|
||||
blockAction: { type: 'string', description: 'Block action' },
|
||||
muteAction: { type: 'string', description: 'Mute action' },
|
||||
// Search/filter fields
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
sortOrder: { type: 'string', description: 'Sort order' },
|
||||
exclude: { type: 'string', description: 'Exclusion filter' },
|
||||
// Time/pagination fields
|
||||
startTime: { type: 'string', description: 'Start time filter' },
|
||||
endTime: { type: 'string', description: 'End time filter' },
|
||||
maxResults: { type: 'number', description: 'Maximum results' },
|
||||
paginationToken: { type: 'string', description: 'Pagination token' },
|
||||
nextToken: { type: 'string', description: 'Next page token' },
|
||||
// Trends fields
|
||||
woeid: { type: 'string', description: 'Where On Earth ID' },
|
||||
maxTrends: { type: 'number', description: 'Maximum trends to return' },
|
||||
// Usage fields
|
||||
days: { type: 'number', description: 'Days of usage data' },
|
||||
},
|
||||
outputs: {
|
||||
// Write and Read operation outputs
|
||||
tweet: {
|
||||
type: 'json',
|
||||
description: 'Tweet data including contextAnnotations and publicMetrics',
|
||||
condition: { field: 'operation', value: ['x_write', 'x_read'] },
|
||||
// Create Tweet outputs
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Created tweet ID',
|
||||
condition: { field: 'operation', value: 'x_create_tweet' },
|
||||
},
|
||||
// Read operation outputs
|
||||
replies: {
|
||||
type: 'json',
|
||||
description: 'Tweet replies (when includeReplies is true)',
|
||||
condition: { field: 'operation', value: 'x_read' },
|
||||
text: {
|
||||
type: 'string',
|
||||
description: 'Text of the created tweet',
|
||||
condition: { field: 'operation', value: 'x_create_tweet' },
|
||||
},
|
||||
context: {
|
||||
type: 'json',
|
||||
description: 'Tweet context (parent and quoted tweets)',
|
||||
condition: { field: 'operation', value: 'x_read' },
|
||||
// Delete Tweet output
|
||||
deleted: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet was deleted',
|
||||
condition: { field: 'operation', value: 'x_delete_tweet' },
|
||||
},
|
||||
// Search operation outputs
|
||||
// Bookmark outputs
|
||||
bookmarked: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet is bookmarked',
|
||||
condition: { field: 'operation', value: ['x_create_bookmark', 'x_delete_bookmark'] },
|
||||
},
|
||||
// Hide Reply output
|
||||
hidden: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the reply is hidden',
|
||||
condition: { field: 'operation', value: 'x_hide_reply' },
|
||||
},
|
||||
// Like output
|
||||
liked: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet is liked',
|
||||
condition: { field: 'operation', value: 'x_manage_like' },
|
||||
},
|
||||
// Retweet output
|
||||
retweeted: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet is retweeted',
|
||||
condition: { field: 'operation', value: 'x_manage_retweet' },
|
||||
},
|
||||
// Follow output
|
||||
following: {
|
||||
type: 'boolean',
|
||||
description: 'Whether following the user',
|
||||
condition: { field: 'operation', value: 'x_manage_follow' },
|
||||
},
|
||||
pendingFollow: {
|
||||
type: 'boolean',
|
||||
description: 'Whether a follow request is pending',
|
||||
condition: { field: 'operation', value: 'x_manage_follow' },
|
||||
},
|
||||
// Block output
|
||||
blocking: {
|
||||
type: 'boolean',
|
||||
description: 'Whether blocking the user',
|
||||
condition: { field: 'operation', value: 'x_manage_block' },
|
||||
},
|
||||
// Mute output
|
||||
muting: {
|
||||
type: 'boolean',
|
||||
description: 'Whether muting the user',
|
||||
condition: { field: 'operation', value: 'x_manage_mute' },
|
||||
},
|
||||
// Tweet list outputs (shared by many operations)
|
||||
tweets: {
|
||||
type: 'json',
|
||||
description: 'Tweets data including contextAnnotations and publicMetrics',
|
||||
condition: { field: 'operation', value: 'x_search' },
|
||||
description: 'Array of tweets',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_search_tweets',
|
||||
'x_get_tweets_by_ids',
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
'x_get_liked_tweets',
|
||||
'x_get_bookmarks',
|
||||
'x_get_quote_tweets',
|
||||
],
|
||||
},
|
||||
},
|
||||
includes: {
|
||||
// User list outputs
|
||||
users: {
|
||||
type: 'json',
|
||||
description: 'Additional data (users, media, polls)',
|
||||
condition: { field: 'operation', value: 'x_search' },
|
||||
description: 'Array of users',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_search_users',
|
||||
'x_get_followers',
|
||||
'x_get_following',
|
||||
'x_get_blocking',
|
||||
'x_get_liking_users',
|
||||
'x_get_retweeted_by',
|
||||
],
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'json',
|
||||
description: 'Response metadata',
|
||||
condition: { field: 'operation', value: 'x_search' },
|
||||
},
|
||||
// User operation outputs
|
||||
// Single user output
|
||||
user: {
|
||||
type: 'json',
|
||||
description: 'User profile data',
|
||||
condition: { field: 'operation', value: 'x_user' },
|
||||
condition: { field: 'operation', value: 'x_get_me' },
|
||||
},
|
||||
recentTweets: {
|
||||
// Pagination metadata
|
||||
meta: {
|
||||
type: 'json',
|
||||
description: 'Recent tweets data',
|
||||
condition: { field: 'operation', value: 'x_user' },
|
||||
description: 'Pagination metadata (resultCount, nextToken)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'x_search_tweets',
|
||||
'x_get_user_tweets',
|
||||
'x_get_user_mentions',
|
||||
'x_get_user_timeline',
|
||||
'x_get_liked_tweets',
|
||||
'x_get_bookmarks',
|
||||
'x_get_quote_tweets',
|
||||
'x_get_liking_users',
|
||||
'x_get_retweeted_by',
|
||||
'x_search_users',
|
||||
'x_get_followers',
|
||||
'x_get_following',
|
||||
'x_get_blocking',
|
||||
],
|
||||
},
|
||||
},
|
||||
// Trends outputs
|
||||
trends: {
|
||||
type: 'json',
|
||||
description: 'Array of trending topics',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['x_get_trends_by_woeid', 'x_get_personalized_trends'],
|
||||
},
|
||||
},
|
||||
// Usage outputs
|
||||
capResetDay: {
|
||||
type: 'number',
|
||||
description: 'Day of month when usage cap resets',
|
||||
condition: { field: 'operation', value: 'x_get_usage' },
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'Project identifier',
|
||||
condition: { field: 'operation', value: 'x_get_usage' },
|
||||
},
|
||||
projectCap: {
|
||||
type: 'number',
|
||||
description: 'Monthly project usage cap',
|
||||
condition: { field: 'operation', value: 'x_get_usage' },
|
||||
},
|
||||
projectUsage: {
|
||||
type: 'number',
|
||||
description: 'Current project usage count',
|
||||
condition: { field: 'operation', value: 'x_get_usage' },
|
||||
},
|
||||
dailyProjectUsage: {
|
||||
type: 'json',
|
||||
description: 'Daily usage breakdown',
|
||||
condition: { field: 'operation', value: 'x_get_usage' },
|
||||
},
|
||||
dailyClientAppUsage: {
|
||||
type: 'json',
|
||||
description: 'Daily client app usage breakdown',
|
||||
condition: { field: 'operation', value: 'x_get_usage' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -310,7 +310,23 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
providerId: 'x',
|
||||
icon: xIcon,
|
||||
baseProviderIcon: xIcon,
|
||||
scopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
|
||||
scopes: [
|
||||
'tweet.read',
|
||||
'tweet.write',
|
||||
'tweet.moderate.write',
|
||||
'users.read',
|
||||
'follows.read',
|
||||
'follows.write',
|
||||
'bookmark.read',
|
||||
'bookmark.write',
|
||||
'like.read',
|
||||
'like.write',
|
||||
'block.read',
|
||||
'block.write',
|
||||
'mute.read',
|
||||
'mute.write',
|
||||
'offline.access',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultService: 'x',
|
||||
|
||||
@@ -2132,7 +2132,40 @@ import {
|
||||
wordpressUploadMediaTool,
|
||||
} from '@/tools/wordpress'
|
||||
import { workflowExecutorTool } from '@/tools/workflow'
|
||||
import { xReadTool, xSearchTool, xUserTool, xWriteTool } from '@/tools/x'
|
||||
import {
|
||||
xCreateBookmarkTool,
|
||||
xCreateTweetTool,
|
||||
xDeleteBookmarkTool,
|
||||
xDeleteTweetTool,
|
||||
xGetBlockingTool,
|
||||
xGetBookmarksTool,
|
||||
xGetFollowersTool,
|
||||
xGetFollowingTool,
|
||||
xGetLikedTweetsTool,
|
||||
xGetLikingUsersTool,
|
||||
xGetMeTool,
|
||||
xGetPersonalizedTrendsTool,
|
||||
xGetQuoteTweetsTool,
|
||||
xGetRetweetedByTool,
|
||||
xGetTrendsByWoeidTool,
|
||||
xGetTweetsByIdsTool,
|
||||
xGetUsageTool,
|
||||
xGetUserMentionsTool,
|
||||
xGetUserTimelineTool,
|
||||
xGetUserTweetsTool,
|
||||
xHideReplyTool,
|
||||
xManageBlockTool,
|
||||
xManageFollowTool,
|
||||
xManageLikeTool,
|
||||
xManageMuteTool,
|
||||
xManageRetweetTool,
|
||||
xReadTool,
|
||||
xSearchTool,
|
||||
xSearchTweetsTool,
|
||||
xSearchUsersTool,
|
||||
xUserTool,
|
||||
xWriteTool,
|
||||
} from '@/tools/x'
|
||||
import {
|
||||
youtubeChannelInfoTool,
|
||||
youtubeChannelPlaylistsTool,
|
||||
@@ -2665,6 +2698,34 @@ export const tools: Record<string, ToolConfig> = {
|
||||
x_read: xReadTool,
|
||||
x_search: xSearchTool,
|
||||
x_user: xUserTool,
|
||||
x_search_tweets: xSearchTweetsTool,
|
||||
x_get_user_tweets: xGetUserTweetsTool,
|
||||
x_get_user_mentions: xGetUserMentionsTool,
|
||||
x_get_user_timeline: xGetUserTimelineTool,
|
||||
x_get_tweets_by_ids: xGetTweetsByIdsTool,
|
||||
x_get_bookmarks: xGetBookmarksTool,
|
||||
x_create_bookmark: xCreateBookmarkTool,
|
||||
x_delete_bookmark: xDeleteBookmarkTool,
|
||||
x_create_tweet: xCreateTweetTool,
|
||||
x_delete_tweet: xDeleteTweetTool,
|
||||
x_get_me: xGetMeTool,
|
||||
x_search_users: xSearchUsersTool,
|
||||
x_get_followers: xGetFollowersTool,
|
||||
x_get_following: xGetFollowingTool,
|
||||
x_manage_follow: xManageFollowTool,
|
||||
x_get_blocking: xGetBlockingTool,
|
||||
x_manage_block: xManageBlockTool,
|
||||
x_get_liked_tweets: xGetLikedTweetsTool,
|
||||
x_get_liking_users: xGetLikingUsersTool,
|
||||
x_manage_like: xManageLikeTool,
|
||||
x_manage_retweet: xManageRetweetTool,
|
||||
x_get_retweeted_by: xGetRetweetedByTool,
|
||||
x_get_quote_tweets: xGetQuoteTweetsTool,
|
||||
x_get_trends_by_woeid: xGetTrendsByWoeidTool,
|
||||
x_get_personalized_trends: xGetPersonalizedTrendsTool,
|
||||
x_get_usage: xGetUsageTool,
|
||||
x_hide_reply: xHideReplyTool,
|
||||
x_manage_mute: xManageMuteTool,
|
||||
pinecone_fetch: pineconeFetchTool,
|
||||
pinecone_generate_embeddings: pineconeGenerateEmbeddingsTool,
|
||||
pinecone_search_text: pineconeSearchTextTool,
|
||||
|
||||
79
apps/sim/tools/x/create_bookmark.ts
Normal file
79
apps/sim/tools/x/create_bookmark.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XCreateBookmarkParams, XCreateBookmarkResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XCreateBookmarkTool')
|
||||
|
||||
export const xCreateBookmarkTool: ToolConfig<XCreateBookmarkParams, XCreateBookmarkResponse> = {
|
||||
id: 'x_create_bookmark',
|
||||
name: 'X Create Bookmark',
|
||||
description: 'Bookmark a tweet for the authenticated user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The tweet ID to bookmark',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.x.com/2/users/${params.userId.trim()}/bookmarks`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
tweet_id: params.tweetId.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Create Bookmark API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'Failed to bookmark tweet',
|
||||
output: {
|
||||
bookmarked: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
bookmarked: data.data.bookmarked ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
bookmarked: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet was successfully bookmarked',
|
||||
},
|
||||
},
|
||||
}
|
||||
129
apps/sim/tools/x/create_tweet.ts
Normal file
129
apps/sim/tools/x/create_tweet.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XCreateTweetParams, XCreateTweetResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XCreateTweetTool')
|
||||
|
||||
export const xCreateTweetTool: ToolConfig<XCreateTweetParams, XCreateTweetResponse> = {
|
||||
id: 'x_create_tweet',
|
||||
name: 'X Create Tweet',
|
||||
description: 'Create a new tweet, reply, or quote tweet on X',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The text content of the tweet (max 280 characters)',
|
||||
},
|
||||
replyToTweetId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Tweet ID to reply to',
|
||||
},
|
||||
quoteTweetId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Tweet ID to quote',
|
||||
},
|
||||
mediaIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated media IDs to attach (up to 4)',
|
||||
},
|
||||
replySettings: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Who can reply: "mentionedUsers", "following", "subscribers", or "verified"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.x.com/2/tweets',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
text: params.text,
|
||||
}
|
||||
|
||||
if (params.replyToTweetId) {
|
||||
body.reply = { in_reply_to_tweet_id: params.replyToTweetId.trim() }
|
||||
}
|
||||
|
||||
if (params.quoteTweetId) {
|
||||
body.quote_tweet_id = params.quoteTweetId.trim()
|
||||
}
|
||||
|
||||
if (params.mediaIds) {
|
||||
const ids = params.mediaIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean)
|
||||
if (ids.length > 0) {
|
||||
body.media = { media_ids: ids }
|
||||
}
|
||||
}
|
||||
|
||||
if (params.replySettings) {
|
||||
body.reply_settings = params.replySettings
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Create Tweet API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'Failed to create tweet',
|
||||
output: {
|
||||
id: '',
|
||||
text: '',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.data.id ?? '',
|
||||
text: data.data.text ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'The ID of the created tweet',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
description: 'The text of the created tweet',
|
||||
},
|
||||
},
|
||||
}
|
||||
77
apps/sim/tools/x/delete_bookmark.ts
Normal file
77
apps/sim/tools/x/delete_bookmark.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XDeleteBookmarkParams, XDeleteBookmarkResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XDeleteBookmarkTool')
|
||||
|
||||
export const xDeleteBookmarkTool: ToolConfig<XDeleteBookmarkParams, XDeleteBookmarkResponse> = {
|
||||
id: 'x_delete_bookmark',
|
||||
name: 'X Delete Bookmark',
|
||||
description: "Remove a tweet from the authenticated user's bookmarks",
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The tweet ID to remove from bookmarks',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.x.com/2/users/${params.userId.trim()}/bookmarks/${params.tweetId.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Delete Bookmark API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'Failed to remove bookmark',
|
||||
output: {
|
||||
bookmarked: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
bookmarked: data.data.bookmarked ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
bookmarked: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet is still bookmarked (should be false after deletion)',
|
||||
},
|
||||
},
|
||||
}
|
||||
70
apps/sim/tools/x/delete_tweet.ts
Normal file
70
apps/sim/tools/x/delete_tweet.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XDeleteTweetParams, XDeleteTweetResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XDeleteTweetTool')
|
||||
|
||||
export const xDeleteTweetTool: ToolConfig<XDeleteTweetParams, XDeleteTweetResponse> = {
|
||||
id: 'x_delete_tweet',
|
||||
name: 'X Delete Tweet',
|
||||
description: 'Delete a tweet authored by the authenticated user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the tweet to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.x.com/2/tweets/${params.tweetId.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Delete Tweet API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'Failed to delete tweet',
|
||||
output: {
|
||||
deleted: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: data.data.deleted ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet was successfully deleted',
|
||||
},
|
||||
},
|
||||
}
|
||||
128
apps/sim/tools/x/get_blocking.ts
Normal file
128
apps/sim/tools/x/get_blocking.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetBlockingParams, XUserListResponse } from '@/tools/x/types'
|
||||
import { transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetBlockingTool')
|
||||
|
||||
export const xGetBlockingTool: ToolConfig<XGetBlockingParams, XUserListResponse> = {
|
||||
id: 'x_get_blocking',
|
||||
name: 'X Get Blocking',
|
||||
description: 'Get the list of users blocked by the authenticated user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-1000)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(1, Math.min(1000, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/blocking?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Blocking API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
users: [],
|
||||
meta: { resultCount: 0, nextToken: null },
|
||||
},
|
||||
error: data.errors?.[0]?.detail ?? 'No blocked users found or invalid response',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users: data.data.map(transformUser),
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? data.data.length,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of blocked user profiles',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
183
apps/sim/tools/x/get_bookmarks.ts
Normal file
183
apps/sim/tools/x/get_bookmarks.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetBookmarksParams, XTweetListResponse } from '@/tools/x/types'
|
||||
import { transformTweet, transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetBookmarksTool')
|
||||
|
||||
export const xGetBookmarksTool: ToolConfig<XGetBookmarksParams, XTweetListResponse> = {
|
||||
id: 'x_get_bookmarks',
|
||||
name: 'X Get Bookmarks',
|
||||
description: 'Get bookmarked tweets for the authenticated user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-100)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page of results',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
|
||||
'tweet.fields':
|
||||
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
|
||||
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(1, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/bookmarks?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Bookmarks API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No bookmarks found or invalid response',
|
||||
output: {
|
||||
tweets: [],
|
||||
meta: {
|
||||
resultCount: 0,
|
||||
newestId: null,
|
||||
oldestId: null,
|
||||
nextToken: null,
|
||||
previousToken: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tweets: data.data.map(transformTweet),
|
||||
includes: {
|
||||
users: data.includes?.users?.map(transformUser) ?? [],
|
||||
},
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? 0,
|
||||
newestId: data.meta?.newest_id ?? null,
|
||||
oldestId: data.meta?.oldest_id ?? null,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
previousToken: data.meta?.previous_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tweets: {
|
||||
type: 'array',
|
||||
description: 'Array of bookmarked tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Tweet ID' },
|
||||
text: { type: 'string', description: 'Tweet text content' },
|
||||
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
|
||||
authorId: { type: 'string', description: 'Author user ID' },
|
||||
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
|
||||
inReplyToUserId: {
|
||||
type: 'string',
|
||||
description: 'User ID being replied to',
|
||||
optional: true,
|
||||
},
|
||||
publicMetrics: {
|
||||
type: 'object',
|
||||
description: 'Engagement metrics',
|
||||
optional: true,
|
||||
properties: {
|
||||
retweetCount: { type: 'number', description: 'Number of retweets' },
|
||||
replyCount: { type: 'number', description: 'Number of replies' },
|
||||
likeCount: { type: 'number', description: 'Number of likes' },
|
||||
quoteCount: { type: 'number', description: 'Number of quotes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
includes: {
|
||||
type: 'object',
|
||||
description: 'Additional data including user profiles',
|
||||
optional: true,
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects referenced in tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
|
||||
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
previousToken: { type: 'string', description: 'Token for previous page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
128
apps/sim/tools/x/get_followers.ts
Normal file
128
apps/sim/tools/x/get_followers.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetFollowersParams, XUserListResponse } from '@/tools/x/types'
|
||||
import { transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetFollowersTool')
|
||||
|
||||
export const xGetFollowersTool: ToolConfig<XGetFollowersParams, XUserListResponse> = {
|
||||
id: 'x_get_followers',
|
||||
name: 'X Get Followers',
|
||||
description: 'Get the list of followers for a user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The user ID whose followers to retrieve',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-1000, default 100)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics,location',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(1, Math.min(1000, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/followers?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Followers API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No followers found or invalid response',
|
||||
output: {
|
||||
users: [],
|
||||
meta: { resultCount: 0, nextToken: null },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users: data.data.map(transformUser),
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? data.data.length,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of follower user profiles',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
128
apps/sim/tools/x/get_following.ts
Normal file
128
apps/sim/tools/x/get_following.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetFollowingParams, XUserListResponse } from '@/tools/x/types'
|
||||
import { transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetFollowingTool')
|
||||
|
||||
export const xGetFollowingTool: ToolConfig<XGetFollowingParams, XUserListResponse> = {
|
||||
id: 'x_get_following',
|
||||
name: 'X Get Following',
|
||||
description: 'Get the list of users that a user is following',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The user ID whose following list to retrieve',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-1000, default 100)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics,location',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(1, Math.min(1000, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/following?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Following API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No following data found or invalid response',
|
||||
output: {
|
||||
users: [],
|
||||
meta: { resultCount: 0, nextToken: null },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users: data.data.map(transformUser),
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? data.data.length,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of users being followed',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
131
apps/sim/tools/x/get_liked_tweets.ts
Normal file
131
apps/sim/tools/x/get_liked_tweets.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetLikedTweetsParams, XTweetListResponse } from '@/tools/x/types'
|
||||
import { transformTweet, transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetLikedTweetsTool')
|
||||
|
||||
export const xGetLikedTweetsTool: ToolConfig<XGetLikedTweetsParams, XTweetListResponse> = {
|
||||
id: 'x_get_liked_tweets',
|
||||
name: 'X Get Liked Tweets',
|
||||
description: 'Get tweets liked by a specific user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The user ID whose liked tweets to retrieve',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (5-100)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
expansions: 'author_id,attachments.media_keys',
|
||||
'tweet.fields': 'created_at,conversation_id,public_metrics,context_annotations',
|
||||
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(5, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/liked_tweets?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Liked Tweets API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
tweets: [],
|
||||
meta: {
|
||||
resultCount: 0,
|
||||
newestId: null,
|
||||
oldestId: null,
|
||||
nextToken: null,
|
||||
previousToken: null,
|
||||
},
|
||||
},
|
||||
error: data.errors?.[0]?.detail ?? 'No liked tweets found or invalid response',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tweets: data.data.map(transformTweet),
|
||||
includes: {
|
||||
users: data.includes?.users?.map(transformUser) ?? [],
|
||||
},
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? 0,
|
||||
newestId: data.meta?.newest_id ?? null,
|
||||
oldestId: data.meta?.oldest_id ?? null,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
previousToken: data.meta?.previous_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tweets: {
|
||||
type: 'array',
|
||||
description: 'Array of liked tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Tweet ID' },
|
||||
text: { type: 'string', description: 'Tweet content' },
|
||||
createdAt: { type: 'string', description: 'Creation timestamp' },
|
||||
authorId: { type: 'string', description: 'Author user ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
128
apps/sim/tools/x/get_liking_users.ts
Normal file
128
apps/sim/tools/x/get_liking_users.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetLikingUsersParams, XUserListResponse } from '@/tools/x/types'
|
||||
import { transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetLikingUsersTool')
|
||||
|
||||
export const xGetLikingUsersTool: ToolConfig<XGetLikingUsersParams, XUserListResponse> = {
|
||||
id: 'x_get_liking_users',
|
||||
name: 'X Get Liking Users',
|
||||
description: 'Get the list of users who liked a specific tweet',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The tweet ID to get liking users for',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-100, default 100)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(1, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
|
||||
return `https://api.x.com/2/tweets/${params.tweetId.trim()}/liking_users?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Liking Users API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
users: [],
|
||||
meta: { resultCount: 0, nextToken: null },
|
||||
},
|
||||
error: data.errors?.[0]?.detail ?? 'No liking users found or invalid response',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users: data.data.map(transformUser),
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? data.data.length,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of users who liked the tweet',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
88
apps/sim/tools/x/get_me.ts
Normal file
88
apps/sim/tools/x/get_me.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetMeParams, XGetMeResponse } from '@/tools/x/types'
|
||||
import { transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetMeTool')
|
||||
|
||||
export const xGetMeTool: ToolConfig<XGetMeParams, XGetMeResponse> = {
|
||||
id: 'x_get_me',
|
||||
name: 'X Get Me',
|
||||
description: "Get the authenticated user's profile information",
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => {
|
||||
const queryParams = new URLSearchParams({
|
||||
'user.fields':
|
||||
'created_at,description,profile_image_url,verified,public_metrics,location,url',
|
||||
})
|
||||
return `https://api.x.com/2/users/me?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Get Me API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'Failed to get authenticated user info',
|
||||
output: {
|
||||
user: {} as XGetMeResponse['output']['user'],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
user: transformUser(data.data),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'Authenticated user profile',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
89
apps/sim/tools/x/get_personalized_trends.ts
Normal file
89
apps/sim/tools/x/get_personalized_trends.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetPersonalizedTrendsParams, XPersonalizedTrendListResponse } from '@/tools/x/types'
|
||||
import { transformPersonalizedTrend } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetPersonalizedTrendsTool')
|
||||
|
||||
export const xGetPersonalizedTrendsTool: ToolConfig<
|
||||
XGetPersonalizedTrendsParams,
|
||||
XPersonalizedTrendListResponse
|
||||
> = {
|
||||
id: 'x_get_personalized_trends',
|
||||
name: 'X Get Personalized Trends',
|
||||
description: 'Get personalized trending topics for the authenticated user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.x.com/2/users/personalized_trends?personalized_trend.fields=category,post_count,trend_name,trending_since',
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Personalized Trends API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No personalized trends found or invalid response',
|
||||
output: {
|
||||
trends: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
trends: data.data.map(transformPersonalizedTrend),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
trends: {
|
||||
type: 'array',
|
||||
description: 'Array of personalized trending topics',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
trendName: { type: 'string', description: 'Name of the trending topic' },
|
||||
postCount: {
|
||||
type: 'number',
|
||||
description: 'Number of posts for this trend',
|
||||
optional: true,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
description: 'Category of the trend',
|
||||
optional: true,
|
||||
},
|
||||
trendingSince: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 timestamp of when the topic started trending',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
152
apps/sim/tools/x/get_quote_tweets.ts
Normal file
152
apps/sim/tools/x/get_quote_tweets.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetQuoteTweetsParams, XTweetListResponse } from '@/tools/x/types'
|
||||
import { transformTweet, transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetQuoteTweetsTool')
|
||||
|
||||
export const xGetQuoteTweetsTool: ToolConfig<XGetQuoteTweetsParams, XTweetListResponse> = {
|
||||
id: 'x_get_quote_tweets',
|
||||
name: 'X Get Quote Tweets',
|
||||
description: 'Get tweets that quote a specific tweet',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The tweet ID to get quote tweets for',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (10-100, default 10)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
expansions: 'author_id,attachments.media_keys',
|
||||
'tweet.fields': 'created_at,conversation_id,public_metrics,context_annotations',
|
||||
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(10, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
|
||||
return `https://api.x.com/2/tweets/${params.tweetId.trim()}/quote_tweets?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Quote Tweets API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No quote tweets found or invalid response',
|
||||
output: {
|
||||
tweets: [],
|
||||
meta: {
|
||||
resultCount: 0,
|
||||
newestId: null,
|
||||
oldestId: null,
|
||||
nextToken: null,
|
||||
previousToken: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tweets: data.data.map(transformTweet),
|
||||
includes: {
|
||||
users: data.includes?.users?.map(transformUser) ?? [],
|
||||
},
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? 0,
|
||||
newestId: data.meta?.newest_id ?? null,
|
||||
oldestId: data.meta?.oldest_id ?? null,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
previousToken: data.meta?.previous_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tweets: {
|
||||
type: 'array',
|
||||
description: 'Array of quote tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Tweet ID' },
|
||||
text: { type: 'string', description: 'Tweet text content' },
|
||||
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
|
||||
authorId: { type: 'string', description: 'Author user ID' },
|
||||
conversationId: {
|
||||
type: 'string',
|
||||
description: 'Conversation thread ID',
|
||||
optional: true,
|
||||
},
|
||||
inReplyToUserId: {
|
||||
type: 'string',
|
||||
description: 'User ID being replied to',
|
||||
optional: true,
|
||||
},
|
||||
publicMetrics: {
|
||||
type: 'object',
|
||||
description: 'Engagement metrics',
|
||||
optional: true,
|
||||
properties: {
|
||||
retweetCount: { type: 'number', description: 'Number of retweets' },
|
||||
replyCount: { type: 'number', description: 'Number of replies' },
|
||||
likeCount: { type: 'number', description: 'Number of likes' },
|
||||
quoteCount: { type: 'number', description: 'Number of quotes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
128
apps/sim/tools/x/get_retweeted_by.ts
Normal file
128
apps/sim/tools/x/get_retweeted_by.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetRetweetedByParams, XUserListResponse } from '@/tools/x/types'
|
||||
import { transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetRetweetedByTool')
|
||||
|
||||
export const xGetRetweetedByTool: ToolConfig<XGetRetweetedByParams, XUserListResponse> = {
|
||||
id: 'x_get_retweeted_by',
|
||||
name: 'X Get Retweeted By',
|
||||
description: 'Get the list of users who retweeted a specific tweet',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The tweet ID to get retweeters for',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-100, default 100)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(1, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
|
||||
return `https://api.x.com/2/tweets/${params.tweetId.trim()}/retweeted_by?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Retweeted By API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No retweeters found or invalid response',
|
||||
output: {
|
||||
users: [],
|
||||
meta: { resultCount: 0, nextToken: null },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users: data.data.map(transformUser),
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? data.data.length,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of users who retweeted the tweet',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
100
apps/sim/tools/x/get_trends_by_woeid.ts
Normal file
100
apps/sim/tools/x/get_trends_by_woeid.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetTrendsByWoeidParams, XTrendListResponse } from '@/tools/x/types'
|
||||
import { transformTrend } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetTrendsByWoeidTool')
|
||||
|
||||
export const xGetTrendsByWoeidTool: ToolConfig<XGetTrendsByWoeidParams, XTrendListResponse> = {
|
||||
id: 'x_get_trends_by_woeid',
|
||||
name: 'X Get Trends By WOEID',
|
||||
description:
|
||||
'Get trending topics for a specific location by WOEID (e.g., 1 for worldwide, 23424977 for US)',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
woeid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Yahoo Where On Earth ID (e.g., "1" for worldwide, "23424977" for US, "23424975" for UK)',
|
||||
},
|
||||
maxTrends: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of trends to return (1-50, default 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
'trend.fields': 'trend_name,tweet_count',
|
||||
})
|
||||
|
||||
if (params.maxTrends) {
|
||||
queryParams.append('max_trends', Number(params.maxTrends).toString())
|
||||
}
|
||||
|
||||
return `https://api.x.com/2/trends/by/woeid/${params.woeid.trim()}?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Trends API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No trends found or invalid response',
|
||||
output: {
|
||||
trends: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
trends: data.data.map(transformTrend),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
trends: {
|
||||
type: 'array',
|
||||
description: 'Array of trending topics',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
trendName: { type: 'string', description: 'Name of the trending topic' },
|
||||
tweetCount: {
|
||||
type: 'number',
|
||||
description: 'Number of tweets for this trend',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
141
apps/sim/tools/x/get_tweets_by_ids.ts
Normal file
141
apps/sim/tools/x/get_tweets_by_ids.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetTweetsByIdsParams, XGetTweetsByIdsResponse } from '@/tools/x/types'
|
||||
import { transformTweet, transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetTweetsByIdsTool')
|
||||
|
||||
export const xGetTweetsByIdsTool: ToolConfig<XGetTweetsByIdsParams, XGetTweetsByIdsResponse> = {
|
||||
id: 'x_get_tweets_by_ids',
|
||||
name: 'X Get Tweets By IDs',
|
||||
description: 'Look up multiple tweets by their IDs (up to 100)',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
ids: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tweet IDs (up to 100)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
ids: params.ids.trim(),
|
||||
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
|
||||
'tweet.fields':
|
||||
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
|
||||
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
return `https://api.x.com/2/tweets?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get Tweets By IDs API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No tweets found or invalid response',
|
||||
output: {
|
||||
tweets: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tweets: data.data.map(transformTweet),
|
||||
includes: {
|
||||
users: data.includes?.users?.map(transformUser) ?? [],
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tweets: {
|
||||
type: 'array',
|
||||
description: 'Array of tweets matching the provided IDs',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Tweet ID' },
|
||||
text: { type: 'string', description: 'Tweet text content' },
|
||||
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
|
||||
authorId: { type: 'string', description: 'Author user ID' },
|
||||
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
|
||||
inReplyToUserId: {
|
||||
type: 'string',
|
||||
description: 'User ID being replied to',
|
||||
optional: true,
|
||||
},
|
||||
publicMetrics: {
|
||||
type: 'object',
|
||||
description: 'Engagement metrics',
|
||||
optional: true,
|
||||
properties: {
|
||||
retweetCount: { type: 'number', description: 'Number of retweets' },
|
||||
replyCount: { type: 'number', description: 'Number of replies' },
|
||||
likeCount: { type: 'number', description: 'Number of likes' },
|
||||
quoteCount: { type: 'number', description: 'Number of quotes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
includes: {
|
||||
type: 'object',
|
||||
description: 'Additional data including user profiles',
|
||||
optional: true,
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects referenced in tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
151
apps/sim/tools/x/get_usage.ts
Normal file
151
apps/sim/tools/x/get_usage.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetUsageParams, XGetUsageResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetUsageTool')
|
||||
|
||||
export const xGetUsageTool: ToolConfig<XGetUsageParams, XGetUsageResponse> = {
|
||||
id: 'x_get_usage',
|
||||
name: 'X Get Usage',
|
||||
description: 'Get the API usage data for your X project',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
days: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of days of usage data to return (1-90, default 7)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
'usage.fields':
|
||||
'cap_reset_day,daily_client_app_usage,daily_project_usage,project_cap,project_id,project_usage',
|
||||
})
|
||||
|
||||
if (params.days) {
|
||||
queryParams.append('days', Number(params.days).toString())
|
||||
}
|
||||
|
||||
return `https://api.x.com/2/usage/tweets?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Get Usage API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'Failed to get usage data',
|
||||
output: {
|
||||
capResetDay: null,
|
||||
projectId: '',
|
||||
projectCap: null,
|
||||
projectUsage: null,
|
||||
dailyProjectUsage: [],
|
||||
dailyClientAppUsage: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
capResetDay: data.data.cap_reset_day ?? null,
|
||||
projectId: String(data.data.project_id ?? ''),
|
||||
projectCap: data.data.project_cap ?? null,
|
||||
projectUsage: data.data.project_usage ?? null,
|
||||
dailyProjectUsage: (data.data.daily_project_usage?.usage ?? []).map(
|
||||
(u: { date: string; usage: number }) => ({
|
||||
date: u.date,
|
||||
usage: u.usage ?? 0,
|
||||
})
|
||||
),
|
||||
dailyClientAppUsage: (data.data.daily_client_app_usage ?? []).map(
|
||||
(app: { client_app_id: string; usage: { date: string; usage: number }[] }) => ({
|
||||
clientAppId: String(app.client_app_id ?? ''),
|
||||
usage: (app.usage ?? []).map((u: { date: string; usage: number }) => ({
|
||||
date: u.date,
|
||||
usage: u.usage ?? 0,
|
||||
})),
|
||||
})
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
capResetDay: {
|
||||
type: 'number',
|
||||
description: 'Day of month when usage cap resets',
|
||||
optional: true,
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'The project ID',
|
||||
},
|
||||
projectCap: {
|
||||
type: 'number',
|
||||
description: 'The project tweet consumption cap',
|
||||
optional: true,
|
||||
},
|
||||
projectUsage: {
|
||||
type: 'number',
|
||||
description: 'Total tweets consumed in current period',
|
||||
optional: true,
|
||||
},
|
||||
dailyProjectUsage: {
|
||||
type: 'array',
|
||||
description: 'Daily project usage breakdown',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: { type: 'string', description: 'Usage date in ISO 8601 format' },
|
||||
usage: { type: 'number', description: 'Number of tweets consumed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
dailyClientAppUsage: {
|
||||
type: 'array',
|
||||
description: 'Daily per-app usage breakdown',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
clientAppId: { type: 'string', description: 'Client application ID' },
|
||||
usage: {
|
||||
type: 'array',
|
||||
description: 'Daily usage entries for this app',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: { type: 'string', description: 'Usage date in ISO 8601 format' },
|
||||
usage: { type: 'number', description: 'Number of tweets consumed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
211
apps/sim/tools/x/get_user_mentions.ts
Normal file
211
apps/sim/tools/x/get_user_mentions.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetUserMentionsParams, XTweetListResponse } from '@/tools/x/types'
|
||||
import { transformTweet, transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetUserMentionsTool')
|
||||
|
||||
export const xGetUserMentionsTool: ToolConfig<XGetUserMentionsParams, XTweetListResponse> = {
|
||||
id: 'x_get_user_mentions',
|
||||
name: 'X Get User Mentions',
|
||||
description: 'Get tweets that mention a specific user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The user ID whose mentions to retrieve',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (5-100, default 10)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page of results',
|
||||
},
|
||||
startTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Oldest UTC timestamp in ISO 8601 format',
|
||||
},
|
||||
endTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Newest UTC timestamp in ISO 8601 format',
|
||||
},
|
||||
sinceId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Returns tweets with ID greater than this',
|
||||
},
|
||||
untilId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Returns tweets with ID less than this',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
|
||||
'tweet.fields':
|
||||
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
|
||||
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(5, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
if (params.startTime) queryParams.append('start_time', params.startTime)
|
||||
if (params.endTime) queryParams.append('end_time', params.endTime)
|
||||
if (params.sinceId) queryParams.append('since_id', params.sinceId)
|
||||
if (params.untilId) queryParams.append('until_id', params.untilId)
|
||||
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/mentions?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get User Mentions API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No mentions found or invalid response',
|
||||
output: {
|
||||
tweets: [],
|
||||
meta: {
|
||||
resultCount: 0,
|
||||
newestId: null,
|
||||
oldestId: null,
|
||||
nextToken: null,
|
||||
previousToken: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tweets: data.data.map(transformTweet),
|
||||
includes: {
|
||||
users: data.includes?.users?.map(transformUser) ?? [],
|
||||
},
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? 0,
|
||||
newestId: data.meta?.newest_id ?? null,
|
||||
oldestId: data.meta?.oldest_id ?? null,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
previousToken: data.meta?.previous_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tweets: {
|
||||
type: 'array',
|
||||
description: 'Array of tweets mentioning the user',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Tweet ID' },
|
||||
text: { type: 'string', description: 'Tweet text content' },
|
||||
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
|
||||
authorId: { type: 'string', description: 'Author user ID' },
|
||||
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
|
||||
inReplyToUserId: {
|
||||
type: 'string',
|
||||
description: 'User ID being replied to',
|
||||
optional: true,
|
||||
},
|
||||
publicMetrics: {
|
||||
type: 'object',
|
||||
description: 'Engagement metrics',
|
||||
optional: true,
|
||||
properties: {
|
||||
retweetCount: { type: 'number', description: 'Number of retweets' },
|
||||
replyCount: { type: 'number', description: 'Number of replies' },
|
||||
likeCount: { type: 'number', description: 'Number of likes' },
|
||||
quoteCount: { type: 'number', description: 'Number of quotes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
includes: {
|
||||
type: 'object',
|
||||
description: 'Additional data including user profiles',
|
||||
optional: true,
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects referenced in tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
|
||||
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
previousToken: { type: 'string', description: 'Token for previous page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
218
apps/sim/tools/x/get_user_timeline.ts
Normal file
218
apps/sim/tools/x/get_user_timeline.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetUserTimelineParams, XTweetListResponse } from '@/tools/x/types'
|
||||
import { transformTweet, transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetUserTimelineTool')
|
||||
|
||||
export const xGetUserTimelineTool: ToolConfig<XGetUserTimelineParams, XTweetListResponse> = {
|
||||
id: 'x_get_user_timeline',
|
||||
name: 'X Get User Timeline',
|
||||
description: 'Get the reverse chronological home timeline for the authenticated user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-100, default 10)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page of results',
|
||||
},
|
||||
startTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Oldest UTC timestamp in ISO 8601 format',
|
||||
},
|
||||
endTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Newest UTC timestamp in ISO 8601 format',
|
||||
},
|
||||
sinceId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Returns tweets with ID greater than this',
|
||||
},
|
||||
untilId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Returns tweets with ID less than this',
|
||||
},
|
||||
exclude: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated types to exclude: "retweets", "replies"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
|
||||
'tweet.fields':
|
||||
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
|
||||
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(1, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
if (params.startTime) queryParams.append('start_time', params.startTime)
|
||||
if (params.endTime) queryParams.append('end_time', params.endTime)
|
||||
if (params.sinceId) queryParams.append('since_id', params.sinceId)
|
||||
if (params.untilId) queryParams.append('until_id', params.untilId)
|
||||
if (params.exclude) queryParams.append('exclude', params.exclude)
|
||||
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/timelines/reverse_chronological?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get User Timeline API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No timeline data found or invalid response',
|
||||
output: {
|
||||
tweets: [],
|
||||
meta: {
|
||||
resultCount: 0,
|
||||
newestId: null,
|
||||
oldestId: null,
|
||||
nextToken: null,
|
||||
previousToken: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tweets: data.data.map(transformTweet),
|
||||
includes: {
|
||||
users: data.includes?.users?.map(transformUser) ?? [],
|
||||
},
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? 0,
|
||||
newestId: data.meta?.newest_id ?? null,
|
||||
oldestId: data.meta?.oldest_id ?? null,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
previousToken: data.meta?.previous_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tweets: {
|
||||
type: 'array',
|
||||
description: 'Array of timeline tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Tweet ID' },
|
||||
text: { type: 'string', description: 'Tweet text content' },
|
||||
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
|
||||
authorId: { type: 'string', description: 'Author user ID' },
|
||||
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
|
||||
inReplyToUserId: {
|
||||
type: 'string',
|
||||
description: 'User ID being replied to',
|
||||
optional: true,
|
||||
},
|
||||
publicMetrics: {
|
||||
type: 'object',
|
||||
description: 'Engagement metrics',
|
||||
optional: true,
|
||||
properties: {
|
||||
retweetCount: { type: 'number', description: 'Number of retweets' },
|
||||
replyCount: { type: 'number', description: 'Number of replies' },
|
||||
likeCount: { type: 'number', description: 'Number of likes' },
|
||||
quoteCount: { type: 'number', description: 'Number of quotes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
includes: {
|
||||
type: 'object',
|
||||
description: 'Additional data including user profiles',
|
||||
optional: true,
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects referenced in tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
|
||||
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
previousToken: { type: 'string', description: 'Token for previous page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
218
apps/sim/tools/x/get_user_tweets.ts
Normal file
218
apps/sim/tools/x/get_user_tweets.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XGetUserTweetsParams, XTweetListResponse } from '@/tools/x/types'
|
||||
import { transformTweet, transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XGetUserTweetsTool')
|
||||
|
||||
export const xGetUserTweetsTool: ToolConfig<XGetUserTweetsParams, XTweetListResponse> = {
|
||||
id: 'x_get_user_tweets',
|
||||
name: 'X Get User Tweets',
|
||||
description: 'Get tweets authored by a specific user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The user ID whose tweets to retrieve',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (5-100, default 10)',
|
||||
},
|
||||
paginationToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page of results',
|
||||
},
|
||||
startTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Oldest UTC timestamp in ISO 8601 format',
|
||||
},
|
||||
endTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Newest UTC timestamp in ISO 8601 format',
|
||||
},
|
||||
sinceId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Returns tweets with ID greater than this',
|
||||
},
|
||||
untilId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Returns tweets with ID less than this',
|
||||
},
|
||||
exclude: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated types to exclude: "retweets", "replies"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
|
||||
'tweet.fields':
|
||||
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
|
||||
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(5, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.paginationToken) queryParams.append('pagination_token', params.paginationToken)
|
||||
if (params.startTime) queryParams.append('start_time', params.startTime)
|
||||
if (params.endTime) queryParams.append('end_time', params.endTime)
|
||||
if (params.sinceId) queryParams.append('since_id', params.sinceId)
|
||||
if (params.untilId) queryParams.append('until_id', params.untilId)
|
||||
if (params.exclude) queryParams.append('exclude', params.exclude)
|
||||
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/tweets?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Get User Tweets API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No tweets found or invalid response',
|
||||
output: {
|
||||
tweets: [],
|
||||
meta: {
|
||||
resultCount: 0,
|
||||
newestId: null,
|
||||
oldestId: null,
|
||||
nextToken: null,
|
||||
previousToken: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tweets: data.data.map(transformTweet),
|
||||
includes: {
|
||||
users: data.includes?.users?.map(transformUser) ?? [],
|
||||
},
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? 0,
|
||||
newestId: data.meta?.newest_id ?? null,
|
||||
oldestId: data.meta?.oldest_id ?? null,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
previousToken: data.meta?.previous_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tweets: {
|
||||
type: 'array',
|
||||
description: 'Array of tweets by the user',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Tweet ID' },
|
||||
text: { type: 'string', description: 'Tweet text content' },
|
||||
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
|
||||
authorId: { type: 'string', description: 'Author user ID' },
|
||||
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
|
||||
inReplyToUserId: {
|
||||
type: 'string',
|
||||
description: 'User ID being replied to',
|
||||
optional: true,
|
||||
},
|
||||
publicMetrics: {
|
||||
type: 'object',
|
||||
description: 'Engagement metrics',
|
||||
optional: true,
|
||||
properties: {
|
||||
retweetCount: { type: 'number', description: 'Number of retweets' },
|
||||
replyCount: { type: 'number', description: 'Number of replies' },
|
||||
likeCount: { type: 'number', description: 'Number of likes' },
|
||||
quoteCount: { type: 'number', description: 'Number of quotes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
includes: {
|
||||
type: 'object',
|
||||
description: 'Additional data including user profiles',
|
||||
optional: true,
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects referenced in tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Pagination metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
|
||||
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
|
||||
nextToken: { type: 'string', description: 'Token for next page', optional: true },
|
||||
previousToken: { type: 'string', description: 'Token for previous page', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
79
apps/sim/tools/x/hide_reply.ts
Normal file
79
apps/sim/tools/x/hide_reply.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XHideReplyParams, XHideReplyResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XHideReplyTool')
|
||||
|
||||
export const xHideReplyTool: ToolConfig<XHideReplyParams, XHideReplyResponse> = {
|
||||
id: 'x_hide_reply',
|
||||
name: 'X Hide Reply',
|
||||
description: 'Hide or unhide a reply to a tweet authored by the authenticated user',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The reply tweet ID to hide or unhide',
|
||||
},
|
||||
hidden: {
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Set to true to hide the reply, false to unhide',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.x.com/2/tweets/${params.tweetId.trim()}/hidden`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
hidden: params.hidden,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Hide Reply API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'Failed to hide/unhide reply',
|
||||
output: {
|
||||
hidden: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
hidden: data.data?.hidden ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
hidden: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the reply is now hidden',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -7,3 +7,33 @@ export { xReadTool }
|
||||
export { xWriteTool }
|
||||
export { xSearchTool }
|
||||
export { xUserTool }
|
||||
|
||||
export { xCreateBookmarkTool } from '@/tools/x/create_bookmark'
|
||||
export { xCreateTweetTool } from '@/tools/x/create_tweet'
|
||||
export { xDeleteBookmarkTool } from '@/tools/x/delete_bookmark'
|
||||
export { xDeleteTweetTool } from '@/tools/x/delete_tweet'
|
||||
export { xGetBlockingTool } from '@/tools/x/get_blocking'
|
||||
export { xGetBookmarksTool } from '@/tools/x/get_bookmarks'
|
||||
export { xGetFollowersTool } from '@/tools/x/get_followers'
|
||||
export { xGetFollowingTool } from '@/tools/x/get_following'
|
||||
export { xGetLikedTweetsTool } from '@/tools/x/get_liked_tweets'
|
||||
export { xGetLikingUsersTool } from '@/tools/x/get_liking_users'
|
||||
export { xGetMeTool } from '@/tools/x/get_me'
|
||||
export { xGetPersonalizedTrendsTool } from '@/tools/x/get_personalized_trends'
|
||||
export { xGetQuoteTweetsTool } from '@/tools/x/get_quote_tweets'
|
||||
export { xGetRetweetedByTool } from '@/tools/x/get_retweeted_by'
|
||||
export { xGetTrendsByWoeidTool } from '@/tools/x/get_trends_by_woeid'
|
||||
export { xGetTweetsByIdsTool } from '@/tools/x/get_tweets_by_ids'
|
||||
export { xGetUsageTool } from '@/tools/x/get_usage'
|
||||
export { xGetUserMentionsTool } from '@/tools/x/get_user_mentions'
|
||||
export { xGetUserTimelineTool } from '@/tools/x/get_user_timeline'
|
||||
export { xGetUserTweetsTool } from '@/tools/x/get_user_tweets'
|
||||
export { xHideReplyTool } from '@/tools/x/hide_reply'
|
||||
export { xManageBlockTool } from '@/tools/x/manage_block'
|
||||
export { xManageFollowTool } from '@/tools/x/manage_follow'
|
||||
export { xManageLikeTool } from '@/tools/x/manage_like'
|
||||
export { xManageMuteTool } from '@/tools/x/manage_mute'
|
||||
export { xManageRetweetTool } from '@/tools/x/manage_retweet'
|
||||
export { xSearchTweetsTool } from '@/tools/x/search_tweets'
|
||||
export { xSearchUsersTool } from '@/tools/x/search_users'
|
||||
export * from '@/tools/x/types'
|
||||
|
||||
93
apps/sim/tools/x/manage_block.ts
Normal file
93
apps/sim/tools/x/manage_block.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XManageBlockParams, XManageBlockResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XManageBlockTool')
|
||||
|
||||
export const xManageBlockTool: ToolConfig<XManageBlockParams, XManageBlockResponse> = {
|
||||
id: 'x_manage_block',
|
||||
name: 'X Manage Block',
|
||||
description: 'Block or unblock a user on X',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
targetUserId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The user ID to block or unblock',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action to perform: "block" or "unblock"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.action === 'unblock') {
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/blocking/${params.targetUserId.trim()}`
|
||||
}
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/blocking`
|
||||
},
|
||||
method: (params) => (params.action === 'unblock' ? 'DELETE' : 'POST'),
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (params.action === 'unblock') return undefined
|
||||
return {
|
||||
target_user_id: params.targetUserId.trim(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Manage Block API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
blocking: false,
|
||||
},
|
||||
error: data.errors?.[0]?.detail ?? 'Failed to manage block',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
blocking: data.data?.blocking ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
blocking: {
|
||||
type: 'boolean',
|
||||
description: 'Whether you are now blocking the user',
|
||||
},
|
||||
},
|
||||
}
|
||||
99
apps/sim/tools/x/manage_follow.ts
Normal file
99
apps/sim/tools/x/manage_follow.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XManageFollowParams, XManageFollowResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XManageFollowTool')
|
||||
|
||||
export const xManageFollowTool: ToolConfig<XManageFollowParams, XManageFollowResponse> = {
|
||||
id: 'x_manage_follow',
|
||||
name: 'X Manage Follow',
|
||||
description: 'Follow or unfollow a user on X',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
targetUserId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The user ID to follow or unfollow',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action to perform: "follow" or "unfollow"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.action === 'unfollow') {
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/following/${params.targetUserId.trim()}`
|
||||
}
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/following`
|
||||
},
|
||||
method: (params) => (params.action === 'unfollow' ? 'DELETE' : 'POST'),
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (params.action === 'unfollow') return undefined
|
||||
return {
|
||||
target_user_id: params.targetUserId.trim(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Manage Follow API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
following: false,
|
||||
pendingFollow: false,
|
||||
},
|
||||
error: data.errors?.[0]?.detail ?? 'Failed to manage follow',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
following: data.data?.following ?? false,
|
||||
pendingFollow: data.data?.pending_follow ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
following: {
|
||||
type: 'boolean',
|
||||
description: 'Whether you are now following the user',
|
||||
},
|
||||
pendingFollow: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the follow request is pending (for protected accounts)',
|
||||
},
|
||||
},
|
||||
}
|
||||
93
apps/sim/tools/x/manage_like.ts
Normal file
93
apps/sim/tools/x/manage_like.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XManageLikeParams, XManageLikeResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XManageLikeTool')
|
||||
|
||||
export const xManageLikeTool: ToolConfig<XManageLikeParams, XManageLikeResponse> = {
|
||||
id: 'x_manage_like',
|
||||
name: 'X Manage Like',
|
||||
description: 'Like or unlike a tweet on X',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The tweet ID to like or unlike',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action to perform: "like" or "unlike"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.action === 'unlike') {
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/likes/${params.tweetId.trim()}`
|
||||
}
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/likes`
|
||||
},
|
||||
method: (params) => (params.action === 'unlike' ? 'DELETE' : 'POST'),
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (params.action === 'unlike') return undefined
|
||||
return {
|
||||
tweet_id: params.tweetId.trim(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Manage Like API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
liked: false,
|
||||
},
|
||||
error: data.errors?.[0]?.detail ?? 'Failed to manage like',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
liked: data.data?.liked ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
liked: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet is now liked',
|
||||
},
|
||||
},
|
||||
}
|
||||
93
apps/sim/tools/x/manage_mute.ts
Normal file
93
apps/sim/tools/x/manage_mute.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XManageMuteParams, XManageMuteResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XManageMuteTool')
|
||||
|
||||
export const xManageMuteTool: ToolConfig<XManageMuteParams, XManageMuteResponse> = {
|
||||
id: 'x_manage_mute',
|
||||
name: 'X Manage Mute',
|
||||
description: 'Mute or unmute a user on X',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
targetUserId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The user ID to mute or unmute',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action to perform: "mute" or "unmute"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.action === 'unmute') {
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/muting/${params.targetUserId.trim()}`
|
||||
}
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/muting`
|
||||
},
|
||||
method: (params) => (params.action === 'unmute' ? 'DELETE' : 'POST'),
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (params.action === 'unmute') return undefined
|
||||
return {
|
||||
target_user_id: params.targetUserId.trim(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Manage Mute API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'Failed to mute/unmute user',
|
||||
output: {
|
||||
muting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
muting: data.data?.muting ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
muting: {
|
||||
type: 'boolean',
|
||||
description: 'Whether you are now muting the user',
|
||||
},
|
||||
},
|
||||
}
|
||||
93
apps/sim/tools/x/manage_retweet.ts
Normal file
93
apps/sim/tools/x/manage_retweet.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XManageRetweetParams, XManageRetweetResponse } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XManageRetweetTool')
|
||||
|
||||
export const xManageRetweetTool: ToolConfig<XManageRetweetParams, XManageRetweetResponse> = {
|
||||
id: 'x_manage_retweet',
|
||||
name: 'X Manage Retweet',
|
||||
description: 'Retweet or unretweet a tweet on X',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The authenticated user ID',
|
||||
},
|
||||
tweetId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The tweet ID to retweet or unretweet',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action to perform: "retweet" or "unretweet"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.action === 'unretweet') {
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/retweets/${params.tweetId.trim()}`
|
||||
}
|
||||
return `https://api.x.com/2/users/${params.userId.trim()}/retweets`
|
||||
},
|
||||
method: (params) => (params.action === 'unretweet' ? 'DELETE' : 'POST'),
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (params.action === 'unretweet') return undefined
|
||||
return {
|
||||
tweet_id: params.tweetId.trim(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data) {
|
||||
logger.error('X Manage Retweet API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
retweeted: false,
|
||||
},
|
||||
error: data.errors?.[0]?.detail ?? 'Failed to manage retweet',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
retweeted: data.data?.retweeted ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
retweeted: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tweet is now retweeted',
|
||||
},
|
||||
},
|
||||
}
|
||||
239
apps/sim/tools/x/search_tweets.ts
Normal file
239
apps/sim/tools/x/search_tweets.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XSearchTweetsParams, XTweet, XUser } from '@/tools/x/types'
|
||||
import { transformTweet, transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XSearchTweetsTool')
|
||||
|
||||
interface XSearchTweetsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
tweets: XTweet[]
|
||||
includes?: { users: XUser[] }
|
||||
meta: {
|
||||
resultCount: number
|
||||
newestId: string | null
|
||||
oldestId: string | null
|
||||
nextToken: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const xSearchTweetsTool: ToolConfig<XSearchTweetsParams, XSearchTweetsResponse> = {
|
||||
id: 'x_search_tweets',
|
||||
name: 'X Search Tweets',
|
||||
description: 'Search for recent tweets using keywords, hashtags, or advanced query operators',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query (supports operators like "from:", "to:", "#hashtag", "has:images", "is:retweet", "lang:")',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (10-100, default 10)',
|
||||
},
|
||||
startTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Oldest UTC timestamp in ISO 8601 format (e.g., 2024-01-01T00:00:00Z)',
|
||||
},
|
||||
endTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Newest UTC timestamp in ISO 8601 format',
|
||||
},
|
||||
sinceId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Returns tweets with ID greater than this',
|
||||
},
|
||||
untilId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Returns tweets with ID less than this',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: "recency" or "relevancy"',
|
||||
},
|
||||
nextToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page of results',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
query: params.query,
|
||||
expansions: 'author_id,referenced_tweets.id,attachments.media_keys,attachments.poll_ids',
|
||||
'tweet.fields':
|
||||
'created_at,conversation_id,in_reply_to_user_id,attachments,context_annotations,public_metrics',
|
||||
'user.fields': 'name,username,description,profile_image_url,verified,public_metrics',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(10, Math.min(100, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.startTime) queryParams.append('start_time', params.startTime)
|
||||
if (params.endTime) queryParams.append('end_time', params.endTime)
|
||||
if (params.sinceId) queryParams.append('since_id', params.sinceId)
|
||||
if (params.untilId) queryParams.append('until_id', params.untilId)
|
||||
if (params.sortOrder) queryParams.append('sort_order', params.sortOrder)
|
||||
if (params.nextToken) queryParams.append('next_token', params.nextToken)
|
||||
|
||||
return `https://api.x.com/2/tweets/search/recent?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Search Tweets API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
data.errors?.[0]?.detail ||
|
||||
data.errors?.[0]?.title ||
|
||||
'No results found or invalid response from X API',
|
||||
output: {
|
||||
tweets: [],
|
||||
includes: { users: [] },
|
||||
meta: {
|
||||
resultCount: 0,
|
||||
newestId: null,
|
||||
oldestId: null,
|
||||
nextToken: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tweets: data.data.map(transformTweet),
|
||||
includes: {
|
||||
users: data.includes?.users?.map(transformUser) ?? [],
|
||||
},
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? 0,
|
||||
newestId: data.meta?.newest_id ?? null,
|
||||
oldestId: data.meta?.oldest_id ?? null,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tweets: {
|
||||
type: 'array',
|
||||
description: 'Array of tweets matching the search query',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Tweet ID' },
|
||||
text: { type: 'string', description: 'Tweet text content' },
|
||||
createdAt: { type: 'string', description: 'Tweet creation timestamp' },
|
||||
authorId: { type: 'string', description: 'Author user ID' },
|
||||
conversationId: { type: 'string', description: 'Conversation thread ID', optional: true },
|
||||
inReplyToUserId: {
|
||||
type: 'string',
|
||||
description: 'User ID being replied to',
|
||||
optional: true,
|
||||
},
|
||||
publicMetrics: {
|
||||
type: 'object',
|
||||
description: 'Engagement metrics',
|
||||
optional: true,
|
||||
properties: {
|
||||
retweetCount: { type: 'number', description: 'Number of retweets' },
|
||||
replyCount: { type: 'number', description: 'Number of replies' },
|
||||
likeCount: { type: 'number', description: 'Number of likes' },
|
||||
quoteCount: { type: 'number', description: 'Number of quotes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
includes: {
|
||||
type: 'object',
|
||||
description: 'Additional data including user profiles',
|
||||
optional: true,
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects referenced in tweets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Search metadata including result count and pagination tokens',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
newestId: { type: 'string', description: 'ID of the newest tweet', optional: true },
|
||||
oldestId: { type: 'string', description: 'ID of the oldest tweet', optional: true },
|
||||
nextToken: {
|
||||
type: 'string',
|
||||
description: 'Pagination token for next page',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
133
apps/sim/tools/x/search_users.ts
Normal file
133
apps/sim/tools/x/search_users.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { XSearchUsersParams, XUserListResponse } from '@/tools/x/types'
|
||||
import { transformUser } from '@/tools/x/types'
|
||||
|
||||
const logger = createLogger('XSearchUsersTool')
|
||||
|
||||
export const xSearchUsersTool: ToolConfig<XSearchUsersParams, XUserListResponse> = {
|
||||
id: 'x_search_users',
|
||||
name: 'X Search Users',
|
||||
description: 'Search for X users by name, username, or bio',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'x',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'X OAuth access token',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search keyword (1-50 chars, matches name, username, or bio)',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results (1-1000, default 100)',
|
||||
},
|
||||
nextToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination token for next page',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
query: params.query,
|
||||
'user.fields': 'created_at,description,profile_image_url,verified,public_metrics,location',
|
||||
})
|
||||
|
||||
if (params.maxResults) {
|
||||
const max = Math.max(1, Math.min(1000, Number(params.maxResults)))
|
||||
queryParams.append('max_results', max.toString())
|
||||
}
|
||||
if (params.nextToken) queryParams.append('next_token', params.nextToken)
|
||||
|
||||
return `https://api.x.com/2/users/search?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.data || !Array.isArray(data.data)) {
|
||||
logger.error('X Search Users API Error:', JSON.stringify(data, null, 2))
|
||||
return {
|
||||
success: false,
|
||||
error: data.errors?.[0]?.detail || 'No users found or invalid response',
|
||||
output: {
|
||||
users: [],
|
||||
meta: { resultCount: 0, nextToken: null },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users: data.data.map(transformUser),
|
||||
meta: {
|
||||
resultCount: data.meta?.result_count ?? data.data.length,
|
||||
nextToken: data.meta?.next_token ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of users matching the search query',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username without @ symbol' },
|
||||
name: { type: 'string', description: 'Display name' },
|
||||
description: { type: 'string', description: 'User bio', optional: true },
|
||||
profileImageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
verified: { type: 'boolean', description: 'Whether the user is verified' },
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'User statistics',
|
||||
properties: {
|
||||
followersCount: { type: 'number', description: 'Number of followers' },
|
||||
followingCount: { type: 'number', description: 'Number of users following' },
|
||||
tweetCount: { type: 'number', description: 'Total number of tweets' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description: 'Search metadata',
|
||||
properties: {
|
||||
resultCount: { type: 'number', description: 'Number of results returned' },
|
||||
nextToken: {
|
||||
type: 'string',
|
||||
description: 'Pagination token for next page',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -184,3 +184,339 @@ export const transformUser = (user: any): XUser => ({
|
||||
tweetCount: user.public_metrics?.tweet_count || 0,
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Trend object from X API (WOEID trends)
|
||||
*/
|
||||
export interface XTrend {
|
||||
trendName: string
|
||||
tweetCount: number | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Personalized trend object from X API
|
||||
*/
|
||||
export interface XPersonalizedTrend {
|
||||
trendName: string
|
||||
postCount: number | null
|
||||
category: string | null
|
||||
trendingSince: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms raw X API trend data (WOEID) into the XTrend format
|
||||
*/
|
||||
export const transformTrend = (trend: any): XTrend => ({
|
||||
trendName: trend.trend_name ?? trend.name ?? '',
|
||||
tweetCount: trend.tweet_count ?? null,
|
||||
})
|
||||
|
||||
/**
|
||||
* Transforms raw X API personalized trend data into the XPersonalizedTrend format
|
||||
*/
|
||||
export const transformPersonalizedTrend = (trend: any): XPersonalizedTrend => ({
|
||||
trendName: trend.trend_name ?? '',
|
||||
postCount: trend.post_count ?? null,
|
||||
category: trend.category ?? null,
|
||||
trendingSince: trend.trending_since ?? null,
|
||||
})
|
||||
|
||||
// --- New Tool Parameter Interfaces ---
|
||||
|
||||
export interface XSearchTweetsParams extends XBaseParams {
|
||||
query: string
|
||||
maxResults?: number
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
sinceId?: string
|
||||
untilId?: string
|
||||
sortOrder?: string
|
||||
nextToken?: string
|
||||
}
|
||||
|
||||
export interface XGetUserTweetsParams extends XBaseParams {
|
||||
userId: string
|
||||
maxResults?: number
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
sinceId?: string
|
||||
untilId?: string
|
||||
exclude?: string
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XGetUserMentionsParams extends XBaseParams {
|
||||
userId: string
|
||||
maxResults?: number
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
sinceId?: string
|
||||
untilId?: string
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XGetUserTimelineParams extends XBaseParams {
|
||||
userId: string
|
||||
maxResults?: number
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
sinceId?: string
|
||||
untilId?: string
|
||||
exclude?: string
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XGetTweetsByIdsParams extends XBaseParams {
|
||||
ids: string
|
||||
}
|
||||
|
||||
export interface XGetTweetsByIdsResponse extends ToolResponse {
|
||||
output: {
|
||||
tweets: XTweet[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface XGetBookmarksParams extends XBaseParams {
|
||||
userId: string
|
||||
maxResults?: number
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XCreateBookmarkParams extends XBaseParams {
|
||||
userId: string
|
||||
tweetId: string
|
||||
}
|
||||
|
||||
export interface XCreateBookmarkResponse extends ToolResponse {
|
||||
output: {
|
||||
bookmarked: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface XDeleteBookmarkParams extends XBaseParams {
|
||||
userId: string
|
||||
tweetId: string
|
||||
}
|
||||
|
||||
export interface XDeleteBookmarkResponse extends ToolResponse {
|
||||
output: {
|
||||
bookmarked: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface XCreateTweetParams extends XBaseParams {
|
||||
text: string
|
||||
replyToTweetId?: string
|
||||
quoteTweetId?: string
|
||||
mediaIds?: string
|
||||
replySettings?: string
|
||||
}
|
||||
|
||||
export interface XCreateTweetResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface XDeleteTweetParams extends XBaseParams {
|
||||
tweetId: string
|
||||
}
|
||||
|
||||
export interface XDeleteTweetResponse extends ToolResponse {
|
||||
output: {
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface XGetMeParams extends XBaseParams {}
|
||||
|
||||
export interface XGetMeResponse extends ToolResponse {
|
||||
output: {
|
||||
user: XUser
|
||||
}
|
||||
}
|
||||
|
||||
export interface XSearchUsersParams extends XBaseParams {
|
||||
query: string
|
||||
maxResults?: number
|
||||
nextToken?: string
|
||||
}
|
||||
|
||||
export interface XGetFollowersParams extends XBaseParams {
|
||||
userId: string
|
||||
maxResults?: number
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XGetFollowingParams extends XBaseParams {
|
||||
userId: string
|
||||
maxResults?: number
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XManageFollowParams extends XBaseParams {
|
||||
userId: string
|
||||
targetUserId: string
|
||||
action: string
|
||||
}
|
||||
|
||||
export interface XManageFollowResponse extends ToolResponse {
|
||||
output: {
|
||||
following: boolean
|
||||
pendingFollow: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface XGetBlockingParams extends XBaseParams {
|
||||
userId: string
|
||||
maxResults?: number
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XManageBlockParams extends XBaseParams {
|
||||
userId: string
|
||||
targetUserId: string
|
||||
action: string
|
||||
}
|
||||
|
||||
export interface XManageBlockResponse extends ToolResponse {
|
||||
output: {
|
||||
blocking: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface XGetLikedTweetsParams extends XBaseParams {
|
||||
userId: string
|
||||
maxResults?: number
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XGetLikingUsersParams extends XBaseParams {
|
||||
tweetId: string
|
||||
maxResults?: number
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XManageLikeParams extends XBaseParams {
|
||||
userId: string
|
||||
tweetId: string
|
||||
action: string
|
||||
}
|
||||
|
||||
export interface XManageLikeResponse extends ToolResponse {
|
||||
output: {
|
||||
liked: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface XManageRetweetParams extends XBaseParams {
|
||||
userId: string
|
||||
tweetId: string
|
||||
action: string
|
||||
}
|
||||
|
||||
export interface XManageRetweetResponse extends ToolResponse {
|
||||
output: {
|
||||
retweeted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface XGetRetweetedByParams extends XBaseParams {
|
||||
tweetId: string
|
||||
maxResults?: number
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XGetQuoteTweetsParams extends XBaseParams {
|
||||
tweetId: string
|
||||
maxResults?: number
|
||||
paginationToken?: string
|
||||
}
|
||||
|
||||
export interface XGetTrendsByWoeidParams extends XBaseParams {
|
||||
woeid: string
|
||||
maxTrends?: number
|
||||
}
|
||||
|
||||
export interface XGetPersonalizedTrendsParams extends XBaseParams {}
|
||||
|
||||
export interface XGetUsageParams extends XBaseParams {
|
||||
days?: number
|
||||
}
|
||||
|
||||
export interface XGetUsageResponse extends ToolResponse {
|
||||
output: {
|
||||
capResetDay: number | null
|
||||
projectId: string
|
||||
projectCap: number | null
|
||||
projectUsage: number | null
|
||||
dailyProjectUsage: Array<{ date: string; usage: number }>
|
||||
dailyClientAppUsage: Array<{
|
||||
clientAppId: string
|
||||
usage: Array<{ date: string; usage: number }>
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface XHideReplyParams extends XBaseParams {
|
||||
tweetId: string
|
||||
hidden: boolean
|
||||
}
|
||||
|
||||
export interface XHideReplyResponse extends ToolResponse {
|
||||
output: {
|
||||
hidden: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface XManageMuteParams extends XBaseParams {
|
||||
userId: string
|
||||
targetUserId: string
|
||||
action: string
|
||||
}
|
||||
|
||||
export interface XManageMuteResponse extends ToolResponse {
|
||||
output: {
|
||||
muting: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Common response types for list endpoints
|
||||
export interface XTweetListResponse extends ToolResponse {
|
||||
output: {
|
||||
tweets: XTweet[]
|
||||
includes?: {
|
||||
users: XUser[]
|
||||
}
|
||||
meta: {
|
||||
resultCount: number
|
||||
newestId: string | null
|
||||
oldestId: string | null
|
||||
nextToken: string | null
|
||||
previousToken: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface XUserListResponse extends ToolResponse {
|
||||
output: {
|
||||
users: XUser[]
|
||||
meta: {
|
||||
resultCount: number
|
||||
nextToken: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface XTrendListResponse extends ToolResponse {
|
||||
output: {
|
||||
trends: XTrend[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface XPersonalizedTrendListResponse extends ToolResponse {
|
||||
output: {
|
||||
trends: XPersonalizedTrend[]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user