Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"RSS Feed": "RSS Feed",
|
||||
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
|
||||
"New Item In Feed": "New Item In Feed",
|
||||
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
|
||||
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
|
||||
"RSS Feed URL": "RSS Feed URL",
|
||||
"RSS Feed URLs": "RSS Feed URLs",
|
||||
"Single RSS feed URL": "Single RSS feed URL",
|
||||
"List of RSS feed URLs": "List of RSS feed URLs"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Stay updated with RSS feeds": "Mit RSS-Feeds auf dem Laufenden bleiben",
|
||||
"New Item In Feed": "Neues Element im Feed",
|
||||
"New Items in Multiple Feeds": "Neue Artikel in mehreren Feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Läuft wenn ein neuer Eintrag im RSS-Feed hinzugefügt wird",
|
||||
"Runs when a new item is added in one of the RSS feed": "Läuft wenn ein neuer Eintrag in einem RSS-Feed hinzugefügt wird",
|
||||
"RSS Feed URL": "RSS-Feed-URL",
|
||||
"RSS Feed URLs": "RSS-Feed URLs",
|
||||
"Single RSS feed URL": "Einzelne RSS-Feed-URL",
|
||||
"List of RSS feed URLs": "Liste der RSS-Feed-URLs"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Stay updated with RSS feeds": "Mantente actualizado con RSS feeds",
|
||||
"New Item In Feed": "Nuevo elemento en la alimentación",
|
||||
"New Items in Multiple Feeds": "Nuevos artículos en Múltiples Ingresos",
|
||||
"Runs when a new item is added in the RSS feed": "Ejecuta cuando se añade un nuevo elemento en el feed RSS",
|
||||
"Runs when a new item is added in one of the RSS feed": "Ejecuta cuando se añade un nuevo elemento en uno de los canales RSS",
|
||||
"RSS Feed URL": "URL del feed RSS",
|
||||
"RSS Feed URLs": "URL del feed RSS",
|
||||
"Single RSS feed URL": "URL única de fuente RSS",
|
||||
"List of RSS feed URLs": "Lista de URLs de fuente RSS"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Stay updated with RSS feeds": "Restez à jour avec les fils RSS",
|
||||
"New Item In Feed": "Nouvel élément dans le flux",
|
||||
"New Items in Multiple Feeds": "Nouveaux éléments dans plusieurs flux",
|
||||
"Runs when a new item is added in the RSS feed": "Exécute lorsqu'un nouvel élément est ajouté dans le flux RSS",
|
||||
"Runs when a new item is added in one of the RSS feed": "Exécute lorsqu'un nouvel élément est ajouté dans un flux RSS",
|
||||
"RSS Feed URL": "URL du flux RSS",
|
||||
"RSS Feed URLs": "URL du flux RSS",
|
||||
"Single RSS feed URL": "URL du flux RSS unique",
|
||||
"List of RSS feed URLs": "Liste des URLs de flux RSS"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"RSS Feed": "RSS Feed",
|
||||
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
|
||||
"New Item In Feed": "New Item In Feed",
|
||||
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
|
||||
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
|
||||
"RSS Feed URL": "RSS Feed URL",
|
||||
"RSS Feed URLs": "RSS Feed URLs",
|
||||
"Single RSS feed URL": "Single RSS feed URL",
|
||||
"List of RSS feed URLs": "List of RSS feed URLs"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"RSS Feed": "RSS Feed",
|
||||
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
|
||||
"New Item In Feed": "New Item In Feed",
|
||||
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
|
||||
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
|
||||
"RSS Feed URL": "RSS Feed URL",
|
||||
"RSS Feed URLs": "RSS Feed URLs",
|
||||
"Single RSS feed URL": "Single RSS feed URL",
|
||||
"List of RSS feed URLs": "List of RSS feed URLs"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Stay updated with RSS feeds": "RSSフィードを更新してください",
|
||||
"New Item In Feed": "フィード内の新しいアイテム",
|
||||
"New Items in Multiple Feeds": "複数のフィードに新しい項目",
|
||||
"Runs when a new item is added in the RSS feed": "RSS フィードに新しいアイテムが追加されたときに実行されます。",
|
||||
"Runs when a new item is added in one of the RSS feed": "RSS フィードのいずれかに新しいアイテムが追加されたときに実行されます。",
|
||||
"RSS Feed URL": "RSSフィードのURL",
|
||||
"RSS Feed URLs": "RSSフィードのURL",
|
||||
"Single RSS feed URL": "Single RSS feed URL",
|
||||
"List of RSS feed URLs": "RSSフィードURL一覧"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Stay updated with RSS feeds": "Blijf op de hoogte van RSS-feeds",
|
||||
"New Item In Feed": "Nieuw item in feed",
|
||||
"New Items in Multiple Feeds": "Nieuwe items in meerdere feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Voert uit wanneer een nieuw item wordt toegevoegd in de RSS-feed",
|
||||
"Runs when a new item is added in one of the RSS feed": "Voert uit wanneer een nieuw item wordt toegevoegd in een van de RSS-feed",
|
||||
"RSS Feed URL": "RSS Feed URL",
|
||||
"RSS Feed URLs": "RSS Feed URL's",
|
||||
"Single RSS feed URL": "Single RSS feed URL",
|
||||
"List of RSS feed URLs": "Lijst van RSS feed URL's"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Stay updated with RSS feeds": "Mantenha-se atualizado com feeds RSS",
|
||||
"New Item In Feed": "Novo item no Feed",
|
||||
"New Items in Multiple Feeds": "Novos itens em Múltiplos Feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Executa quando um novo item é adicionado no feed RSS",
|
||||
"Runs when a new item is added in one of the RSS feed": "Executa quando um novo item é adicionado em um dos feeds RSS",
|
||||
"RSS Feed URL": "URL do Feed RSS",
|
||||
"RSS Feed URLs": "URLs do Feed RSS",
|
||||
"Single RSS feed URL": "Única URL de feed RSS",
|
||||
"List of RSS feed URLs": "Lista de URLs de feed RSS"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"RSS Feed": "RSS-канал",
|
||||
"Stay updated with RSS feeds": "Будьте в курсе RSS-каналов",
|
||||
"New Item In Feed": "Новый элемент в ленте новостей",
|
||||
"New Items in Multiple Feeds": "Новые элементы в нескольких каналах",
|
||||
"Runs when a new item is added in the RSS feed": "Выполняется при добавлении нового элемента в RSS-канал",
|
||||
"Runs when a new item is added in one of the RSS feed": "Выполняется при добавлении нового элемента в один из RSS-каналов",
|
||||
"RSS Feed URL": "URL RSS",
|
||||
"RSS Feed URLs": "URL-адреса RSS",
|
||||
"Single RSS feed URL": "URL одного канала RSS",
|
||||
"List of RSS feed URLs": "Список URL RSS-ленты"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
|
||||
"New Item In Feed": "New Item In Feed",
|
||||
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
|
||||
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
|
||||
"RSS Feed URL": "RSS Feed URL",
|
||||
"RSS Feed URLs": "RSS Feed URLs",
|
||||
"Single RSS feed URL": "Single RSS feed URL",
|
||||
"List of RSS feed URLs": "List of RSS feed URLs"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"RSS Feed": "RSS Feed",
|
||||
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
|
||||
"New Item In Feed": "New Item In Feed",
|
||||
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
|
||||
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
|
||||
"RSS Feed URL": "RSS Feed URL",
|
||||
"RSS Feed URLs": "RSS Feed URLs",
|
||||
"Single RSS feed URL": "Single RSS feed URL",
|
||||
"List of RSS feed URLs": "List of RSS feed URLs"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
|
||||
"New Item In Feed": "New Item In Feed",
|
||||
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
|
||||
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
|
||||
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
|
||||
"RSS Feed URL": "RSS Feed URL",
|
||||
"RSS Feed URLs": "RSS Feed URLs",
|
||||
"Single RSS feed URL": "Single RSS feed URL",
|
||||
"List of RSS feed URLs": "List of RSS feed URLs"
|
||||
}
|
||||
18
activepieces-fork/packages/pieces/community/rss/src/index.ts
Normal file
18
activepieces-fork/packages/pieces/community/rss/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { PieceAuth, createPiece } from '@activepieces/pieces-framework';
|
||||
import { rssNewItemTrigger } from './lib/triggers/new-item-trigger';
|
||||
import { rssNewItemListTrigger } from './lib/triggers/new-item-list-triggers';
|
||||
|
||||
export const rssFeed = createPiece({
|
||||
displayName: 'RSS Feed',
|
||||
description: 'Stay updated with RSS feeds',
|
||||
authors: ["Abdallah-Alwarawreh","kishanprmr","khaledmashaly","abuaboud", "Kevinyu-alan"],
|
||||
minimumSupportedRelease: '0.30.0',
|
||||
logoUrl: 'https://cdn.activepieces.com/pieces/rss.png',
|
||||
categories: [],
|
||||
auth: PieceAuth.None(),
|
||||
actions: [],
|
||||
triggers: [
|
||||
rssNewItemTrigger,
|
||||
rssNewItemListTrigger
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// Some RSS feeds use the id field, some use the guid field, and some use neither.
|
||||
export function getId(item: { id: string; guid: string }) {
|
||||
if (item === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (item.guid) {
|
||||
return item.guid;
|
||||
}
|
||||
if (item.id) {
|
||||
return item.id;
|
||||
}
|
||||
return JSON.stringify(item);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Property } from '@activepieces/pieces-framework';
|
||||
|
||||
export const rssFeedUrl = Property.ShortText({
|
||||
displayName: 'RSS Feed URL',
|
||||
description: 'Single RSS feed URL',
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const rssFeedUrls = Property.Array({
|
||||
displayName: 'RSS Feed URLs',
|
||||
description: 'List of RSS feed URLs',
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
export const sampleData = {
|
||||
title: 'AWS Cloud Quest: Container Services',
|
||||
description:
|
||||
'<p>This is the DIY challenge of the Container Services in AWS Cloud Quest.</p>\n\n<p></ol>',
|
||||
summary:
|
||||
'<p>This is the DIY challenge of the Container Services in AWS Cloud Quest.</ol>',
|
||||
date: '2023-03-08T21:57:48.000Z',
|
||||
pubdate: '2023-03-08T21:57:48.000Z',
|
||||
pubDate: '2023-03-08T21:57:48.000Z',
|
||||
link: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
|
||||
guid: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
|
||||
author: 'architec',
|
||||
comments: null,
|
||||
origlink: null,
|
||||
image: {},
|
||||
source: {},
|
||||
categories: ['aws'],
|
||||
enclosures: [],
|
||||
'rss:@': {},
|
||||
'rss:title': {
|
||||
'@': {},
|
||||
'#': 'AWS Cloud Quest: Container Services',
|
||||
},
|
||||
'dc:creator': {
|
||||
'@': {},
|
||||
'#': 'architec',
|
||||
},
|
||||
'rss:pubdate': {
|
||||
'@': {},
|
||||
'#': 'Wed, 08 Mar 2023 21:57:48 +0000',
|
||||
},
|
||||
'rss:link': {
|
||||
'@': {},
|
||||
'#': 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
|
||||
},
|
||||
permalink: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
|
||||
'rss:guid': {
|
||||
'@': {},
|
||||
'#': 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
|
||||
},
|
||||
'rss:description': {
|
||||
'@': {},
|
||||
'#': '<p>This is the DIY challenge of the Container Services in AWS Cloud Quest.</p>\n\n<p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pZTG6rga--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/993bebzvmiomak17lm98.png" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pZTG6rga--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/993bebzvmiomak17lm98.png" alt="Image description" width="880" height="419"></a></p>\n\n<h3>\n \n \n DIY Steps:\n</h3>\n\n<ol>\n<li>Repeat step 28-42</li>\n</ol>',
|
||||
},
|
||||
'rss:category': {
|
||||
'@': {},
|
||||
'#': 'aws',
|
||||
},
|
||||
meta: {
|
||||
'#ns': [
|
||||
{
|
||||
'xmlns:atom': 'http://www.w3.org/2005/Atom',
|
||||
},
|
||||
{
|
||||
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
|
||||
},
|
||||
],
|
||||
'@': [
|
||||
{
|
||||
'xmlns:atom': 'http://www.w3.org/2005/Atom',
|
||||
},
|
||||
{
|
||||
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
|
||||
},
|
||||
],
|
||||
'#xml': {
|
||||
version: '1.0',
|
||||
encoding: 'UTF-8',
|
||||
},
|
||||
'#type': 'rss',
|
||||
'#version': '2.0',
|
||||
title: 'DEV Community',
|
||||
description: 'The most recent home feed on DEV Community.',
|
||||
date: null,
|
||||
pubdate: null,
|
||||
pubDate: null,
|
||||
link: 'https://dev.to/',
|
||||
xmlurl: 'https://dev.to/feed/',
|
||||
xmlUrl: 'https://dev.to/feed/',
|
||||
author: null,
|
||||
language: 'en',
|
||||
favicon: null,
|
||||
copyright: null,
|
||||
generator: null,
|
||||
cloud: {},
|
||||
image: {},
|
||||
categories: [],
|
||||
'rss:@': {},
|
||||
'rss:title': {
|
||||
'@': {},
|
||||
'#': 'DEV Community',
|
||||
},
|
||||
'rss:description': {
|
||||
'@': {},
|
||||
'#': 'The most recent home feed on DEV Community.',
|
||||
},
|
||||
'rss:link': {
|
||||
'@': {},
|
||||
'#': 'https://dev.to/',
|
||||
},
|
||||
'atom:link': {
|
||||
'@': {
|
||||
rel: 'self',
|
||||
type: 'application/rss+xml',
|
||||
href: 'https://dev.to/feed/',
|
||||
},
|
||||
},
|
||||
'rss:language': {
|
||||
'@': {},
|
||||
'#': 'en',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
PieceAuth,
|
||||
PieceAuthProperty,
|
||||
PiecePropValueSchema,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { rssFeedUrls } from '../common/props';
|
||||
import FeedParser from 'feedparser';
|
||||
import axios from 'axios';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
import dayjs from 'dayjs';
|
||||
import { getId } from '../common/getId';
|
||||
import { sampleData } from '../common/sampleData';
|
||||
|
||||
type PollingProps = {
|
||||
rss_feed_urls: string[];
|
||||
};
|
||||
|
||||
export const rssNewItemListTrigger = createTrigger({
|
||||
name: 'new-item-list',
|
||||
displayName: 'New Items in Multiple Feeds',
|
||||
description: 'Runs when a new item is added in one of the RSS feed',
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: sampleData,
|
||||
auth: PieceAuth.None(),
|
||||
props: {
|
||||
rss_feed_urls: rssFeedUrls,
|
||||
},
|
||||
async test({ auth, propsValue, store, files }): Promise<unknown[]> {
|
||||
return await pollingHelper.test(polling, {
|
||||
auth,
|
||||
store: store,
|
||||
propsValue: propsValue as PollingProps,
|
||||
files: files,
|
||||
});
|
||||
},
|
||||
async onEnable({ auth, propsValue, store }): Promise<void> {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
auth,
|
||||
store: store,
|
||||
propsValue: propsValue as PollingProps,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable({ auth, propsValue, store }): Promise<void> {
|
||||
const lastFetchDate = await store.get<number>('_lastRssPublishDate');
|
||||
if (!isNil(lastFetchDate)) {
|
||||
await store.delete('_lastRssPublishDate');
|
||||
}
|
||||
await pollingHelper.onDisable(polling, {
|
||||
auth,
|
||||
store: store,
|
||||
propsValue: propsValue as PollingProps,
|
||||
});
|
||||
},
|
||||
|
||||
async run({ auth, propsValue, store, files }): Promise<unknown[]> {
|
||||
const lastFetchDate = await store.get<number>('_lastRssPublishDate');
|
||||
const newItems = (
|
||||
await pollingHelper.poll(polling, {
|
||||
auth,
|
||||
store: store,
|
||||
propsValue: propsValue as PollingProps,
|
||||
files: files,
|
||||
})
|
||||
).filter((f) => {
|
||||
if (isNil(lastFetchDate)) {
|
||||
return true;
|
||||
}
|
||||
const newItem = f as { pubdate: string; pubDate: string };
|
||||
const newDate = newItem.pubdate ?? newItem.pubDate;
|
||||
if (isNil(newDate)) {
|
||||
return true;
|
||||
}
|
||||
return dayjs(newDate).unix() > lastFetchDate;
|
||||
});
|
||||
let newFetchDateUnix = lastFetchDate;
|
||||
for (const item of newItems) {
|
||||
const newItem = item as { pubdate: string; pubDate: string };
|
||||
const newDate = newItem.pubdate ?? newItem.pubDate;
|
||||
if (!isNil(newDate)) {
|
||||
const newDateUnix = dayjs(newDate).unix();
|
||||
if (isNil(newFetchDateUnix) || newDateUnix > newFetchDateUnix) {
|
||||
newFetchDateUnix = newDateUnix;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isNil(newFetchDateUnix)) {
|
||||
await store.put('_lastRssPublishDate', newFetchDateUnix);
|
||||
}
|
||||
return newItems.sort((a, b) => {
|
||||
const aDate =
|
||||
(a as { pubdate: string; pubDate: string }).pubdate ??
|
||||
(a as { pubdate: string; pubDate: string }).pubDate;
|
||||
const bDate =
|
||||
(b as { pubdate: string; pubDate: string }).pubdate ??
|
||||
(b as { pubdate: string; pubDate: string }).pubDate;
|
||||
if (aDate && bDate) {
|
||||
const aUnix = dayjs(aDate).unix();
|
||||
const bUnix = dayjs(bDate).unix();
|
||||
if (aUnix === bUnix) {
|
||||
return newItems.indexOf(a) - newItems.indexOf(b);
|
||||
} else {
|
||||
return bUnix - aUnix;
|
||||
}
|
||||
} else {
|
||||
return newItems.indexOf(a) - newItems.indexOf(b);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
async function getRssItemsFromMultipleUrls(urls: string[]): Promise<any[]> {
|
||||
const allItems: any[] = [];
|
||||
await Promise.all(
|
||||
urls.map(async ( url ) => {
|
||||
try {
|
||||
const items = await getRssItems(url);
|
||||
allItems.push(...items);
|
||||
} catch (error) {
|
||||
console.error(`Error fetching RSS feed from ${url}:`, error);
|
||||
}
|
||||
})
|
||||
);
|
||||
return allItems;
|
||||
}
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<undefined>, PollingProps> = {
|
||||
strategy: DedupeStrategy.LAST_ITEM,
|
||||
items: async ({ store, propsValue }) => {
|
||||
const items = await getRssItemsFromMultipleUrls(propsValue.rss_feed_urls);
|
||||
return items.map((item) => ({
|
||||
id: getId(item),
|
||||
data: item,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
function getRssItems(url: string): Promise<any[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(url, {
|
||||
responseType: 'stream',
|
||||
})
|
||||
.then((response) => {
|
||||
const feedparser = new FeedParser({
|
||||
addmeta: true,
|
||||
});
|
||||
response.data.pipe(feedparser);
|
||||
const items: any[] = [];
|
||||
|
||||
feedparser.on('readable', () => {
|
||||
let item = feedparser.read();
|
||||
while (item) {
|
||||
items.push(item);
|
||||
item = feedparser.read();
|
||||
}
|
||||
});
|
||||
|
||||
feedparser.on('end', () => {
|
||||
resolve(items);
|
||||
});
|
||||
|
||||
feedparser.on('error', (error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
PieceAuth,
|
||||
PieceAuthProperty,
|
||||
PiecePropValueSchema,
|
||||
Store,
|
||||
StoreScope,
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { rssFeedUrl } from '../common/props';
|
||||
import FeedParser from 'feedparser';
|
||||
import axios from 'axios';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
import dayjs from 'dayjs';
|
||||
import { getId } from '../common/getId';
|
||||
import { sampleData } from '../common/sampleData';
|
||||
|
||||
export const rssNewItemTrigger = createTrigger({
|
||||
name: 'new-item',
|
||||
displayName: 'New Item In Feed',
|
||||
description: 'Runs when a new item is added in the RSS feed',
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: sampleData,
|
||||
auth: PieceAuth.None(),
|
||||
props: {
|
||||
rss_feed_url: rssFeedUrl,
|
||||
},
|
||||
async test({ auth, propsValue, store, files }): Promise<unknown[]> {
|
||||
return await pollingHelper.test(polling, {
|
||||
auth,
|
||||
store: store,
|
||||
propsValue: propsValue,
|
||||
files: files,
|
||||
});
|
||||
},
|
||||
async onEnable({ auth, propsValue, store }): Promise<void> {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
auth,
|
||||
store: store,
|
||||
propsValue: propsValue,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable({ auth, propsValue, store }): Promise<void> {
|
||||
const lastFetchDate = await store.get<number>('_lastRssPublishDate');
|
||||
if (!isNil(lastFetchDate)) {
|
||||
await store.delete('_lastRssPublishDate');
|
||||
}
|
||||
await pollingHelper.onDisable(polling, {
|
||||
auth,
|
||||
store: store,
|
||||
propsValue: propsValue,
|
||||
});
|
||||
},
|
||||
|
||||
async run({ auth, propsValue, store, files }): Promise<unknown[]> {
|
||||
const lastFetchDate = await store.get<number>('_lastRssPublishDate');
|
||||
const newItems = (
|
||||
await pollingHelper.poll(polling, {
|
||||
auth,
|
||||
store: store,
|
||||
propsValue: propsValue,
|
||||
files: files,
|
||||
})
|
||||
).filter((f) => {
|
||||
if (isNil(lastFetchDate)) {
|
||||
return true;
|
||||
}
|
||||
const newItem = f as { pubdate: string; pubDate: string };
|
||||
const newDate = newItem.pubdate ?? newItem.pubDate;
|
||||
if (isNil(newDate)) {
|
||||
return true;
|
||||
}
|
||||
return dayjs(newDate).unix() > lastFetchDate;
|
||||
});
|
||||
let newFetchDateUnix = lastFetchDate;
|
||||
for (const item of newItems) {
|
||||
const newItem = item as { pubdate: string; pubDate: string };
|
||||
const newDate = newItem.pubdate ?? newItem.pubDate;
|
||||
if (!isNil(newDate)) {
|
||||
const newDateUnix = dayjs(newDate).unix();
|
||||
if (isNil(newFetchDateUnix) || newDateUnix > newFetchDateUnix) {
|
||||
newFetchDateUnix = newDateUnix;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isNil(newFetchDateUnix)) {
|
||||
await store.put('_lastRssPublishDate', newFetchDateUnix);
|
||||
}
|
||||
return newItems.sort((a, b) => {
|
||||
const aDate =
|
||||
(a as { pubdate: string; pubDate: string }).pubdate ??
|
||||
(a as { pubdate: string; pubDate: string }).pubDate;
|
||||
const bDate =
|
||||
(b as { pubdate: string; pubDate: string }).pubdate ??
|
||||
(b as { pubdate: string; pubDate: string }).pubDate;
|
||||
if (aDate && bDate) {
|
||||
const aUnix = dayjs(aDate).unix();
|
||||
const bUnix = dayjs(bDate).unix();
|
||||
if (aUnix === bUnix) {
|
||||
return newItems.indexOf(a) - newItems.indexOf(b);
|
||||
} else {
|
||||
return bUnix - aUnix;
|
||||
}
|
||||
} else {
|
||||
return newItems.indexOf(a) - newItems.indexOf(b);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<undefined>,
|
||||
{ rss_feed_url: string }
|
||||
> = {
|
||||
strategy: DedupeStrategy.LAST_ITEM,
|
||||
items: async ({
|
||||
propsValue,
|
||||
}: {
|
||||
store: Store;
|
||||
propsValue: { rss_feed_url: string };
|
||||
}) => {
|
||||
const items = await getRssItems(propsValue.rss_feed_url);
|
||||
return items.map((item) => ({
|
||||
id: getId(item),
|
||||
data: item,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
function getRssItems(url: string): Promise<any[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(url, {
|
||||
responseType: 'stream',
|
||||
})
|
||||
.then((response) => {
|
||||
const feedparser = new FeedParser({
|
||||
addmeta: true,
|
||||
});
|
||||
response.data.pipe(feedparser);
|
||||
const items: any[] = [];
|
||||
|
||||
feedparser.on('readable', () => {
|
||||
let item = feedparser.read();
|
||||
while (item) {
|
||||
items.push(item);
|
||||
item = feedparser.read();
|
||||
}
|
||||
});
|
||||
|
||||
feedparser.on('end', () => {
|
||||
resolve(items);
|
||||
});
|
||||
|
||||
feedparser.on('error', (error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user