feat(reddit): OAuth2 credential and 5 authenticated operations (closes #21)#80
Open
jackulau wants to merge 1 commit into
Open
feat(reddit): OAuth2 credential and 5 authenticated operations (closes #21)#80jackulau wants to merge 1 commit into
jackulau wants to merge 1 commit into
Conversation
…espreadjam#21) Extends the existing reddit-monitor (unauthenticated public search) with authenticated operations for posting, commenting, replying, searching, and fetching comment trees. Mirrors the twitter-extended pattern established in the same directory. New credential: - redditCredential (OAuth2, scopes: identity, submit, read, history, mysubreddits) New nodes (all under packages/nodes/src/integrations/social/): - redditCreatePostNode — self / link / image posts with schema-level kind/url guard - redditCreateCommentNode — top-level post comments (auto t3_ prefix) - redditReplyToCommentNode — comment replies (auto t1_ prefix) - redditSearchPostsNode — query search with subreddit / sort / time / limit - redditGetPostCommentsNode — iterative flatten of nested comment trees with depth Shared helper reddit-client.ts wraps fetchWithRetry with Bearer auth and the required 'jam-nodes/1.0' User-Agent. Reddit's form-encoded /api/submit and /api/comment endpoints are handled via URLSearchParams; json.errors responses are surfaced as node failures rather than swallowed. NodeCredentials in @jam-nodes/core gains a reddit field so nodes can read context.credentials.reddit.accessToken in a typed way, matching the existing twitter / slack / googleSheets entries. 33 new vitest cases in reddit-extended.test.ts cover credential metadata, the missing-token path for each node, happy paths with mocked fetch, schema validation edge cases, thing_id prefix normalization, nested reply flattening, "more" stub skipping, and deep-chain stack safety.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #21.
Summary
Extends the existing
redditMonitorNode(unauthenticated publicsearch.jsonscraping) with five authenticated Reddit operations and the OAuth2 credential they share. Mirrors thetwitter-extendedpattern already established inpackages/nodes/src/integrations/social/— one file per operation, one shared*-client.tsauth helper, one consolidated*-extended.test.ts.Acceptance Criteria
What's in the diff
Credential
redditCredentialdefined inpackages/nodes/src/integrations/social/credentials.tsviadefineOAuth2Credential.namereddittypeoauth2(non-PKCE)authorizationUrlhttps://www.reddit.com/api/v1/authorizetokenUrlhttps://www.reddit.com/api/v1/access_tokenscopesidentity,submit,read,history,mysubredditsclientId,clientSecret,accessToken,refreshToken?,expiresAt?NodeCredentialsin@jam-nodes/corealso gains a typedredditentry so nodes can readcontext.credentials.reddit.accessTokenwithout casts — matching the existingtwitter/slack/googleSheetsentries.Operations
Each operation is a standalone file under
packages/nodes/src/integrations/social/:redditCreatePostNodePOST /api/submitkind: 'self' | 'link' | 'image'with.superRefineguard requiringtextforselfandurlforlink/image. Form-encoded body viaURLSearchParams. ReturnspostId,postName(t3_…fullname),url, synthesizedpermalink,title,subreddit.redditCreateCommentNodePOST /api/commentthing_id— if caller passes rawpostId(e.g.abc123), prependst3_; if already prefixed (t3_abc123/t1_abc123), passes through unchanged.redditReplyToCommentNodePOST /api/commentt1_prefix. Kept as a separate node for API clarity since the targetthing_idtype differs.redditSearchPostsNodeGET /searchorGET /r/<sr>/searchsort='new',time='day',limit=25(max 100). Sendsrestrict_sr=onwhen a subreddit is provided. Returns normalized post list, paginationaftertoken, andresultCount.redditGetPostCommentsNodeGET /comments/<postId>t3_prefix from the input id. Iterative BFS flatten of nested comment trees (not recursive — verified safe at 50-level depth in tests). Skipskind: 'more'stubs and gracefully handlesreplies: ""leaves. EmitsparentIdanddepthper comment.Shared client (
reddit-client.ts)redditRequest<T>(context, path, init)wrapsfetchWithRetry(which already handles 429 + Retry-After, 5xx backoff, timeouts, and throws on 401/403) and injects:Authorization: Bearer <accessToken>User-Agent: jam-nodes/1.0— Reddit rejects unrecognized / default-fetch UAsgetRedditAccessToken(context)returns the token ornullfor the per-node guard clause.formatRedditErrors(errors)flattens Reddit's[code, message, field]error tuples into readable strings, since/api/submitand/api/commentreturn200 OKwith non-emptyjson.errorsfor logical failures.Architectural alignment
CLAUDE.md, issue feat: add execution engine with configurable retry, caching, and timeout #37, PR feat: add an execution engine with configurable retry, caching, and timeout #39).z.any()oras any— every input and output has an explicit Zod schema.accessToken,fetchWithRetrysurfaces the 401 directly and the caller handles refresh.Test plan
Single consolidated file:
packages/nodes/src/integrations/social/reddit-extended.test.ts(33 tests, all passing). Uses thevi.stubGlobal('fetch', ...)pattern fromtwitter-extended.test.ts.Coverage:
&, Content-Type header).thing_idnormalization: four cases covering raw + prefixed input for both comment nodes.json.errorsnon-empty → node returnssuccess: falsewith the formatted error for all three write nodes.limit > 100, normalized post shape, empty result set.depth,kind: 'more'skipped, empty listing, 50-level deep chain (stack-safety).Verification
Run from the
packages/nodesdirectory:packages/corenpm run typecheckpasses.packages/nodesnpm run typechecksurfaces the same 18 pre-existing errors asmain(apify / google-sheets / slack), zero new from this change.packages/corenpm run buildpasses.