Skip to content

Commit 2fcd07e

Browse files
authored
feat(triggers): added rss feed trigger & poller (#2267)
1 parent 0db5ba1 commit 2fcd07e

File tree

19 files changed

+884
-6
lines changed

19 files changed

+884
-6
lines changed

apps/docs/components/icons.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4170,3 +4170,32 @@ export function DuckDuckGoIcon(props: SVGProps<SVGSVGElement>) {
41704170
</svg>
41714171
)
41724172
}
4173+
4174+
export function RssIcon(props: SVGProps<SVGSVGElement>) {
4175+
return (
4176+
<svg
4177+
{...props}
4178+
width='24'
4179+
height='24'
4180+
viewBox='0 0 24 24'
4181+
fill='none'
4182+
xmlns='http://www.w3.org/2000/svg'
4183+
>
4184+
<path
4185+
d='M4 11C6.38695 11 8.67613 11.9482 10.364 13.636C12.0518 15.3239 13 17.6131 13 20'
4186+
stroke='currentColor'
4187+
strokeWidth='2'
4188+
strokeLinecap='round'
4189+
strokeLinejoin='round'
4190+
/>
4191+
<path
4192+
d='M4 4C8.24346 4 12.3131 5.68571 15.3137 8.68629C18.3143 11.6869 20 15.7565 20 20'
4193+
stroke='currentColor'
4194+
strokeWidth='2'
4195+
strokeLinecap='round'
4196+
strokeLinejoin='round'
4197+
/>
4198+
<circle cx='5' cy='19' r='1' fill='currentColor' />
4199+
</svg>
4200+
)
4201+
}

apps/docs/content/docs/en/triggers/index.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ Use the Start block for everything originating from the editor, deploy-to-API, o
3030
<Card title="Schedule" href="/triggers/schedule">
3131
Cron or interval based execution
3232
</Card>
33+
<Card title="RSS Feed" href="/triggers/rss">
34+
Monitor RSS and Atom feeds for new content
35+
</Card>
3336
</Cards>
3437

3538
## Quick Comparison
@@ -39,6 +42,7 @@ Use the Start block for everything originating from the editor, deploy-to-API, o
3942
| **Start** | Editor runs, deploy-to-API requests, or chat messages |
4043
| **Schedule** | Timer managed in schedule block |
4144
| **Webhook** | On inbound HTTP request |
45+
| **RSS Feed** | New item published to feed |
4246

4347
> The Start block always exposes `input`, `conversationId`, and `files` fields. Add custom fields to the input format for additional structured data.
4448
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"pages": ["index", "start", "schedule", "webhook"]
2+
"pages": ["index", "start", "schedule", "webhook", "rss"]
33
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: RSS Feed
3+
---
4+
5+
import { Callout } from 'fumadocs-ui/components/callout'
6+
import { Image } from '@/components/ui/image'
7+
8+
The RSS Feed block monitors RSS and Atom feeds – when new items are published, your workflow triggers automatically.
9+
10+
<div className="flex justify-center">
11+
<Image
12+
src="/static/blocks/rss.png"
13+
alt="RSS Feed Block"
14+
width={500}
15+
height={400}
16+
className="my-6"
17+
/>
18+
</div>
19+
20+
## Configuration
21+
22+
1. **Add RSS Feed Block** - Drag the RSS Feed block to start your workflow
23+
2. **Enter Feed URL** - Paste the URL of any RSS or Atom feed
24+
3. **Deploy** - Deploy your workflow to activate polling
25+
26+
Once deployed, the feed is checked every minute for new items.
27+
28+
## Output Fields
29+
30+
| Field | Type | Description |
31+
|-------|------|-------------|
32+
| `title` | string | Item title |
33+
| `link` | string | Item link |
34+
| `pubDate` | string | Publication date |
35+
| `item` | object | Raw item with all fields |
36+
| `feed` | object | Raw feed metadata |
37+
38+
Access mapped fields directly (`<rss.title>`) or use the raw objects for any field (`<rss.item.author>`, `<rss.feed.language>`).
39+
40+
## Use Cases
41+
42+
- **Content monitoring** - Track blogs, news sites, or competitor updates
43+
- **Podcast automation** - Trigger workflows when new episodes drop
44+
- **Release tracking** - Monitor GitHub releases, changelogs, or product updates
45+
- **Social aggregation** - Collect content from platforms that expose RSS feeds
46+
47+
<Callout>
48+
RSS triggers only fire for items published after you save the trigger. Existing feed items are not processed.
49+
</Callout>
18.9 KB
Loading
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { nanoid } from 'nanoid'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
import { verifyCronAuth } from '@/lib/auth/internal'
4+
import { acquireLock, releaseLock } from '@/lib/core/config/redis'
5+
import { createLogger } from '@/lib/logs/console/logger'
6+
import { pollRssWebhooks } from '@/lib/webhooks/rss-polling-service'
7+
8+
const logger = createLogger('RssPollingAPI')
9+
10+
export const dynamic = 'force-dynamic'
11+
export const maxDuration = 180 // Allow up to 3 minutes for polling to complete
12+
13+
const LOCK_KEY = 'rss-polling-lock'
14+
const LOCK_TTL_SECONDS = 180 // Same as maxDuration (3 min)
15+
16+
export async function GET(request: NextRequest) {
17+
const requestId = nanoid()
18+
logger.info(`RSS webhook polling triggered (${requestId})`)
19+
20+
let lockValue: string | undefined
21+
22+
try {
23+
const authError = verifyCronAuth(request, 'RSS webhook polling')
24+
if (authError) {
25+
return authError
26+
}
27+
28+
lockValue = requestId
29+
const locked = await acquireLock(LOCK_KEY, lockValue, LOCK_TTL_SECONDS)
30+
31+
if (!locked) {
32+
return NextResponse.json(
33+
{
34+
success: true,
35+
message: 'Polling already in progress – skipped',
36+
requestId,
37+
status: 'skip',
38+
},
39+
{ status: 202 }
40+
)
41+
}
42+
43+
const results = await pollRssWebhooks()
44+
45+
return NextResponse.json({
46+
success: true,
47+
message: 'RSS polling completed',
48+
requestId,
49+
status: 'completed',
50+
...results,
51+
})
52+
} catch (error) {
53+
logger.error(`Error during RSS polling (${requestId}):`, error)
54+
return NextResponse.json(
55+
{
56+
success: false,
57+
message: 'RSS polling failed',
58+
error: error instanceof Error ? error.message : 'Unknown error',
59+
requestId,
60+
},
61+
{ status: 500 }
62+
)
63+
} finally {
64+
await releaseLock(LOCK_KEY).catch(() => {})
65+
}
66+
}

apps/sim/app/api/webhooks/route.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,43 @@ export async function POST(request: NextRequest) {
544544
}
545545
// --- End Outlook specific logic ---
546546

547+
// --- RSS webhook setup ---
548+
if (savedWebhook && provider === 'rss') {
549+
logger.info(`[${requestId}] RSS provider detected. Setting up RSS webhook configuration.`)
550+
try {
551+
const { configureRssPolling } = await import('@/lib/webhooks/utils.server')
552+
const success = await configureRssPolling(savedWebhook, requestId)
553+
554+
if (!success) {
555+
logger.error(`[${requestId}] Failed to configure RSS polling, rolling back webhook`)
556+
await db.delete(webhook).where(eq(webhook.id, savedWebhook.id))
557+
return NextResponse.json(
558+
{
559+
error: 'Failed to configure RSS polling',
560+
details: 'Please try again',
561+
},
562+
{ status: 500 }
563+
)
564+
}
565+
566+
logger.info(`[${requestId}] Successfully configured RSS polling`)
567+
} catch (err) {
568+
logger.error(
569+
`[${requestId}] Error setting up RSS webhook configuration, rolling back webhook`,
570+
err
571+
)
572+
await db.delete(webhook).where(eq(webhook.id, savedWebhook.id))
573+
return NextResponse.json(
574+
{
575+
error: 'Failed to configure RSS webhook',
576+
details: err instanceof Error ? err.message : 'Unknown error',
577+
},
578+
{ status: 500 }
579+
)
580+
}
581+
}
582+
// --- End RSS specific logic ---
583+
547584
const status = targetWebhookId ? 200 : 201
548585
return NextResponse.json({ webhook: savedWebhook }, { status })
549586
} catch (error: any) {

apps/sim/blocks/blocks/rss.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { RssIcon } from '@/components/icons'
2+
import type { BlockConfig } from '@/blocks/types'
3+
import { getTrigger } from '@/triggers'
4+
5+
export const RssBlock: BlockConfig = {
6+
type: 'rss',
7+
name: 'RSS Feed',
8+
description: 'Monitor RSS feeds and trigger workflows when new items are published',
9+
longDescription:
10+
'Subscribe to any RSS or Atom feed and automatically trigger your workflow when new content is published. Perfect for monitoring blogs, news sites, podcasts, and any content that publishes an RSS feed.',
11+
category: 'triggers',
12+
bgColor: '#F97316',
13+
icon: RssIcon,
14+
triggerAllowed: true,
15+
16+
subBlocks: [...getTrigger('rss_poller').subBlocks],
17+
18+
tools: {
19+
access: [], // Trigger-only for now
20+
},
21+
22+
inputs: {},
23+
24+
outputs: {
25+
title: { type: 'string', description: 'Item title' },
26+
link: { type: 'string', description: 'Item link' },
27+
pubDate: { type: 'string', description: 'Publication date' },
28+
item: { type: 'json', description: 'Raw item object with all fields' },
29+
feed: { type: 'json', description: 'Raw feed object with all fields' },
30+
},
31+
32+
triggers: {
33+
enabled: true,
34+
available: ['rss_poller'],
35+
},
36+
}

apps/sim/blocks/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import { RedditBlock } from '@/blocks/blocks/reddit'
8989
import { ResendBlock } from '@/blocks/blocks/resend'
9090
import { ResponseBlock } from '@/blocks/blocks/response'
9191
import { RouterBlock } from '@/blocks/blocks/router'
92+
import { RssBlock } from '@/blocks/blocks/rss'
9293
import { S3Block } from '@/blocks/blocks/s3'
9394
import { SalesforceBlock } from '@/blocks/blocks/salesforce'
9495
import { ScheduleBlock } from '@/blocks/blocks/schedule'
@@ -229,6 +230,7 @@ export const registry: Record<string, BlockConfig> = {
229230
reddit: RedditBlock,
230231
resend: ResendBlock,
231232
response: ResponseBlock,
233+
rss: RssBlock,
232234
router: RouterBlock,
233235
s3: S3Block,
234236
salesforce: SalesforceBlock,

apps/sim/components/icons.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4170,3 +4170,32 @@ export function DuckDuckGoIcon(props: SVGProps<SVGSVGElement>) {
41704170
</svg>
41714171
)
41724172
}
4173+
4174+
export function RssIcon(props: SVGProps<SVGSVGElement>) {
4175+
return (
4176+
<svg
4177+
{...props}
4178+
width='24'
4179+
height='24'
4180+
viewBox='0 0 24 24'
4181+
fill='none'
4182+
xmlns='http://www.w3.org/2000/svg'
4183+
>
4184+
<path
4185+
d='M4 11C6.38695 11 8.67613 11.9482 10.364 13.636C12.0518 15.3239 13 17.6131 13 20'
4186+
stroke='currentColor'
4187+
strokeWidth='2'
4188+
strokeLinecap='round'
4189+
strokeLinejoin='round'
4190+
/>
4191+
<path
4192+
d='M4 4C8.24346 4 12.3131 5.68571 15.3137 8.68629C18.3143 11.6869 20 15.7565 20 20'
4193+
stroke='currentColor'
4194+
strokeWidth='2'
4195+
strokeLinecap='round'
4196+
strokeLinejoin='round'
4197+
/>
4198+
<circle cx='5' cy='19' r='1' fill='currentColor' />
4199+
</svg>
4200+
)
4201+
}

0 commit comments

Comments
 (0)