Skip to content

Commit e10721c

Browse files
committed
feat(x): add 28 new X API v2 tool integrations and expand OAuth scopes
1 parent 4ccb573 commit e10721c

33 files changed

+3892
-3
lines changed

apps/sim/blocks/blocks/x.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,23 @@ export const XBlock: BlockConfig<XResponse> = {
3434
serviceId: 'x',
3535
canonicalParamId: 'oauthCredential',
3636
mode: 'basic',
37-
requiredScopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
37+
requiredScopes: [
38+
'tweet.read',
39+
'tweet.write',
40+
'tweet.moderate.write',
41+
'users.read',
42+
'follows.read',
43+
'follows.write',
44+
'bookmark.read',
45+
'bookmark.write',
46+
'like.read',
47+
'like.write',
48+
'block.read',
49+
'block.write',
50+
'mute.read',
51+
'mute.write',
52+
'offline.access',
53+
],
3854
placeholder: 'Select X account',
3955
},
4056
{

apps/sim/lib/oauth/oauth.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,23 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
310310
providerId: 'x',
311311
icon: xIcon,
312312
baseProviderIcon: xIcon,
313-
scopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
313+
scopes: [
314+
'tweet.read',
315+
'tweet.write',
316+
'tweet.moderate.write',
317+
'users.read',
318+
'follows.read',
319+
'follows.write',
320+
'bookmark.read',
321+
'bookmark.write',
322+
'like.read',
323+
'like.write',
324+
'block.read',
325+
'block.write',
326+
'mute.read',
327+
'mute.write',
328+
'offline.access',
329+
],
314330
},
315331
},
316332
defaultService: 'x',

apps/sim/tools/registry.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2058,7 +2058,40 @@ import {
20582058
wordpressUploadMediaTool,
20592059
} from '@/tools/wordpress'
20602060
import { workflowExecutorTool } from '@/tools/workflow'
2061-
import { xReadTool, xSearchTool, xUserTool, xWriteTool } from '@/tools/x'
2061+
import {
2062+
xCreateBookmarkTool,
2063+
xCreateTweetTool,
2064+
xDeleteBookmarkTool,
2065+
xDeleteTweetTool,
2066+
xGetBlockingTool,
2067+
xGetBookmarksTool,
2068+
xGetFollowersTool,
2069+
xGetFollowingTool,
2070+
xGetLikedTweetsTool,
2071+
xGetLikingUsersTool,
2072+
xGetMeTool,
2073+
xGetPersonalizedTrendsTool,
2074+
xGetQuoteTweetsTool,
2075+
xGetRetweetedByTool,
2076+
xGetTrendsByWoeidTool,
2077+
xGetTweetsByIdsTool,
2078+
xGetUsageTool,
2079+
xGetUserMentionsTool,
2080+
xGetUserTimelineTool,
2081+
xGetUserTweetsTool,
2082+
xHideReplyTool,
2083+
xManageBlockTool,
2084+
xManageFollowTool,
2085+
xManageLikeTool,
2086+
xManageMuteTool,
2087+
xManageRetweetTool,
2088+
xReadTool,
2089+
xSearchTool,
2090+
xSearchTweetsTool,
2091+
xSearchUsersTool,
2092+
xUserTool,
2093+
xWriteTool,
2094+
} from '@/tools/x'
20622095
import {
20632096
youtubeChannelInfoTool,
20642097
youtubeChannelPlaylistsTool,
@@ -2539,6 +2572,34 @@ export const tools: Record<string, ToolConfig> = {
25392572
x_read: xReadTool,
25402573
x_search: xSearchTool,
25412574
x_user: xUserTool,
2575+
x_search_tweets: xSearchTweetsTool,
2576+
x_get_user_tweets: xGetUserTweetsTool,
2577+
x_get_user_mentions: xGetUserMentionsTool,
2578+
x_get_user_timeline: xGetUserTimelineTool,
2579+
x_get_tweets_by_ids: xGetTweetsByIdsTool,
2580+
x_get_bookmarks: xGetBookmarksTool,
2581+
x_create_bookmark: xCreateBookmarkTool,
2582+
x_delete_bookmark: xDeleteBookmarkTool,
2583+
x_create_tweet: xCreateTweetTool,
2584+
x_delete_tweet: xDeleteTweetTool,
2585+
x_get_me: xGetMeTool,
2586+
x_search_users: xSearchUsersTool,
2587+
x_get_followers: xGetFollowersTool,
2588+
x_get_following: xGetFollowingTool,
2589+
x_manage_follow: xManageFollowTool,
2590+
x_get_blocking: xGetBlockingTool,
2591+
x_manage_block: xManageBlockTool,
2592+
x_get_liked_tweets: xGetLikedTweetsTool,
2593+
x_get_liking_users: xGetLikingUsersTool,
2594+
x_manage_like: xManageLikeTool,
2595+
x_manage_retweet: xManageRetweetTool,
2596+
x_get_retweeted_by: xGetRetweetedByTool,
2597+
x_get_quote_tweets: xGetQuoteTweetsTool,
2598+
x_get_trends_by_woeid: xGetTrendsByWoeidTool,
2599+
x_get_personalized_trends: xGetPersonalizedTrendsTool,
2600+
x_get_usage: xGetUsageTool,
2601+
x_hide_reply: xHideReplyTool,
2602+
x_manage_mute: xManageMuteTool,
25422603
pinecone_fetch: pineconeFetchTool,
25432604
pinecone_generate_embeddings: pineconeGenerateEmbeddingsTool,
25442605
pinecone_search_text: pineconeSearchTextTool,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { ToolConfig } from '@/tools/types'
2+
import type { XCreateBookmarkParams, XCreateBookmarkResponse } from '@/tools/x/types'
3+
4+
export const xCreateBookmarkTool: ToolConfig<XCreateBookmarkParams, XCreateBookmarkResponse> = {
5+
id: 'x_create_bookmark',
6+
name: 'X Create Bookmark',
7+
description: 'Bookmark a tweet for the authenticated user',
8+
version: '1.0.0',
9+
10+
oauth: {
11+
required: true,
12+
provider: 'x',
13+
},
14+
15+
params: {
16+
accessToken: {
17+
type: 'string',
18+
required: true,
19+
visibility: 'hidden',
20+
description: 'X OAuth access token',
21+
},
22+
userId: {
23+
type: 'string',
24+
required: true,
25+
visibility: 'user-or-llm',
26+
description: 'The authenticated user ID',
27+
},
28+
tweetId: {
29+
type: 'string',
30+
required: true,
31+
visibility: 'user-or-llm',
32+
description: 'The tweet ID to bookmark',
33+
},
34+
},
35+
36+
request: {
37+
url: (params) => `https://api.x.com/2/users/${params.userId.trim()}/bookmarks`,
38+
method: 'POST',
39+
headers: (params) => ({
40+
Authorization: `Bearer ${params.accessToken}`,
41+
'Content-Type': 'application/json',
42+
}),
43+
body: (params) => ({
44+
tweet_id: params.tweetId.trim(),
45+
}),
46+
},
47+
48+
transformResponse: async (response) => {
49+
const data = await response.json()
50+
return {
51+
success: true,
52+
output: {
53+
bookmarked: data.data?.bookmarked ?? false,
54+
},
55+
}
56+
},
57+
58+
outputs: {
59+
bookmarked: {
60+
type: 'boolean',
61+
description: 'Whether the tweet was successfully bookmarked',
62+
},
63+
},
64+
}

apps/sim/tools/x/create_tweet.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type { ToolConfig } from '@/tools/types'
2+
import type { XCreateTweetParams, XCreateTweetResponse } from '@/tools/x/types'
3+
4+
export const xCreateTweetTool: ToolConfig<XCreateTweetParams, XCreateTweetResponse> = {
5+
id: 'x_create_tweet',
6+
name: 'X Create Tweet',
7+
description: 'Create a new tweet, reply, or quote tweet on X',
8+
version: '1.0.0',
9+
10+
oauth: {
11+
required: true,
12+
provider: 'x',
13+
},
14+
15+
params: {
16+
accessToken: {
17+
type: 'string',
18+
required: true,
19+
visibility: 'hidden',
20+
description: 'X OAuth access token',
21+
},
22+
text: {
23+
type: 'string',
24+
required: true,
25+
visibility: 'user-or-llm',
26+
description: 'The text content of the tweet (max 280 characters)',
27+
},
28+
replyToTweetId: {
29+
type: 'string',
30+
required: false,
31+
visibility: 'user-or-llm',
32+
description: 'Tweet ID to reply to',
33+
},
34+
quoteTweetId: {
35+
type: 'string',
36+
required: false,
37+
visibility: 'user-or-llm',
38+
description: 'Tweet ID to quote',
39+
},
40+
mediaIds: {
41+
type: 'string',
42+
required: false,
43+
visibility: 'user-or-llm',
44+
description: 'Comma-separated media IDs to attach (up to 4)',
45+
},
46+
replySettings: {
47+
type: 'string',
48+
required: false,
49+
visibility: 'user-or-llm',
50+
description: 'Who can reply: "mentionedUsers", "following", "subscribers", or "verified"',
51+
},
52+
},
53+
54+
request: {
55+
url: 'https://api.x.com/2/tweets',
56+
method: 'POST',
57+
headers: (params) => ({
58+
Authorization: `Bearer ${params.accessToken}`,
59+
'Content-Type': 'application/json',
60+
}),
61+
body: (params) => {
62+
const body: Record<string, unknown> = {
63+
text: params.text,
64+
}
65+
66+
if (params.replyToTweetId) {
67+
body.reply = { in_reply_to_tweet_id: params.replyToTweetId.trim() }
68+
}
69+
70+
if (params.quoteTweetId) {
71+
body.quote_tweet_id = params.quoteTweetId.trim()
72+
}
73+
74+
if (params.mediaIds) {
75+
const ids = params.mediaIds
76+
.split(',')
77+
.map((id) => id.trim())
78+
.filter(Boolean)
79+
if (ids.length > 0) {
80+
body.media = { media_ids: ids }
81+
}
82+
}
83+
84+
if (params.replySettings) {
85+
body.reply_settings = params.replySettings
86+
}
87+
88+
return body
89+
},
90+
},
91+
92+
transformResponse: async (response) => {
93+
const data = await response.json()
94+
return {
95+
success: true,
96+
output: {
97+
id: data.data?.id ?? '',
98+
text: data.data?.text ?? '',
99+
},
100+
}
101+
},
102+
103+
outputs: {
104+
id: {
105+
type: 'string',
106+
description: 'The ID of the created tweet',
107+
},
108+
text: {
109+
type: 'string',
110+
description: 'The text of the created tweet',
111+
},
112+
},
113+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { ToolConfig } from '@/tools/types'
2+
import type { XDeleteBookmarkParams, XDeleteBookmarkResponse } from '@/tools/x/types'
3+
4+
export const xDeleteBookmarkTool: ToolConfig<XDeleteBookmarkParams, XDeleteBookmarkResponse> = {
5+
id: 'x_delete_bookmark',
6+
name: 'X Delete Bookmark',
7+
description: "Remove a tweet from the authenticated user's bookmarks",
8+
version: '1.0.0',
9+
10+
oauth: {
11+
required: true,
12+
provider: 'x',
13+
},
14+
15+
params: {
16+
accessToken: {
17+
type: 'string',
18+
required: true,
19+
visibility: 'hidden',
20+
description: 'X OAuth access token',
21+
},
22+
userId: {
23+
type: 'string',
24+
required: true,
25+
visibility: 'user-or-llm',
26+
description: 'The authenticated user ID',
27+
},
28+
tweetId: {
29+
type: 'string',
30+
required: true,
31+
visibility: 'user-or-llm',
32+
description: 'The tweet ID to remove from bookmarks',
33+
},
34+
},
35+
36+
request: {
37+
url: (params) =>
38+
`https://api.x.com/2/users/${params.userId.trim()}/bookmarks/${params.tweetId.trim()}`,
39+
method: 'DELETE',
40+
headers: (params) => ({
41+
Authorization: `Bearer ${params.accessToken}`,
42+
'Content-Type': 'application/json',
43+
}),
44+
},
45+
46+
transformResponse: async (response) => {
47+
const data = await response.json()
48+
return {
49+
success: true,
50+
output: {
51+
bookmarked: data.data?.bookmarked ?? false,
52+
},
53+
}
54+
},
55+
56+
outputs: {
57+
bookmarked: {
58+
type: 'boolean',
59+
description: 'Whether the tweet is still bookmarked (should be false after deletion)',
60+
},
61+
},
62+
}

0 commit comments

Comments
 (0)