kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: add @agentic/reddit package
rodzic
fa4c88f0df
commit
c9fda50c09
|
@ -83,6 +83,7 @@
|
|||
"tools/polygon",
|
||||
"tools/predict-leads",
|
||||
"tools/proxycurl",
|
||||
"tools/reddit",
|
||||
"tools/rocketreach",
|
||||
"tools/searxng",
|
||||
"tools/serpapi",
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: Reddit
|
||||
description: Basic readonly Reddit API for getting top/hot/new/rising posts from subreddits.
|
||||
---
|
||||
|
||||
- package: `@agentic/reddit`
|
||||
- exports: `class RedditClient`, `namespace reddit`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/reddit/src/reddit-client.ts)
|
||||
- [reddit api docs](https://old.reddit.com/dev/api)
|
||||
|
||||
<Note>
|
||||
This client uses Reddit's free, publicly accessible legacy JSON API aimed at
|
||||
RSS feeds, so no API key or auth is required. With that being said, Reddit
|
||||
does impose rate limits on the API, so be considerate.
|
||||
</Note>
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/reddit
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/reddit
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/reddit
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { RedditClient } from '@agentic/reddit'
|
||||
|
||||
const reddit = new RedditClient()
|
||||
const result = await reddit.getSubredditPosts({
|
||||
subreddit: 'AskReddit',
|
||||
type: 'hot',
|
||||
limit: 10
|
||||
})
|
||||
```
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "@agentic/reddit",
|
||||
"version": "0.1.0",
|
||||
"description": "Agentic SDK for Reddit.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/reddit"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"ky": "catalog:",
|
||||
"p-throttle": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<p align="center">
|
||||
<a href="https://agentic.so">
|
||||
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||
</p>
|
||||
|
||||
# Agentic
|
||||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1 @@
|
|||
export * from './reddit-client'
|
|
@ -0,0 +1,452 @@
|
|||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
pick,
|
||||
sanitizeSearchParams
|
||||
} from '@agentic/core'
|
||||
import defaultKy, { type KyInstance } from 'ky'
|
||||
import { z } from 'zod'
|
||||
|
||||
export namespace reddit {
|
||||
export const BASE_URL = 'https://www.reddit.com'
|
||||
|
||||
export interface Post {
|
||||
id: string
|
||||
name: string // name is `t3_<id>`
|
||||
title: string
|
||||
subreddit: string
|
||||
selftext?: string
|
||||
author: string
|
||||
author_fullname: string
|
||||
url: string
|
||||
permalink: string
|
||||
thumbnail?: string
|
||||
thumbnail_width?: number
|
||||
thumbnail_height?: number
|
||||
score: number
|
||||
ups: number
|
||||
downs: number
|
||||
num_comments: number
|
||||
created_utc: number
|
||||
is_self: boolean
|
||||
is_video: boolean
|
||||
}
|
||||
|
||||
export interface FullPost {
|
||||
id: string
|
||||
name: string
|
||||
author: string
|
||||
title: string
|
||||
subreddit: string
|
||||
subreddit_name_prefixed: string
|
||||
score: number
|
||||
approved_at_utc: string | null
|
||||
selftext?: string
|
||||
author_fullname: string
|
||||
is_self: boolean
|
||||
saved: boolean
|
||||
url: string
|
||||
permalink: string
|
||||
mod_reason_title: string | null
|
||||
gilded: number
|
||||
clicked: boolean
|
||||
link_flair_richtext: any[]
|
||||
hidden: boolean
|
||||
pwls: number
|
||||
link_flair_css_class: string
|
||||
downs: number
|
||||
thumbnail_height: any
|
||||
top_awarded_type: any
|
||||
hide_score: boolean
|
||||
quarantine: boolean
|
||||
link_flair_text_color: string
|
||||
upvote_ratio: number
|
||||
author_flair_background_color: any
|
||||
subreddit_type: string
|
||||
ups: number
|
||||
total_awards_received: number
|
||||
media_embed?: any
|
||||
secure_media_embed?: any
|
||||
thumbnail_width: any
|
||||
author_flair_template_id: any
|
||||
is_original_content: boolean
|
||||
user_reports: any[]
|
||||
secure_media: any
|
||||
is_reddit_media_domain: boolean
|
||||
is_meta: boolean
|
||||
category: any
|
||||
link_flair_text: string
|
||||
can_mod_post: boolean
|
||||
approved_by: any
|
||||
is_created_from_ads_ui: boolean
|
||||
author_premium: boolean
|
||||
thumbnail?: string
|
||||
edited: boolean
|
||||
author_flair_css_class: any
|
||||
author_flair_richtext: any[]
|
||||
gildings?: any
|
||||
content_categories: any
|
||||
mod_note: any
|
||||
created: number
|
||||
link_flair_type: string
|
||||
wls: number
|
||||
removed_by_category: any
|
||||
banned_by: any
|
||||
author_flair_type: string
|
||||
domain: string
|
||||
allow_live_comments: boolean
|
||||
selftext_html: string
|
||||
likes: any
|
||||
suggested_sort: any
|
||||
banned_at_utc: any
|
||||
view_count: any
|
||||
archived: boolean
|
||||
no_follow: boolean
|
||||
is_crosspostable: boolean
|
||||
pinned: boolean
|
||||
over_18: boolean
|
||||
all_awardings: any[]
|
||||
awarders: any[]
|
||||
media_only: boolean
|
||||
link_flair_template_id: string
|
||||
can_gild: boolean
|
||||
spoiler: boolean
|
||||
locked: boolean
|
||||
author_flair_text: any
|
||||
treatment_tags: any[]
|
||||
visited: boolean
|
||||
removed_by: any
|
||||
num_reports: any
|
||||
distinguished: any
|
||||
subreddit_id: string
|
||||
author_is_blocked: boolean
|
||||
mod_reason_by: any
|
||||
removal_reason: any
|
||||
link_flair_background_color: string
|
||||
is_robot_indexable: boolean
|
||||
report_reasons: any
|
||||
discussion_type: any
|
||||
num_comments: number
|
||||
send_replies: boolean
|
||||
contest_mode: boolean
|
||||
mod_reports: any[]
|
||||
author_patreon_flair: boolean
|
||||
author_flair_text_color: any
|
||||
stickied: boolean
|
||||
subreddit_subscribers: number
|
||||
created_utc: number
|
||||
num_crossposts: number
|
||||
media?: any
|
||||
is_video: boolean
|
||||
|
||||
// preview images
|
||||
preview?: {
|
||||
enabled: boolean
|
||||
images: Array<{
|
||||
id: string
|
||||
source: Image
|
||||
resolutions: Image[]
|
||||
variants?: Record<
|
||||
string,
|
||||
{
|
||||
id: string
|
||||
source: Image
|
||||
resolutions: Image[]
|
||||
}
|
||||
>
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
url: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface PostT3 {
|
||||
kind: 't3'
|
||||
data: FullPost
|
||||
}
|
||||
|
||||
export interface PostListingResponse {
|
||||
kind: 'Listing'
|
||||
data: {
|
||||
after: string
|
||||
dist: number
|
||||
modhash: string
|
||||
geo_filter?: null
|
||||
children: PostT3[]
|
||||
}
|
||||
before?: null
|
||||
}
|
||||
|
||||
export type PostFilter = 'hot' | 'top' | 'new' | 'rising'
|
||||
|
||||
export type GeoFilter =
|
||||
| 'GLOBAL'
|
||||
| 'US'
|
||||
| 'AR'
|
||||
| 'AU'
|
||||
| 'BG'
|
||||
| 'CA'
|
||||
| 'CL'
|
||||
| 'CO'
|
||||
| 'HR'
|
||||
| 'CZ'
|
||||
| 'FI'
|
||||
| 'FR'
|
||||
| 'DE'
|
||||
| 'GR'
|
||||
| 'HU'
|
||||
| 'IS'
|
||||
| 'IN'
|
||||
| 'IE'
|
||||
| 'IT'
|
||||
| 'JP'
|
||||
| 'MY'
|
||||
| 'MX'
|
||||
| 'NZ'
|
||||
| 'PH'
|
||||
| 'PL'
|
||||
| 'PT'
|
||||
| 'PR'
|
||||
| 'RO'
|
||||
| 'RS'
|
||||
| 'SG'
|
||||
| 'ES'
|
||||
| 'SE'
|
||||
| 'TW'
|
||||
| 'TH'
|
||||
| 'TR'
|
||||
| 'GB'
|
||||
| 'US_WA'
|
||||
| 'US_DE'
|
||||
| 'US_DC'
|
||||
| 'US_WI'
|
||||
| 'US_WV'
|
||||
| 'US_HI'
|
||||
| 'US_FL'
|
||||
| 'US_WY'
|
||||
| 'US_NH'
|
||||
| 'US_NJ'
|
||||
| 'US_NM'
|
||||
| 'US_TX'
|
||||
| 'US_LA'
|
||||
| 'US_NC'
|
||||
| 'US_ND'
|
||||
| 'US_NE'
|
||||
| 'US_TN'
|
||||
| 'US_NY'
|
||||
| 'US_PA'
|
||||
| 'US_CA'
|
||||
| 'US_NV'
|
||||
| 'US_VA'
|
||||
| 'US_CO'
|
||||
| 'US_AK'
|
||||
| 'US_AL'
|
||||
| 'US_AR'
|
||||
| 'US_VT'
|
||||
| 'US_IL'
|
||||
| 'US_GA'
|
||||
| 'US_IN'
|
||||
| 'US_IA'
|
||||
| 'US_OK'
|
||||
| 'US_AZ'
|
||||
| 'US_ID'
|
||||
| 'US_CT'
|
||||
| 'US_ME'
|
||||
| 'US_MD'
|
||||
| 'US_MA'
|
||||
| 'US_OH'
|
||||
| 'US_UT'
|
||||
| 'US_MO'
|
||||
| 'US_MN'
|
||||
| 'US_MI'
|
||||
| 'US_RI'
|
||||
| 'US_KS'
|
||||
| 'US_MT'
|
||||
| 'US_MS'
|
||||
| 'US_SC'
|
||||
| 'US_KY'
|
||||
| 'US_OR'
|
||||
| 'US_SD'
|
||||
|
||||
export type TimePeriod = 'hour' | 'day' | 'week' | 'month' | 'year' | 'all'
|
||||
|
||||
export type GetSubredditPostsOptions = {
|
||||
subreddit: string
|
||||
type?: PostFilter
|
||||
|
||||
// Pagination size and offset (count)
|
||||
limit?: number
|
||||
count?: number
|
||||
|
||||
// Pagination by fullnames of posts
|
||||
before?: string
|
||||
after?: string
|
||||
|
||||
/**
|
||||
* Geographical filter. Only applicable to 'hot' posts.
|
||||
*/
|
||||
geo?: GeoFilter
|
||||
|
||||
/**
|
||||
* Filter by time period. Only applicable to 'top' posts.
|
||||
*/
|
||||
time?: TimePeriod
|
||||
}
|
||||
|
||||
export interface PostListingResult {
|
||||
subreddit: string
|
||||
type: PostFilter
|
||||
geo?: GeoFilter
|
||||
time?: TimePeriod
|
||||
posts: Post[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic readonly Reddit API for fetching top/hot/new/rising posts from subreddits.
|
||||
*
|
||||
* Uses Reddit's legacy JSON API aimed at RSS feeds.
|
||||
*
|
||||
* @see https://old.reddit.com/dev/api
|
||||
*/
|
||||
export class RedditClient extends AIFunctionsProvider {
|
||||
protected readonly ky: KyInstance
|
||||
protected readonly baseUrl: string
|
||||
|
||||
constructor({
|
||||
baseUrl = reddit.BASE_URL,
|
||||
userAgent = 'agentic-reddit-client/1.0.0',
|
||||
ky = defaultKy
|
||||
}: {
|
||||
baseUrl?: string
|
||||
userAgent?: string
|
||||
ky?: KyInstance
|
||||
} = {}) {
|
||||
super()
|
||||
|
||||
this.baseUrl = baseUrl
|
||||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: this.baseUrl,
|
||||
headers: {
|
||||
'User-Agent': userAgent
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches posts from a subreddit.
|
||||
*
|
||||
* @see https://old.reddit.com/dev/api/#GET_hot
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'reddit_get_subreddit_posts',
|
||||
description: 'Fetches posts from a subreddit.',
|
||||
inputSchema: z.object({
|
||||
subreddit: z.string().describe('The subreddit to fetch posts from.'),
|
||||
type: z
|
||||
.union([
|
||||
z.literal('hot'),
|
||||
z.literal('top'),
|
||||
z.literal('new'),
|
||||
z.literal('rising')
|
||||
])
|
||||
.optional()
|
||||
.describe('Type of posts to fetch (defaults to "hot").'),
|
||||
limit: z
|
||||
.number()
|
||||
.int()
|
||||
.max(100)
|
||||
.optional()
|
||||
.describe('Max number of posts to return (defaults to 5).'),
|
||||
count: z
|
||||
.number()
|
||||
.int()
|
||||
.optional()
|
||||
.describe('Number of posts to offset by (defaults to 0).'),
|
||||
time: z
|
||||
.union([
|
||||
z.literal('hour'),
|
||||
z.literal('day'),
|
||||
z.literal('week'),
|
||||
z.literal('month'),
|
||||
z.literal('year'),
|
||||
z.literal('all')
|
||||
])
|
||||
.optional()
|
||||
.describe(
|
||||
'Time period to filter posts by (defaults to "all"). Only applicable to "top" posts type.'
|
||||
)
|
||||
})
|
||||
})
|
||||
async getSubredditPosts(
|
||||
subredditOrOpts: string | reddit.GetSubredditPostsOptions
|
||||
): Promise<reddit.PostListingResult> {
|
||||
const params =
|
||||
typeof subredditOrOpts === 'string'
|
||||
? { subreddit: subredditOrOpts }
|
||||
: subredditOrOpts
|
||||
const { subreddit, type = 'hot', limit = 5, geo, time, ...opts } = params
|
||||
|
||||
const res = await this.ky
|
||||
.get(`r/${subreddit}/${type}.json`, {
|
||||
searchParams: sanitizeSearchParams({
|
||||
...opts,
|
||||
limit,
|
||||
g: type === 'hot' ? geo : undefined,
|
||||
t: type === 'top' ? time : undefined
|
||||
})
|
||||
})
|
||||
.json<reddit.PostListingResponse>()
|
||||
|
||||
return {
|
||||
subreddit,
|
||||
type,
|
||||
geo: type === 'hot' ? geo : undefined,
|
||||
time: type === 'top' ? time : undefined,
|
||||
posts: res.data.children.map((child) => {
|
||||
const post = child.data
|
||||
|
||||
// Trim the post data to only include the bare minimum
|
||||
// TODO: add preview images
|
||||
// TODO: add video media info
|
||||
return {
|
||||
...pick(
|
||||
post,
|
||||
'id',
|
||||
'name',
|
||||
'title',
|
||||
'subreddit',
|
||||
'selftext',
|
||||
'author',
|
||||
'author_fullname',
|
||||
'url',
|
||||
'permalink',
|
||||
'thumbnail',
|
||||
'thumbnail_width',
|
||||
'thumbnail_height',
|
||||
'score',
|
||||
'ups',
|
||||
'downs',
|
||||
'num_comments',
|
||||
'created_utc',
|
||||
'is_self',
|
||||
'is_video'
|
||||
),
|
||||
permalink: `${this.baseUrl}${post.permalink}`,
|
||||
thumbnail:
|
||||
post.thumbnail !== 'self' &&
|
||||
post.thumbnail !== 'default' &&
|
||||
post.thumbnail !== 'spoiler' &&
|
||||
post.thumbnail !== 'nsfw'
|
||||
? post.thumbnail
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -62,6 +62,7 @@
|
|||
"@agentic/polygon": "workspace:*",
|
||||
"@agentic/predict-leads": "workspace:*",
|
||||
"@agentic/proxycurl": "workspace:*",
|
||||
"@agentic/reddit": "workspace:*",
|
||||
"@agentic/rocketreach": "workspace:*",
|
||||
"@agentic/searxng": "workspace:*",
|
||||
"@agentic/serpapi": "workspace:*",
|
||||
|
|
|
@ -27,6 +27,7 @@ export * from '@agentic/perigon'
|
|||
export * from '@agentic/polygon'
|
||||
export * from '@agentic/predict-leads'
|
||||
export * from '@agentic/proxycurl'
|
||||
export * from '@agentic/reddit'
|
||||
export * from '@agentic/rocketreach'
|
||||
export * from '@agentic/searxng'
|
||||
export * from '@agentic/serpapi'
|
||||
|
|
|
@ -976,6 +976,21 @@ importers:
|
|||
specifier: 'catalog:'
|
||||
version: 3.24.2
|
||||
|
||||
packages/reddit:
|
||||
dependencies:
|
||||
'@agentic/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
ky:
|
||||
specifier: 'catalog:'
|
||||
version: 1.8.0
|
||||
p-throttle:
|
||||
specifier: 'catalog:'
|
||||
version: 6.2.0
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 3.24.2
|
||||
|
||||
packages/rocketreach:
|
||||
dependencies:
|
||||
'@agentic/core':
|
||||
|
@ -1146,6 +1161,9 @@ importers:
|
|||
'@agentic/proxycurl':
|
||||
specifier: workspace:*
|
||||
version: link:../proxycurl
|
||||
'@agentic/reddit':
|
||||
specifier: workspace:*
|
||||
version: link:../reddit
|
||||
'@agentic/rocketreach':
|
||||
specifier: workspace:*
|
||||
version: link:../rocketreach
|
||||
|
@ -5063,7 +5081,6 @@ packages:
|
|||
|
||||
libsql@0.4.7:
|
||||
resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==}
|
||||
cpu: [x64, arm64, wasm32]
|
||||
os: [darwin, linux, win32]
|
||||
|
||||
lilconfig@3.1.3:
|
||||
|
|
|
@ -208,6 +208,7 @@ Full docs are available at [agentic.so](https://agentic.so).
|
|||
| [Polygon](https://polygon.io) | `@agentic/polygon` | [docs](https://agentic.so/tools/polygon) | Stock market and company financial data. |
|
||||
| [PredictLeads](https://predictleads.com) | `@agentic/predict-leads` | [docs](https://agentic.so/tools/predict-leads) | In-depth company data including signals like fundraising events, hiring news, product launches, technologies used, etc. |
|
||||
| [Proxycurl](https://nubela.co/proxycurl) | `@agentic/proxycurl` | [docs](https://agentic.so/tools/proxycurl) | People and company data from LinkedIn & Crunchbase. |
|
||||
| [Reddit](https://old.reddit.com/dev/api) | `@agentic/reddit` | [docs](https://agentic.so/tools/reddit) | Basic readonly Reddit API for getting top/hot/new/rising posts from subreddits. |
|
||||
| [RocketReach](https://rocketreach.co/api/v2/docs) | `@agentic/rocketreach` | [docs](https://agentic.so/tools/rocketreach) | B2B person and company enrichment API. |
|
||||
| [Searxng](https://docs.searxng.org) | `@agentic/searxng` | [docs](https://agentic.so/tools/searxng) | OSS meta search engine capable of searching across many providers like Reddit, Google, Brave, Arxiv, Genius, IMDB, Rotten Tomatoes, Wikidata, Wolfram Alpha, YouTube, GitHub, [etc](https://docs.searxng.org/user/configured_engines.html#configured-engines). |
|
||||
| [SerpAPI](https://serpapi.com/search-api) | `@agentic/serpapi` | [docs](https://agentic.so/tools/serpapi) | Lightweight wrapper around SerpAPI for Google search. |
|
||||
|
|
Ładowanie…
Reference in New Issue