From a769c316263940479a167a7b6a79b80b0a390f4c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 28 Apr 2025 14:13:15 -0700 Subject: [PATCH] feat(tools): added additional reddit tools, docs --- docs/content/docs/tools/reddit.mdx | 52 ++++++++- sim/blocks/blocks/reddit.ts | 182 +++++++++++++++++++++++++++-- sim/tools/reddit/get_comments.ts | 110 +++++++++++++++++ sim/tools/reddit/get_posts.ts | 94 +++++++++++++++ sim/tools/reddit/index.ts | 4 + sim/tools/reddit/types.ts | 50 ++++++++ sim/tools/registry.ts | 4 +- 7 files changed, 481 insertions(+), 15 deletions(-) create mode 100644 sim/tools/reddit/get_comments.ts create mode 100644 sim/tools/reddit/get_posts.ts diff --git a/docs/content/docs/tools/reddit.mdx b/docs/content/docs/tools/reddit.mdx index fdcd02639b..e961e9ba81 100644 --- a/docs/content/docs/tools/reddit.mdx +++ b/docs/content/docs/tools/reddit.mdx @@ -1,6 +1,6 @@ --- title: Reddit -description: Fetch popular posts from Reddit +description: Access Reddit data and content --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -44,7 +44,7 @@ In Sim Studio, the Reddit integration enables your agents to programmatically ac ## Usage Instructions -Access Reddit data to retrieve the most popular (hot) posts from any subreddit. Get post titles, content, authors, scores, and more. +Access Reddit data to retrieve posts and comments from any subreddit. Get post titles, content, authors, scores, comments and more. @@ -67,6 +67,50 @@ Fetch the most popular (hot) posts from a specified subreddit. | --------- | ---- | | `subreddit` | string | +### `reddit_get_posts` + +Fetch posts from a subreddit with different sorting options + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `subreddit` | string | Yes | The name of the subreddit to fetch posts from \(without the r/ prefix\) | +| `sort` | string | No | Sort method for posts: | +| `limit` | number | No | Maximum number of posts to return \(default: 10, max: 100\) | +| `time` | string | No | Time filter for | + +#### Output + +| Parameter | Type | +| --------- | ---- | +| `subreddit` | string | + +### `reddit_get_comments` + +Fetch comments from a specific Reddit post + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `postId` | string | Yes | The ID of the Reddit post to fetch comments from | +| `subreddit` | string | Yes | The subreddit where the post is located \(without the r/ prefix\) | +| `sort` | string | No | Sort method for comments: | +| `limit` | number | No | Maximum number of comments to return \(default: 50, max: 100\) | + +#### Output + +| Parameter | Type | +| --------- | ---- | +| `post` | string | +| `title` | string | +| `author` | string | +| `selftext` | string | +| `created_utc` | string | +| `score` | string | +| `permalink` | string | + ## Block Configuration @@ -75,7 +119,7 @@ Fetch the most popular (hot) posts from a specified subreddit. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subreddit` | string | No | Subreddit - Enter subreddit name \(without r/\) | +| `action` | string | No | Action | @@ -86,6 +130,8 @@ Fetch the most popular (hot) posts from a specified subreddit. | `response` | object | Output from response | | ↳ `subreddit` | string | subreddit of the response | | ↳ `posts` | json | posts of the response | +| ↳ `post` | json | post of the response | +| ↳ `comments` | json | comments of the response | ## Notes diff --git a/sim/blocks/blocks/reddit.ts b/sim/blocks/blocks/reddit.ts index 69467ec796..70dc4efa33 100644 --- a/sim/blocks/blocks/reddit.ts +++ b/sim/blocks/blocks/reddit.ts @@ -1,57 +1,217 @@ import { RedditIcon } from '@/components/icons' -import { RedditHotPostsResponse } from '@/tools/reddit/types' +import { RedditHotPostsResponse, RedditPostsResponse, RedditCommentsResponse } from '@/tools/reddit/types' import { BlockConfig } from '../types' -export const RedditBlock: BlockConfig = { +export const RedditBlock: BlockConfig = { type: 'reddit', name: 'Reddit', - description: 'Fetch popular posts from Reddit', + description: 'Access Reddit data and content', longDescription: - 'Access Reddit data to retrieve the most popular (hot) posts from any subreddit. Get post titles, content, authors, scores, and more.', + 'Access Reddit data to retrieve posts and comments from any subreddit. Get post titles, content, authors, scores, comments and more.', category: 'tools', bgColor: '#FF5700', icon: RedditIcon, subBlocks: [ - // Subreddit input + // Action selection + { + id: 'action', + title: 'Action', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Get Posts', id: 'get_posts' }, + { label: 'Get Comments', id: 'get_comments' } + ], + }, + + // Common fields - appear for all actions { id: 'subreddit', title: 'Subreddit', type: 'short-input', layout: 'full', placeholder: 'Enter subreddit name (without r/)', + condition: { + field: 'action', + value: ['get_posts', 'get_comments'] + } + }, + + // Get Posts specific fields + { + id: 'sort', + title: 'Sort By', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Hot', id: 'hot' }, + { label: 'New', id: 'new' }, + { label: 'Top', id: 'top' }, + { label: 'Rising', id: 'rising' } + ], + condition: { + field: 'action', + value: 'get_posts' + } + }, + { + id: 'time', + title: 'Time Filter (for Top sort)', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Day', id: 'day' }, + { label: 'Week', id: 'week' }, + { label: 'Month', id: 'month' }, + { label: 'Year', id: 'year' }, + { label: 'All Time', id: 'all' } + ], + condition: { + field: 'action', + value: 'get_posts', + and: { + field: 'sort', + value: 'top' + } + } }, - // Limit input { id: 'limit', - title: 'Number of Top Posts', + title: 'Number of Posts', type: 'short-input', layout: 'full', placeholder: '10', + condition: { + field: 'action', + value: 'get_posts' + } }, + + // Get Comments specific fields + { + id: 'postId', + title: 'Post ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter post ID', + condition: { + field: 'action', + value: 'get_comments' + } + }, + { + id: 'commentSort', + title: 'Sort Comments By', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Confidence', id: 'confidence' }, + { label: 'Top', id: 'top' }, + { label: 'New', id: 'new' }, + { label: 'Controversial', id: 'controversial' }, + { label: 'Old', id: 'old' }, + { label: 'Random', id: 'random' }, + { label: 'Q&A', id: 'qa' } + ], + condition: { + field: 'action', + value: 'get_comments' + } + }, + { + id: 'commentLimit', + title: 'Number of Comments', + type: 'short-input', + layout: 'full', + placeholder: '50', + condition: { + field: 'action', + value: 'get_comments' + } + } ], tools: { - access: ['reddit_hot_posts'], + access: ['reddit_hot_posts', 'reddit_get_posts', 'reddit_get_comments'], config: { - tool: () => 'reddit_hot_posts', - }, + tool: (inputs) => { + const action = inputs.action || 'get_posts' + + if (action === 'get_comments') { + return 'reddit_get_comments' + } + + return 'reddit_get_posts' + }, + params: (inputs) => { + const action = inputs.action || 'get_posts' + + if (action === 'get_comments') { + return { + postId: inputs.postId, + subreddit: inputs.subreddit, + sort: inputs.commentSort, + limit: inputs.commentLimit ? parseInt(inputs.commentLimit) : undefined + } + } + + return { + subreddit: inputs.subreddit, + sort: inputs.sort, + limit: inputs.limit ? parseInt(inputs.limit) : undefined, + time: inputs.sort === 'top' ? inputs.time : undefined + } + } + } }, inputs: { + action: { + type: 'string', + required: true, + description: 'The action to perform: get_posts or get_comments', + }, subreddit: { type: 'string', required: true, - description: 'The name of the subreddit to fetch posts from (without the r/ prefix)', + description: 'The name of the subreddit to fetch data from (without the r/ prefix)', + }, + sort: { + type: 'string', + required: true, + description: 'Sort method for posts: "hot", "new", "top", or "rising" (default: "hot")', + }, + time: { + type: 'string', + required: false, + description: 'Time filter for "top" sorted posts: "hour", "day", "week", "month", "year", or "all" (default: "day")', }, limit: { type: 'number', required: false, description: 'Maximum number of posts to return (default: 10, max: 100)', }, + postId: { + type: 'string', + required: true, + description: 'The ID of the Reddit post to fetch comments from', + }, + commentSort: { + type: 'string', + required: false, + description: 'Sort method for comments: "confidence", "top", "new", "controversial", "old", "random", "qa" (default: "confidence")', + }, + commentLimit: { + type: 'number', + required: false, + description: 'Maximum number of comments to return (default: 50, max: 100)', + } }, outputs: { response: { type: { subreddit: 'string', posts: 'json', + post: 'json', + comments: 'json' }, }, }, diff --git a/sim/tools/reddit/get_comments.ts b/sim/tools/reddit/get_comments.ts new file mode 100644 index 0000000000..94c4d25c3d --- /dev/null +++ b/sim/tools/reddit/get_comments.ts @@ -0,0 +1,110 @@ +import fetch from 'node-fetch' +import { ToolConfig } from '../types' +import { RedditCommentsParams, RedditCommentsResponse } from './types' + +export const getCommentsTool: ToolConfig = { + id: 'reddit_get_comments', + name: 'Get Reddit Comments', + description: 'Fetch comments from a specific Reddit post', + version: '1.0.0', + + params: { + postId: { + type: 'string', + required: true, + description: 'The ID of the Reddit post to fetch comments from' + }, + subreddit: { + type: 'string', + required: true, + description: 'The subreddit where the post is located (without the r/ prefix)' + }, + sort: { + type: 'string', + required: false, + description: 'Sort method for comments: "confidence", "top", "new", "controversial", "old", "random", "qa" (default: "confidence")' + }, + limit: { + type: 'number', + required: false, + description: 'Maximum number of comments to return (default: 50, max: 100)' + } + }, + + request: { + url: (params: RedditCommentsParams) => { + // Sanitize inputs + const subreddit = params.subreddit.trim().replace(/^r\//, '') + 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}` + }, + method: 'GET', + headers: () => ({ + 'User-Agent': 'Sim Studio Reddit Tool/1.0' + }) + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + // Extract post data (first element in the array) + const postData = data[0]?.data?.children[0]?.data || {} + + // Extract and transform comments (second element in the array) + const commentsData = data[1]?.data?.children || [] + + // Recursive function to process nested comments + const processComments = (comments: any[]): any[] => { + return comments.map(comment => { + const commentData = comment.data + + // Skip non-comment items like "more" items + if (!commentData || comment.kind !== 't1') { + return null + } + + // Process nested replies if they exist + const replies = commentData.replies && + commentData.replies.data && + commentData.replies.data.children ? + processComments(commentData.replies.data.children) : + [] + + return { + id: commentData.id, + author: commentData.author, + body: commentData.body, + created_utc: commentData.created_utc, + score: commentData.score, + permalink: `https://www.reddit.com${commentData.permalink}`, + replies: replies.filter(Boolean) + } + }).filter(Boolean) + } + + const comments = processComments(commentsData) + + return { + success: true, + output: { + post: { + id: postData.id, + title: postData.title, + author: postData.author, + selftext: postData.selftext, + created_utc: postData.created_utc, + score: postData.score, + permalink: `https://www.reddit.com${postData.permalink}` + }, + comments: comments + } + } + }, + + transformError: (error) => { + return `Error fetching Reddit comments: ${error.message}` + } +} \ No newline at end of file diff --git a/sim/tools/reddit/get_posts.ts b/sim/tools/reddit/get_posts.ts new file mode 100644 index 0000000000..cab0e87d7d --- /dev/null +++ b/sim/tools/reddit/get_posts.ts @@ -0,0 +1,94 @@ +import fetch from 'node-fetch' +import { ToolConfig } from '../types' +import { RedditPostsParams, RedditPostsResponse } from './types' + +export const getPostsTool: ToolConfig = { + id: 'reddit_get_posts', + name: 'Get Reddit Posts', + description: 'Fetch posts from a subreddit with different sorting options', + version: '1.0.0', + + params: { + subreddit: { + type: 'string', + required: true, + description: 'The name of the subreddit to fetch posts from (without the r/ prefix)' + }, + sort: { + type: 'string', + required: false, + description: 'Sort method for posts: "hot", "new", "top", or "rising" (default: "hot")' + }, + limit: { + type: 'number', + required: false, + description: 'Maximum number of posts to return (default: 10, max: 100)' + }, + time: { + type: 'string', + required: false, + description: 'Time filter for "top" sorted posts: "day", "week", "month", "year", or "all" (default: "day")' + } + }, + + request: { + url: (params: RedditPostsParams) => { + // Sanitize inputs + const subreddit = params.subreddit.trim().replace(/^r\//, '') + 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}` + + // Add time parameter only for 'top' sorting + if (sort === 'top' && params.time) { + url += `&t=${params.time}` + } + + return url + }, + method: 'GET', + headers: () => ({ + 'User-Agent': 'Sim Studio Reddit Tool/1.0' + }) + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + // Extract subreddit name from response + const subredditName = data.data?.children[0]?.data?.subreddit || 'unknown' + + // Transform posts data + const posts = data.data?.children.map((child: any) => { + const post = child.data + return { + id: post.id, + title: post.title, + author: post.author, + url: post.url, + permalink: `https://www.reddit.com${post.permalink}`, + created_utc: post.created_utc, + score: post.score, + num_comments: post.num_comments, + is_self: post.is_self, + selftext: post.selftext, + thumbnail: post.thumbnail, + subreddit: post.subreddit + } + }) || [] + + return { + success: true, + output: { + subreddit: subredditName, + posts + } + } + }, + + transformError: (error) => { + return `Error fetching Reddit posts: ${error.message}` + } +} \ No newline at end of file diff --git a/sim/tools/reddit/index.ts b/sim/tools/reddit/index.ts index 514d1ef5b5..d35c0cdef7 100644 --- a/sim/tools/reddit/index.ts +++ b/sim/tools/reddit/index.ts @@ -1,3 +1,7 @@ import { hotPostsTool } from './hot_posts' +import { getPostsTool } from './get_posts' +import { getCommentsTool } from './get_comments' export const redditHotPostsTool = hotPostsTool +export const redditGetPostsTool = getPostsTool +export const redditGetCommentsTool = getCommentsTool diff --git a/sim/tools/reddit/types.ts b/sim/tools/reddit/types.ts index 47d91efadc..6c338a09ca 100644 --- a/sim/tools/reddit/types.ts +++ b/sim/tools/reddit/types.ts @@ -16,9 +16,59 @@ export interface RedditPost { subreddit_name_prefixed: string } +export interface RedditComment { + id: string + author: string + body: string + created_utc: number + score: number + permalink: string + replies: RedditComment[] +} + export interface RedditHotPostsResponse extends ToolResponse { output: { subreddit: string posts: RedditPost[] } } + +// Parameters for the generalized get_posts tool +export interface RedditPostsParams { + subreddit: string + sort?: 'hot' | 'new' | 'top' | 'rising' + limit?: number + time?: 'day' | 'week' | 'month' | 'year' | 'all' +} + +// Response for the generalized get_posts tool +export interface RedditPostsResponse extends ToolResponse { + output: { + subreddit: string + posts: RedditPost[] + } +} + +// Parameters for the get_comments tool +export interface RedditCommentsParams { + postId: string + subreddit: string + sort?: 'confidence' | 'top' | 'new' | 'controversial' | 'old' | 'random' | 'qa' + limit?: number +} + +// Response for the get_comments tool +export interface RedditCommentsResponse extends ToolResponse { + output: { + post: { + id: string + title: string + author: string + selftext?: string + created_utc: number + score: number + permalink: string + } + comments: RedditComment[] + } +} diff --git a/sim/tools/registry.ts b/sim/tools/registry.ts index 44e7efd606..1e4f90ce64 100644 --- a/sim/tools/registry.ts +++ b/sim/tools/registry.ts @@ -22,7 +22,7 @@ import { notionReadTool, notionWriteTool } from './notion' import { dalleTool, embeddingsTool as openAIEmbeddings } from './openai' import { perplexityChatTool } from './perplexity' import { pineconeFetchTool, pineconeGenerateEmbeddingsTool, pineconeSearchTextTool, pineconeSearchVectorTool, pineconeUpsertTextTool } from './pinecone' -import { redditHotPostsTool } from './reddit' +import { redditHotPostsTool, redditGetPostsTool, redditGetCommentsTool } from './reddit' import { opportunitiesTool as salesforceOpportunities } from './salesforce/opportunities' import { searchTool as serperSearch } from './serper' import { sheetsReadTool, sheetsUpdateTool, sheetsWriteTool } from './google_sheets' @@ -94,6 +94,8 @@ export const tools: Record = { exa_find_similar_links: exaFindSimilarLinksTool, exa_answer: exaAnswerTool, reddit_hot_posts: redditHotPostsTool, + reddit_get_posts: redditGetPostsTool, + reddit_get_comments: redditGetCommentsTool, google_drive_download: driveDownloadTool, google_drive_list: driveListTool, google_drive_upload: driveUploadTool,