mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-10 07:27:57 -05:00
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>
This commit is contained in:
committed by
GitHub
parent
c635b19548
commit
60e2e6c735
@@ -31,6 +31,18 @@ export const RedditBlock: BlockConfig<
|
||||
],
|
||||
},
|
||||
|
||||
// Reddit OAuth Authentication
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Reddit Account',
|
||||
type: 'oauth-input',
|
||||
layout: 'full',
|
||||
provider: 'reddit',
|
||||
serviceId: 'reddit',
|
||||
requiredScopes: ['identity', 'read'],
|
||||
placeholder: 'Select Reddit account',
|
||||
},
|
||||
|
||||
// Common fields - appear for all actions
|
||||
{
|
||||
id: 'subreddit',
|
||||
@@ -151,27 +163,31 @@ export const RedditBlock: BlockConfig<
|
||||
},
|
||||
params: (inputs) => {
|
||||
const action = inputs.action || 'get_posts'
|
||||
const { credential, ...rest } = inputs
|
||||
|
||||
if (action === 'get_comments') {
|
||||
return {
|
||||
postId: inputs.postId,
|
||||
subreddit: inputs.subreddit,
|
||||
sort: inputs.commentSort,
|
||||
limit: inputs.commentLimit ? Number.parseInt(inputs.commentLimit) : undefined,
|
||||
postId: rest.postId,
|
||||
subreddit: rest.subreddit,
|
||||
sort: rest.commentSort,
|
||||
limit: rest.commentLimit ? Number.parseInt(rest.commentLimit) : undefined,
|
||||
credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subreddit: inputs.subreddit,
|
||||
sort: inputs.sort,
|
||||
limit: inputs.limit ? Number.parseInt(inputs.limit) : undefined,
|
||||
time: inputs.sort === 'top' ? inputs.time : undefined,
|
||||
subreddit: rest.subreddit,
|
||||
sort: rest.sort,
|
||||
limit: rest.limit ? Number.parseInt(rest.limit) : undefined,
|
||||
time: rest.sort === 'top' ? rest.time : undefined,
|
||||
credential: credential,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
action: { type: 'string', required: true },
|
||||
credential: { type: 'string', required: true },
|
||||
subreddit: { type: 'string', required: true },
|
||||
sort: { type: 'string', required: true },
|
||||
time: { type: 'string', required: false },
|
||||
|
||||
@@ -135,6 +135,7 @@ export const auth = betterAuth({
|
||||
'notion',
|
||||
'microsoft',
|
||||
'slack',
|
||||
'reddit',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -825,6 +826,57 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
|
||||
// Reddit provider
|
||||
{
|
||||
providerId: 'reddit',
|
||||
clientId: env.REDDIT_CLIENT_ID as string,
|
||||
clientSecret: env.REDDIT_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://www.reddit.com/api/v1/authorize',
|
||||
tokenUrl: 'https://www.reddit.com/api/v1/access_token',
|
||||
userInfoUrl: 'https://oauth.reddit.com/api/v1/me',
|
||||
scopes: ['identity', 'read'],
|
||||
responseType: 'code',
|
||||
pkce: false,
|
||||
accessType: 'offline',
|
||||
authentication: 'basic',
|
||||
prompt: 'consent',
|
||||
redirectURI: `${env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/reddit`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://oauth.reddit.com/api/v1/me', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
'User-Agent': 'sim-studio/1.0',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Error fetching Reddit user info:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const now = new Date()
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
name: data.name || 'Reddit User',
|
||||
email: `${data.name}@reddit.user`, // Reddit doesn't provide email in identity scope
|
||||
image: data.icon_img || null,
|
||||
emailVerified: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Reddit getUserInfo:', { error })
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'linear',
|
||||
clientId: env.LINEAR_CLIENT_ID as string,
|
||||
|
||||
@@ -103,6 +103,8 @@ export const env = createEnv({
|
||||
LINEAR_CLIENT_SECRET: z.string().optional(),
|
||||
SLACK_CLIENT_ID: z.string().optional(),
|
||||
SLACK_CLIENT_SECRET: z.string().optional(),
|
||||
REDDIT_CLIENT_ID: z.string().optional(),
|
||||
REDDIT_CLIENT_SECRET: z.string().optional(),
|
||||
SOCKET_SERVER_URL: z.string().url().optional(),
|
||||
SOCKET_PORT: z.number().optional(),
|
||||
PORT: z.number().optional(),
|
||||
|
||||
@@ -26,6 +26,8 @@ vi.mock('../env', () => ({
|
||||
LINEAR_CLIENT_SECRET: 'linear_client_secret',
|
||||
SLACK_CLIENT_ID: 'slack_client_id',
|
||||
SLACK_CLIENT_SECRET: 'slack_client_secret',
|
||||
REDDIT_CLIENT_ID: 'reddit_client_id',
|
||||
REDDIT_CLIENT_SECRET: 'reddit_client_secret',
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -80,6 +82,11 @@ describe('OAuth Token Refresh', () => {
|
||||
endpoint: 'https://discord.com/api/v10/oauth2/token',
|
||||
},
|
||||
{ name: 'Linear', providerId: 'linear', endpoint: 'https://api.linear.app/oauth/token' },
|
||||
{
|
||||
name: 'Reddit',
|
||||
providerId: 'reddit',
|
||||
endpoint: 'https://www.reddit.com/api/v1/access_token',
|
||||
},
|
||||
]
|
||||
|
||||
basicAuthProviders.forEach(({ name, providerId, endpoint }) => {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
MicrosoftTeamsIcon,
|
||||
NotionIcon,
|
||||
OutlookIcon,
|
||||
RedditIcon,
|
||||
SlackIcon,
|
||||
SupabaseIcon,
|
||||
xIcon,
|
||||
@@ -39,6 +40,7 @@ export type OAuthProvider =
|
||||
| 'microsoft'
|
||||
| 'linear'
|
||||
| 'slack'
|
||||
| 'reddit'
|
||||
| string
|
||||
|
||||
export type OAuthService =
|
||||
@@ -61,6 +63,7 @@ export type OAuthService =
|
||||
| 'outlook'
|
||||
| 'linear'
|
||||
| 'slack'
|
||||
| 'reddit'
|
||||
|
||||
export interface OAuthProviderConfig {
|
||||
id: OAuthProvider
|
||||
@@ -387,6 +390,23 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
},
|
||||
defaultService: 'slack',
|
||||
},
|
||||
reddit: {
|
||||
id: 'reddit',
|
||||
name: 'Reddit',
|
||||
icon: (props) => RedditIcon(props),
|
||||
services: {
|
||||
reddit: {
|
||||
id: 'reddit',
|
||||
name: 'Reddit',
|
||||
description: 'Access Reddit data and content from subreddits.',
|
||||
providerId: 'reddit',
|
||||
icon: (props) => RedditIcon(props),
|
||||
baseProviderIcon: (props) => RedditIcon(props),
|
||||
scopes: ['identity', 'read'],
|
||||
},
|
||||
},
|
||||
defaultService: 'reddit',
|
||||
},
|
||||
}
|
||||
|
||||
// Helper function to get a service by provider and service ID
|
||||
@@ -695,6 +715,18 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
|
||||
useBasicAuth: false,
|
||||
}
|
||||
}
|
||||
case 'reddit': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.REDDIT_CLIENT_ID,
|
||||
env.REDDIT_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported provider: ${provider}`)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@ export const getCommentsTool: ToolConfig<RedditCommentsParams, RedditCommentsRes
|
||||
description: 'Fetch comments from a specific Reddit post',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'reddit',
|
||||
additionalScopes: ['read'],
|
||||
},
|
||||
|
||||
params: {
|
||||
postId: {
|
||||
type: 'string',
|
||||
@@ -38,15 +44,21 @@ export const getCommentsTool: ToolConfig<RedditCommentsParams, RedditCommentsRes
|
||||
const sort = params.sort || 'confidence'
|
||||
const limit = Math.min(Math.max(1, params.limit || 50), 100)
|
||||
|
||||
// Build URL
|
||||
return `https://www.reddit.com/r/${subreddit}/comments/${params.postId}.json?sort=${sort}&limit=${limit}&raw_json=1`
|
||||
// Build URL using OAuth endpoint
|
||||
return `https://oauth.reddit.com/r/${subreddit}/comments/${params.postId}?sort=${sort}&limit=${limit}&raw_json=1`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
headers: (params: RedditCommentsParams) => {
|
||||
if (!params.accessToken?.trim()) {
|
||||
throw new Error('Access token is required for Reddit API')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, requestParams?: RedditCommentsParams) => {
|
||||
|
||||
@@ -7,6 +7,12 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
|
||||
description: 'Fetch posts from a subreddit with different sorting options',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'reddit',
|
||||
additionalScopes: ['read'],
|
||||
},
|
||||
|
||||
params: {
|
||||
subreddit: {
|
||||
type: 'string',
|
||||
@@ -38,8 +44,8 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
|
||||
const sort = params.sort || 'hot'
|
||||
const limit = Math.min(Math.max(1, params.limit || 10), 100)
|
||||
|
||||
// Build URL with appropriate parameters
|
||||
let url = `https://www.reddit.com/r/${subreddit}/${sort}.json?limit=${limit}&raw_json=1`
|
||||
// Build URL with appropriate parameters using OAuth endpoint
|
||||
let url = `https://oauth.reddit.com/r/${subreddit}/${sort}?limit=${limit}&raw_json=1`
|
||||
|
||||
// Add time parameter only for 'top' sorting
|
||||
if (sort === 'top' && params.time) {
|
||||
@@ -49,29 +55,54 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
|
||||
return url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
headers: (params: RedditPostsParams) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required for Reddit API')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, requestParams?: RedditPostsParams) => {
|
||||
try {
|
||||
// Check if response is OK
|
||||
if (!response.ok) {
|
||||
// Get response text for better error details
|
||||
const errorText = await response.text()
|
||||
console.error('Reddit API Error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: errorText,
|
||||
url: response.url,
|
||||
})
|
||||
|
||||
if (response.status === 403 || response.status === 429) {
|
||||
throw new Error('Reddit API access blocked or rate limited. Please try again later.')
|
||||
}
|
||||
throw new Error(`Reddit API returned ${response.status}: ${response.statusText}`)
|
||||
throw new Error(
|
||||
`Reddit API returned ${response.status}: ${response.statusText}. Body: ${errorText}`
|
||||
)
|
||||
}
|
||||
|
||||
// Attempt to parse JSON
|
||||
let data
|
||||
try {
|
||||
data = await response.json()
|
||||
} catch (_error) {
|
||||
throw new Error('Failed to parse Reddit API response: Response was not valid JSON')
|
||||
} catch (error) {
|
||||
const responseText = await response.text()
|
||||
console.error('Failed to parse Reddit API response as JSON:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
responseText,
|
||||
contentType: response.headers.get('content-type'),
|
||||
})
|
||||
throw new Error(
|
||||
`Failed to parse Reddit API response: Response was not valid JSON. Content: ${responseText}`
|
||||
)
|
||||
}
|
||||
|
||||
// Check if response contains error
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { RedditHotPostsResponse, RedditPost } from './types'
|
||||
interface HotPostsParams {
|
||||
subreddit: string
|
||||
limit?: number
|
||||
accessToken: string
|
||||
}
|
||||
|
||||
export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> = {
|
||||
@@ -12,6 +13,12 @@ export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> =
|
||||
description: 'Fetch the most popular (hot) posts from a specified subreddit.',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'reddit',
|
||||
additionalScopes: ['read'],
|
||||
},
|
||||
|
||||
params: {
|
||||
subreddit: {
|
||||
type: 'string',
|
||||
@@ -31,14 +38,20 @@ export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> =
|
||||
const subreddit = params.subreddit.trim().replace(/^r\//, '')
|
||||
const limit = Math.min(Math.max(1, params.limit || 10), 100)
|
||||
|
||||
return `https://www.reddit.com/r/${subreddit}/hot.json?limit=${limit}&raw_json=1`
|
||||
return `https://oauth.reddit.com/r/${subreddit}/hot?limit=${limit}&raw_json=1`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
headers: (params: HotPostsParams) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required for Reddit API')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, requestParams?: HotPostsParams) => {
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface RedditPostsParams {
|
||||
sort?: 'hot' | 'new' | 'top' | 'rising'
|
||||
limit?: number
|
||||
time?: 'day' | 'week' | 'month' | 'year' | 'all'
|
||||
accessToken?: string
|
||||
}
|
||||
|
||||
// Response for the generalized get_posts tool
|
||||
@@ -55,6 +56,7 @@ export interface RedditCommentsParams {
|
||||
subreddit: string
|
||||
sort?: 'confidence' | 'top' | 'new' | 'controversial' | 'old' | 'random' | 'qa'
|
||||
limit?: number
|
||||
accessToken?: string
|
||||
}
|
||||
|
||||
// Response for the get_comments tool
|
||||
|
||||
Reference in New Issue
Block a user