import { REDDIT_ERROR_TUPLE_SCHEMA, REDDIT_INBOX_ID_SCHEMA, REDDIT_JSON_STATUS_SCHEMA, REDDIT_MESSAGE_PAGE_SCHEMA, REDDIT_MESSAGE_SCHEMA, REDDIT_PROVIDER_MESSAGE_THING_SCHEMA, REDDIT_PROVIDER_MESSAGE_LISTING_SCHEMA, REDDIT_SUBREDDIT_NAME_SCHEMA, REDDIT_USERNAME_SCHEMA, toRedditMessage, toRedditPageInfo, } from "@automate.ax/integration-contracts/reddit" import { z } from "zod" import { defineAction } from "../../automation/actions" import { getRedditApi, REDDIT_ACCOUNT, redditAccountRequirement } from "./lib" const REDDIT_MESSAGE_RESPONSE_SCHEMA = z.object({ json: z.object({ data: z.object({ things: REDDIT_PROVIDER_MESSAGE_THING_SCHEMA.array() }), errors: REDDIT_ERROR_TUPLE_SCHEMA.array(), }), }) /** Hides Reddit posts from the connected account's default listings. */ export const hideRedditPosts = postStateAction("Hide Reddit posts", "api/hide") /** Restores hidden Reddit posts to the connected account's listings. */ export const unhideRedditPosts = postStateAction( "Unhide Reddit posts", "api/unhide", ) /** Subscribes the connected account to a subreddit. */ export const subscribeRedditSubreddit = subredditStateAction( "Subscribe to Reddit subreddit", "sub", ) /** Unsubscribes the connected account from a subreddit. */ export const unsubscribeRedditSubreddit = subredditStateAction( "Unsubscribe from Reddit subreddit", "unsub", ) /** Lists the connected account's Reddit inbox, unread items, or sent items. */ export const listRedditInbox = defineAction("List Reddit inbox") .account(REDDIT_ACCOUNT, redditAccountRequirement("privatemessages")) .input( z.object({ after: REDDIT_INBOX_ID_SCHEMA.optional(), limit: z.number().int().min(1).max(100).prefault(25), view: z.enum(["inbox", "sent", "unread"]).prefault("inbox"), }), ) .output(REDDIT_MESSAGE_PAGE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const response = await getRedditApi(account.secret).request( `message/${input.view}`, { query: { after: input.after, limit: input.limit }, responseSchema: REDDIT_PROVIDER_MESSAGE_LISTING_SCHEMA, }, ) return { messages: response.data.children.map(({ data }) => toRedditMessage(data)), pageInfo: toRedditPageInfo(response), } }) /** Marks selected Reddit inbox records as read. */ export const markRedditMessagesRead = messageStateAction( "Mark Reddit messages read", "api/read_message", ) /** Marks selected Reddit inbox records as unread. */ export const markRedditMessagesUnread = messageStateAction( "Mark Reddit messages unread", "api/unread_message", ) /** Sends a private message after the recipient has explicitly consented. */ export const sendRedditPrivateMessage = defineAction( "Send Reddit private message", ) .describe("Sends one consented private message to an existing Reddit user.") .account(REDDIT_ACCOUNT, redditAccountRequirement("privatemessages")) .input( z.object({ fromSubreddit: REDDIT_SUBREDDIT_NAME_SCHEMA.optional(), subject: z.string().trim().min(1).max(100), text: z.string().min(1), to: REDDIT_USERNAME_SCHEMA, }), ) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getRedditApi(account.secret).request("api/compose", { body: { api_type: "json", from_sr: input.fromSubreddit, subject: input.subject, text: input.text, to: input.to, }, method: "POST", responseSchema: REDDIT_JSON_STATUS_SCHEMA, }) }) /** Replies to one private-message thread. */ export const replyToRedditMessage = defineAction("Reply to Reddit message") .account(REDDIT_ACCOUNT, redditAccountRequirement("privatemessages")) .input( z.object({ messageId: z.string().regex(/^t4_[a-z0-9]+$/i), text: z.string().min(1), }), ) .output(REDDIT_MESSAGE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { // Keep the provider envelope named before requiring its created message. const response = await getRedditApi(account.secret).request("api/comment", { body: { api_type: "json", text: input.text, thing_id: input.messageId, }, method: "POST", responseSchema: REDDIT_MESSAGE_RESPONSE_SCHEMA, }) const message = response.json.data.things[0] if (!message) throw new Error("Reddit did not return the created reply.") return toRedditMessage(message.data) }) /** Builds a batch post visibility mutation. */ /** * @param label - Public action label. * @param path - Reddit API path. */ function postStateAction(label: string, path: string) { return defineAction(label) .account(REDDIT_ACCOUNT, redditAccountRequirement("report")) .input( z.object({ postIds: z .string() .regex(/^t3_[a-z0-9]+$/i) .array() .min(1) .max(100), }), ) .output(z.void()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { await updateRedditIdState(account.secret, path, input.postIds, 50) }) } /** Builds an idempotent subreddit subscription mutation. */ /** * @param label - Public action label. * @param action - Reddit subscription state. */ function subredditStateAction(label: string, action: "sub" | "unsub") { return defineAction(label) .account(REDDIT_ACCOUNT, redditAccountRequirement("subscribe")) .input(z.object({ subreddit: REDDIT_SUBREDDIT_NAME_SCHEMA })) .output(z.void()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { await getRedditApi(account.secret).request("api/subscribe", { body: { action, sr_name: input.subreddit }, method: "POST", responseSchema: z.undefined(), }) }) } /** Builds a batch inbox read-state mutation. */ /** * @param label - Public action label. * @param path - Reddit API path. */ function messageStateAction(label: string, path: string) { return defineAction(label) .account(REDDIT_ACCOUNT, redditAccountRequirement("privatemessages")) .input( z.object({ messageIds: REDDIT_INBOX_ID_SCHEMA.array().min(1).max(100) }), ) .output(z.void()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { await updateRedditIdState(account.secret, path, input.messageIds, 25) }) } /** Applies one Reddit ID-state mutation within provider batch limits. */ /** * @param secret - Connected Reddit account secret. * @param path - Reddit API mutation path. * @param ids - Thing fullnames to update. * @param batchSize - Maximum IDs accepted by one provider request. */ async function updateRedditIdState( secret: unknown, path: string, ids: readonly string[], batchSize: number, ) { const api = getRedditApi(secret) for (let index = 0; index < ids.length; index += batchSize) { await api.request(path, { body: { id: ids.slice(index, index + batchSize).join(",") }, method: "POST", responseSchema: z.undefined(), }) } }