Skip to content

Commit 60e2e6c

Browse files
icecrasher321Vikhyath MondretiVikhyath Mondretigreptile-apps[bot]
authored
fix(reddit): update to oauth endpoints (#627)
* fix(reddit): change tool to use oauth token * fix lint * add contact info * Update apps/sim/tools/reddit/get_comments.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update apps/sim/tools/reddit/hot_posts.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update apps/sim/tools/reddit/get_posts.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix type error --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
1 parent c635b19 commit 60e2e6c

File tree

9 files changed

+198
-31
lines changed

9 files changed

+198
-31
lines changed

apps/sim/blocks/blocks/reddit.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ export const RedditBlock: BlockConfig<
3131
],
3232
},
3333

34+
// Reddit OAuth Authentication
35+
{
36+
id: 'credential',
37+
title: 'Reddit Account',
38+
type: 'oauth-input',
39+
layout: 'full',
40+
provider: 'reddit',
41+
serviceId: 'reddit',
42+
requiredScopes: ['identity', 'read'],
43+
placeholder: 'Select Reddit account',
44+
},
45+
3446
// Common fields - appear for all actions
3547
{
3648
id: 'subreddit',
@@ -151,27 +163,31 @@ export const RedditBlock: BlockConfig<
151163
},
152164
params: (inputs) => {
153165
const action = inputs.action || 'get_posts'
166+
const { credential, ...rest } = inputs
154167

155168
if (action === 'get_comments') {
156169
return {
157-
postId: inputs.postId,
158-
subreddit: inputs.subreddit,
159-
sort: inputs.commentSort,
160-
limit: inputs.commentLimit ? Number.parseInt(inputs.commentLimit) : undefined,
170+
postId: rest.postId,
171+
subreddit: rest.subreddit,
172+
sort: rest.commentSort,
173+
limit: rest.commentLimit ? Number.parseInt(rest.commentLimit) : undefined,
174+
credential: credential,
161175
}
162176
}
163177

164178
return {
165-
subreddit: inputs.subreddit,
166-
sort: inputs.sort,
167-
limit: inputs.limit ? Number.parseInt(inputs.limit) : undefined,
168-
time: inputs.sort === 'top' ? inputs.time : undefined,
179+
subreddit: rest.subreddit,
180+
sort: rest.sort,
181+
limit: rest.limit ? Number.parseInt(rest.limit) : undefined,
182+
time: rest.sort === 'top' ? rest.time : undefined,
183+
credential: credential,
169184
}
170185
},
171186
},
172187
},
173188
inputs: {
174189
action: { type: 'string', required: true },
190+
credential: { type: 'string', required: true },
175191
subreddit: { type: 'string', required: true },
176192
sort: { type: 'string', required: true },
177193
time: { type: 'string', required: false },

apps/sim/lib/auth.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export const auth = betterAuth({
135135
'notion',
136136
'microsoft',
137137
'slack',
138+
'reddit',
138139
],
139140
},
140141
},
@@ -825,6 +826,57 @@ export const auth = betterAuth({
825826
},
826827
},
827828

829+
// Reddit provider
830+
{
831+
providerId: 'reddit',
832+
clientId: env.REDDIT_CLIENT_ID as string,
833+
clientSecret: env.REDDIT_CLIENT_SECRET as string,
834+
authorizationUrl: 'https://www.reddit.com/api/v1/authorize',
835+
tokenUrl: 'https://www.reddit.com/api/v1/access_token',
836+
userInfoUrl: 'https://oauth.reddit.com/api/v1/me',
837+
scopes: ['identity', 'read'],
838+
responseType: 'code',
839+
pkce: false,
840+
accessType: 'offline',
841+
authentication: 'basic',
842+
prompt: 'consent',
843+
redirectURI: `${env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/reddit`,
844+
getUserInfo: async (tokens) => {
845+
try {
846+
const response = await fetch('https://oauth.reddit.com/api/v1/me', {
847+
headers: {
848+
Authorization: `Bearer ${tokens.accessToken}`,
849+
'User-Agent': 'sim-studio/1.0',
850+
},
851+
})
852+
853+
if (!response.ok) {
854+
logger.error('Error fetching Reddit user info:', {
855+
status: response.status,
856+
statusText: response.statusText,
857+
})
858+
return null
859+
}
860+
861+
const data = await response.json()
862+
const now = new Date()
863+
864+
return {
865+
id: data.id,
866+
name: data.name || 'Reddit User',
867+
email: `${data.name}@reddit.user`, // Reddit doesn't provide email in identity scope
868+
image: data.icon_img || null,
869+
emailVerified: false,
870+
createdAt: now,
871+
updatedAt: now,
872+
}
873+
} catch (error) {
874+
logger.error('Error in Reddit getUserInfo:', { error })
875+
return null
876+
}
877+
},
878+
},
879+
828880
{
829881
providerId: 'linear',
830882
clientId: env.LINEAR_CLIENT_ID as string,

apps/sim/lib/env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ export const env = createEnv({
103103
LINEAR_CLIENT_SECRET: z.string().optional(),
104104
SLACK_CLIENT_ID: z.string().optional(),
105105
SLACK_CLIENT_SECRET: z.string().optional(),
106+
REDDIT_CLIENT_ID: z.string().optional(),
107+
REDDIT_CLIENT_SECRET: z.string().optional(),
106108
SOCKET_SERVER_URL: z.string().url().optional(),
107109
SOCKET_PORT: z.number().optional(),
108110
PORT: z.number().optional(),

apps/sim/lib/oauth/oauth.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ vi.mock('../env', () => ({
2626
LINEAR_CLIENT_SECRET: 'linear_client_secret',
2727
SLACK_CLIENT_ID: 'slack_client_id',
2828
SLACK_CLIENT_SECRET: 'slack_client_secret',
29+
REDDIT_CLIENT_ID: 'reddit_client_id',
30+
REDDIT_CLIENT_SECRET: 'reddit_client_secret',
2931
},
3032
}))
3133

@@ -80,6 +82,11 @@ describe('OAuth Token Refresh', () => {
8082
endpoint: 'https://discord.com/api/v10/oauth2/token',
8183
},
8284
{ name: 'Linear', providerId: 'linear', endpoint: 'https://api.linear.app/oauth/token' },
85+
{
86+
name: 'Reddit',
87+
providerId: 'reddit',
88+
endpoint: 'https://www.reddit.com/api/v1/access_token',
89+
},
8390
]
8491

8592
basicAuthProviders.forEach(({ name, providerId, endpoint }) => {

apps/sim/lib/oauth/oauth.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
MicrosoftTeamsIcon,
1818
NotionIcon,
1919
OutlookIcon,
20+
RedditIcon,
2021
SlackIcon,
2122
SupabaseIcon,
2223
xIcon,
@@ -39,6 +40,7 @@ export type OAuthProvider =
3940
| 'microsoft'
4041
| 'linear'
4142
| 'slack'
43+
| 'reddit'
4244
| string
4345

4446
export type OAuthService =
@@ -61,6 +63,7 @@ export type OAuthService =
6163
| 'outlook'
6264
| 'linear'
6365
| 'slack'
66+
| 'reddit'
6467

6568
export interface OAuthProviderConfig {
6669
id: OAuthProvider
@@ -387,6 +390,23 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
387390
},
388391
defaultService: 'slack',
389392
},
393+
reddit: {
394+
id: 'reddit',
395+
name: 'Reddit',
396+
icon: (props) => RedditIcon(props),
397+
services: {
398+
reddit: {
399+
id: 'reddit',
400+
name: 'Reddit',
401+
description: 'Access Reddit data and content from subreddits.',
402+
providerId: 'reddit',
403+
icon: (props) => RedditIcon(props),
404+
baseProviderIcon: (props) => RedditIcon(props),
405+
scopes: ['identity', 'read'],
406+
},
407+
},
408+
defaultService: 'reddit',
409+
},
390410
}
391411

392412
// Helper function to get a service by provider and service ID
@@ -695,6 +715,18 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
695715
useBasicAuth: false,
696716
}
697717
}
718+
case 'reddit': {
719+
const { clientId, clientSecret } = getCredentials(
720+
env.REDDIT_CLIENT_ID,
721+
env.REDDIT_CLIENT_SECRET
722+
)
723+
return {
724+
tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
725+
clientId,
726+
clientSecret,
727+
useBasicAuth: true,
728+
}
729+
}
698730
default:
699731
throw new Error(`Unsupported provider: ${provider}`)
700732
}

apps/sim/tools/reddit/get_comments.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ export const getCommentsTool: ToolConfig<RedditCommentsParams, RedditCommentsRes
77
description: 'Fetch comments from a specific Reddit post',
88
version: '1.0.0',
99

10+
oauth: {
11+
required: true,
12+
provider: 'reddit',
13+
additionalScopes: ['read'],
14+
},
15+
1016
params: {
1117
postId: {
1218
type: 'string',
@@ -38,15 +44,21 @@ export const getCommentsTool: ToolConfig<RedditCommentsParams, RedditCommentsRes
3844
const sort = params.sort || 'confidence'
3945
const limit = Math.min(Math.max(1, params.limit || 50), 100)
4046

41-
// Build URL
42-
return `https://www.reddit.com/r/${subreddit}/comments/${params.postId}.json?sort=${sort}&limit=${limit}&raw_json=1`
47+
// Build URL using OAuth endpoint
48+
return `https://oauth.reddit.com/r/${subreddit}/comments/${params.postId}?sort=${sort}&limit=${limit}&raw_json=1`
4349
},
4450
method: 'GET',
45-
headers: () => ({
46-
'User-Agent':
47-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
48-
Accept: 'application/json',
49-
}),
51+
headers: (params: RedditCommentsParams) => {
52+
if (!params.accessToken?.trim()) {
53+
throw new Error('Access token is required for Reddit API')
54+
}
55+
56+
return {
57+
Authorization: `Bearer ${params.accessToken}`,
58+
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
59+
Accept: 'application/json',
60+
}
61+
},
5062
},
5163

5264
transformResponse: async (response: Response, requestParams?: RedditCommentsParams) => {

apps/sim/tools/reddit/get_posts.ts

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
77
description: 'Fetch posts from a subreddit with different sorting options',
88
version: '1.0.0',
99

10+
oauth: {
11+
required: true,
12+
provider: 'reddit',
13+
additionalScopes: ['read'],
14+
},
15+
1016
params: {
1117
subreddit: {
1218
type: 'string',
@@ -38,8 +44,8 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
3844
const sort = params.sort || 'hot'
3945
const limit = Math.min(Math.max(1, params.limit || 10), 100)
4046

41-
// Build URL with appropriate parameters
42-
let url = `https://www.reddit.com/r/${subreddit}/${sort}.json?limit=${limit}&raw_json=1`
47+
// Build URL with appropriate parameters using OAuth endpoint
48+
let url = `https://oauth.reddit.com/r/${subreddit}/${sort}?limit=${limit}&raw_json=1`
4349

4450
// Add time parameter only for 'top' sorting
4551
if (sort === 'top' && params.time) {
@@ -49,29 +55,54 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
4955
return url
5056
},
5157
method: 'GET',
52-
headers: () => ({
53-
'User-Agent':
54-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
55-
Accept: 'application/json',
56-
}),
58+
headers: (params: RedditPostsParams) => {
59+
if (!params.accessToken) {
60+
throw new Error('Access token is required for Reddit API')
61+
}
62+
63+
return {
64+
Authorization: `Bearer ${params.accessToken}`,
65+
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
66+
Accept: 'application/json',
67+
}
68+
},
5769
},
5870

5971
transformResponse: async (response: Response, requestParams?: RedditPostsParams) => {
6072
try {
6173
// Check if response is OK
6274
if (!response.ok) {
75+
// Get response text for better error details
76+
const errorText = await response.text()
77+
console.error('Reddit API Error:', {
78+
status: response.status,
79+
statusText: response.statusText,
80+
body: errorText,
81+
url: response.url,
82+
})
83+
6384
if (response.status === 403 || response.status === 429) {
6485
throw new Error('Reddit API access blocked or rate limited. Please try again later.')
6586
}
66-
throw new Error(`Reddit API returned ${response.status}: ${response.statusText}`)
87+
throw new Error(
88+
`Reddit API returned ${response.status}: ${response.statusText}. Body: ${errorText}`
89+
)
6790
}
6891

6992
// Attempt to parse JSON
7093
let data
7194
try {
7295
data = await response.json()
73-
} catch (_error) {
74-
throw new Error('Failed to parse Reddit API response: Response was not valid JSON')
96+
} catch (error) {
97+
const responseText = await response.text()
98+
console.error('Failed to parse Reddit API response as JSON:', {
99+
error: error instanceof Error ? error.message : String(error),
100+
responseText,
101+
contentType: response.headers.get('content-type'),
102+
})
103+
throw new Error(
104+
`Failed to parse Reddit API response: Response was not valid JSON. Content: ${responseText}`
105+
)
75106
}
76107

77108
// Check if response contains error

apps/sim/tools/reddit/hot_posts.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { RedditHotPostsResponse, RedditPost } from './types'
44
interface HotPostsParams {
55
subreddit: string
66
limit?: number
7+
accessToken: string
78
}
89

910
export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> = {
@@ -12,6 +13,12 @@ export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> =
1213
description: 'Fetch the most popular (hot) posts from a specified subreddit.',
1314
version: '1.0.0',
1415

16+
oauth: {
17+
required: true,
18+
provider: 'reddit',
19+
additionalScopes: ['read'],
20+
},
21+
1522
params: {
1623
subreddit: {
1724
type: 'string',
@@ -31,14 +38,20 @@ export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> =
3138
const subreddit = params.subreddit.trim().replace(/^r\//, '')
3239
const limit = Math.min(Math.max(1, params.limit || 10), 100)
3340

34-
return `https://www.reddit.com/r/${subreddit}/hot.json?limit=${limit}&raw_json=1`
41+
return `https://oauth.reddit.com/r/${subreddit}/hot?limit=${limit}&raw_json=1`
3542
},
3643
method: 'GET',
37-
headers: () => ({
38-
'User-Agent':
39-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
40-
Accept: 'application/json',
41-
}),
44+
headers: (params: HotPostsParams) => {
45+
if (!params.accessToken) {
46+
throw new Error('Access token is required for Reddit API')
47+
}
48+
49+
return {
50+
Authorization: `Bearer ${params.accessToken}`,
51+
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
52+
Accept: 'application/json',
53+
}
54+
},
4255
},
4356

4457
transformResponse: async (response: Response, requestParams?: HotPostsParams) => {

0 commit comments

Comments
 (0)