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,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