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:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,67 @@
import { scrapelessApiAuth } from '../../index';
import { createAction, Property } from '@activepieces/pieces-framework';
import { createScrapelessClient } from '../services/scrapeless-api-client';
export const crawlCrawlApi = createAction({
auth: scrapelessApiAuth,
name: 'crawl_crawl',
displayName: 'Crawl Data from All Pages',
description: 'Crawls a website and its linked pages to extract comprehensive data.',
props: {
url: Property.ShortText({
displayName: 'URL to Crawl',
description: 'The URL of the webpage to crawl.',
required: true,
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Number Of Subpages',
required: true,
defaultValue: 5,
})
},
async run({ propsValue, auth }) {
try {
const client = createScrapelessClient(auth.secret_text);
const url = propsValue.url;
const limit = propsValue.limit;
const browserOptions = {
"proxy_country": "ANY",
"session_name": "Crawl",
"session_recording": true,
"session_ttl": 900,
}
const response = await client.scrapingCrawl.crawl.crawlUrl(url, {
browserOptions,
limit
})
if (response.status === 'completed' && response.data) {
return {
success: true,
data: response.data || null,
}
} else {
return {
success: false,
error: 'Scraping failed',
error_type: 'ScrapingFailed',
timestamp: new Date().toISOString(),
}
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
success: false,
error: errorMessage,
error_type: error instanceof Error ? error.constructor.name : 'UnknownError',
timestamp: new Date().toISOString(),
};
}
},
});

View File

@@ -0,0 +1,60 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { scrapelessApiAuth } from '../../index';
import { createScrapelessClient } from '../services/scrapeless-api-client';
export const crawlScrapeApi = createAction({
auth: scrapelessApiAuth,
name: 'crawl_scrape',
displayName: 'Scrape Webpage Data',
description: 'Extracts data from a single webpage.',
props: {
url: Property.ShortText({
displayName: 'URL to Crawl',
description: 'The URL of the webpage to scrape.',
required: true,
})
},
async run({ propsValue, auth }) {
try {
const client = createScrapelessClient(auth.secret_text);
const url = propsValue.url;
const browserOptions = {
"proxy_country": "ANY",
"session_name": "Crawl",
"session_recording": true,
"session_ttl": 900,
}
const response = await client.scrapingCrawl.scrape.scrapeUrl(url, {
browserOptions
})
if (response.status === 'completed' && response.data) {
return {
success: true,
data: response.data || null,
}
} else {
return {
success: false,
error: 'Scraping failed',
error_type: 'ScrapingFailed',
timestamp: new Date().toISOString(),
}
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
success: false,
error: errorMessage,
error_type: error instanceof Error ? error.constructor.name : 'UnknownError',
timestamp: new Date().toISOString(),
};
}
},
});

View File

@@ -0,0 +1,76 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { scrapelessApiAuth } from '../../index';
import { createScrapelessClient } from '../services/scrapeless-api-client';
export const googleSearchApi = createAction({
auth: scrapelessApiAuth,
name: 'google_search_api',
displayName: 'Google Search',
description: 'Retrieves search result data for any query.',
props: {
q: Property.ShortText({
displayName: 'Search Query',
description: 'Parameter defines the query you want to search. You can use anything that you would use in a regular Google search. e.g. inurl:, site:, intitle:. We also support advanced search query parameters such as as_dt and as_eq.',
defaultValue: 'coffee',
required: true,
}),
hl: Property.ShortText({
displayName: 'Language',
description: "Parameter defines the language to use for the Google search. It's a two-letter language code. (e.g., en for English, es for Spanish, or fr for French).",
defaultValue: 'en',
required: false,
}),
gl: Property.ShortText({
displayName: 'Country',
description: "Parameter defines the country to use for the Google search. It's a two-letter country code. (e.g., us for the United States, uk for United Kingdom, or fr for France).",
defaultValue: 'us',
required: false,
}),
},
async run({ propsValue, auth }) {
try {
const client = createScrapelessClient(auth.secret_text);
const input = {
q: propsValue.q,
hl: propsValue.hl,
gl: propsValue.gl,
}
const response = await client.deepserp.createTask({
actor: 'scraper.google.search',
input,
});
if (response.status === 200) {
return {
success: true,
data: response.data || null,
}
}
while (true) {
await new Promise(resolve => setTimeout(resolve, 1000));
const result = await client.deepserp.getTaskResult(response.data.taskId);
if (result.status === 200) {
return {
success: true,
data: result.data || null,
}
}
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
success: false,
error: errorMessage,
error_type: error instanceof Error ? error.constructor.name : 'UnknownError',
timestamp: new Date().toISOString(),
};
}
},
});

View File

@@ -0,0 +1,96 @@
import { scrapelessApiAuth } from '../../index';
import { createAction, Property } from '@activepieces/pieces-framework';
import { googleTrendsDataTypeOptions } from '../constants';
import { createScrapelessClient } from '../services/scrapeless-api-client';
export const googleTrendsApi = createAction({
auth: scrapelessApiAuth,
name: 'google_trends_api',
displayName: 'Google Trends',
description: 'Access popular keyword and interest data from Google Trends.',
props: {
q: Property.ShortText({
displayName: 'Search Query',
description: 'Parameter defines the query or queries you want to search. You can use anything that you would use in a regular Google Trends search. The maximum number of queries per search is 5 (this only applies to `interest_over_time` and `compared_breakdown_by_region` data_type, other types of data will only accept 1 query per search).',
defaultValue: 'Mercedes-Benz,BMW X5',
required: true,
}),
data_type: Property.StaticDropdown({
displayName: 'Data Type',
description: 'Parameter defines the type of data you want to search. You can use anything that you would use in a regular Google Trends search. The maximum number of queries per search is 5 (this only applies to `interest_over_time` and `compared_breakdown_by_region` data_type, other types of data will only accept 1 query per search).',
required: true,
defaultValue: 'interest_over_time',
options: {
options: googleTrendsDataTypeOptions,
},
}),
date: Property.ShortText({
displayName: 'Date',
description: "The supported dates are: `now 1-H`, `now 4-H`, `now 1-d`, `now 7-d`, `today 1-m`, `today 3-m`, `today 12-m`, `today 5-y`, `all`.You can also pass custom values:Dates from 2004 to present: `yyyy-mm-dd yyyy-mm-dd` (e.g. `2021-10-15 2022-05-25`)\nDates with hours within a week range: `yyyy-mm-ddThh yyyy-mm-ddThh` (e.g. `2022-05-19T10 2022-05-24T22`). Hours will be calculated depending on the tz (time zone) parameter.",
required: true,
defaultValue: 'today 1-m',
}),
hl: Property.ShortText({
displayName: 'Language',
description: "Parameter defines the language to use for the Google Trends search. It's a two-letter language code. (e.g., `en` for English, `es` for Spanish, or `fr` for French).",
required: false,
defaultValue: 'en',
}),
tz: Property.ShortText({
displayName: 'Time zone',
description: "time zone offset. default is `420`.",
required: false,
defaultValue: '420',
}),
},
async run({ propsValue, auth }) {
try {
const client = createScrapelessClient(auth.secret_text);
const input = {
q: propsValue.q,
data_type: propsValue.data_type,
date: propsValue.date,
hl: propsValue.hl,
tz: propsValue.tz,
}
const response = await client.deepserp.createTask({
actor: 'scraper.google.trends',
input,
});
if (response.status === 200) {
return {
success: true,
data: response.data || null,
}
}
while (true) {
await new Promise(resolve => setTimeout(resolve, 1000));
const result = await client.deepserp.getTaskResult(response.data.taskId);
if (result.status === 200) {
return {
success: true,
data: result.data || null,
}
}
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
success: false,
error: errorMessage,
error_type: error instanceof Error ? error.constructor.name : 'UnknownError',
timestamp: new Date().toISOString(),
};
}
},
});

View File

@@ -0,0 +1,81 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { scrapelessApiAuth } from '../../index';
import { proxyCountryOptions } from '../constants';
import { createScrapelessClient } from '../services/scrapeless-api-client';
export const universalScrapingApi = createAction({
auth: scrapelessApiAuth,
name: 'universal_scraping_api',
displayName: 'Universal Scraping',
description: 'Seamlessly accesses protected or dynamic pages by handling anti-scraping systems automatically.',
props: {
url: Property.ShortText({
displayName: 'Target URL',
required: true,
}),
js_render: Property.Checkbox({
displayName: 'Js Render',
required: false,
defaultValue: true,
}),
headless: Property.Checkbox({
displayName: 'Headless',
required: false,
defaultValue: true,
}),
country: Property.StaticDropdown({
displayName: 'Country',
required: false,
defaultValue: 'ANY',
options: {
options: proxyCountryOptions,
},
}),
js_instructions: Property.Json({
displayName: 'Js Instructions',
required: false,
defaultValue: [{ "wait": 1000 }],
}),
block: Property.Json({
displayName: 'Block',
required: false,
defaultValue: { "resources": ["image", "font", "script"], "urls": ["https://example.com"] },
}),
},
async run({ propsValue, auth }) {
try {
const client = createScrapelessClient(auth.secret_text);
const input = {
url: propsValue.url,
js_render: propsValue.js_render,
headless: propsValue.headless,
js_instructions: propsValue.js_instructions,
block: propsValue.block,
}
const proxy = {
country: propsValue.country,
}
const response = await client.universal.scrape({
actor: 'unlocker.webunlocker',
input,
proxy,
})
return {
success: true,
data: response || null,
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
success: false,
error: errorMessage,
}
}
},
});

View File

@@ -0,0 +1,669 @@
export const googleTrendsDataTypeOptions = [
{
label: 'Auto complete',
value: 'autocomplete',
},
{
label: 'Interest over time',
value: 'interest_over_time',
},
{
label: 'Compared breakdown by region',
value: 'compared_breakdown_by_region',
},
{
label: 'Interest by region',
value: 'interest_by_subregion',
},
{
label: 'Related queries',
value: 'related_queries',
},
{
label: 'Related topics',
value: 'related_topics',
},
];
export const proxyCountryOptions = [
{
label: 'World Wide',
value: 'ANY',
},
{
label: 'Albania',
value: 'AL',
},
{
label: 'Algeria',
value: 'DZ',
},
{
label: 'Bhutan',
value: 'BT',
},
{
label: 'Bolivia',
value: 'BO',
},
{
label: 'Austria',
value: 'AT',
},
{
label: 'Bahrain',
value: 'BH',
},
{
label: 'Argentina',
value: 'AR',
},
{
label: 'Azerbaijan',
value: 'AZ',
},
{
label: 'Cyprus',
value: 'CY',
},
{
label: 'Burundi',
value: 'BI',
},
{
label: 'Cameroon',
value: 'CM',
},
{
label: 'Croatia',
value: 'HR',
},
{
label: 'Dominican Republic',
value: 'DO',
},
{
label: 'Australia',
value: 'AU',
},
{
label: 'Belarus',
value: 'BY',
},
{
label: 'Angola',
value: 'AO',
},
{
label: 'Bulgaria',
value: 'BG',
},
{
label: 'Democratic Republic of the Congo',
value: 'CD',
},
{
label: 'Aruba',
value: 'AW',
},
{
label: 'Armenia',
value: 'AM',
},
{
label: 'Bangladesh',
value: 'BD',
},
{
label: 'Belgium',
value: 'BE',
},
{
label: 'Cambodia',
value: 'KH',
},
{
label: 'Czech Republic',
value: 'CZ',
},
{
label: 'Denmark',
value: 'DK',
},
{
label: 'Burkina Faso',
value: 'BF',
},
{
label: 'Canada',
value: 'CA',
},
{
label: 'Cuba',
value: 'CU',
},
{
label: 'China',
value: 'CN',
},
{
label: 'Gambia The',
value: 'GM',
},
{
label: 'Georgia',
value: 'GE',
},
{
label: 'Chile',
value: 'CL',
},
{
label: 'Congo',
value: 'CG',
},
{
label: "Cote D'Ivoire (Ivory Coast)",
value: 'CI',
},
{
label: 'Gabon',
value: 'GA',
},
{
label: 'Colombia',
value: 'CO',
},
{
label: 'Costa Rica',
value: 'CR',
},
{
label: 'Indonesia',
value: 'ID',
},
{
label: 'French Polynesia',
value: 'PF',
},
{
label: 'Luxembourg',
value: 'LU',
},
{
label: 'Malta',
value: 'MT',
},
{
label: 'Mali',
value: 'ML',
},
{
label: 'Mexico',
value: 'MX',
},
{
label: 'Mauritius',
value: 'MU',
},
{
label: 'Madagascar',
value: 'MG',
},
{
label: 'Malawi',
value: 'MW',
},
{
label: 'Montenegro',
value: 'ME',
},
{
label: 'Netherlands',
value: 'NL',
},
{
label: 'Macau S.A.R.',
value: 'MO',
},
{
label: 'Moldova',
value: 'MD',
},
{
label: 'Malaysia',
value: 'MY',
},
{
label: 'Mongolia',
value: 'MN',
},
{
label: 'Man (Isle of)',
value: 'IM',
},
{
label: 'Fiji Islands',
value: 'FJ',
},
{
label: 'Finland',
value: 'FI',
},
{
label: 'France',
value: 'FR',
},
{
label: 'India',
value: 'IN',
},
{
label: 'Iceland',
value: 'IS',
},
{
label: 'North Macedonia',
value: 'MK',
},
{
label: 'New Zealand',
value: 'NZ',
},
{
label: 'Nicaragua',
value: 'NI',
},
{
label: 'Norway',
value: 'NO',
},
{
label: 'Haiti',
value: 'HT',
},
{
label: 'Honduras',
value: 'HN',
},
{
label: 'Hungary',
value: 'HU',
},
{
label: 'Guyana',
value: 'GY',
},
{
label: 'Hong Kong S.A.R.',
value: 'HK',
},
{
label: 'Nigeria',
value: 'NG',
},
{
label: 'Oman',
value: 'OM',
},
{
label: 'Peru',
value: 'PE',
},
{
label: 'Pakistan',
value: 'PK',
},
{
label: 'Palestinian Territory Occupied',
value: 'PS',
},
{
label: 'Papua new Guinea',
value: 'PG',
},
{
label: 'Poland',
value: 'PL',
},
{
label: 'Paraguay',
value: 'PY',
},
{
label: 'Panama',
value: 'PA',
},
{
label: 'Philippines',
value: 'PH',
},
{
label: 'Ethiopia',
value: 'ET',
},
{
label: 'Guatemala',
value: 'GT',
},
{
label: 'Greece',
value: 'GR',
},
{
label: 'Guinea-Bissau',
value: 'GW',
},
{
label: 'Estonia',
value: 'EE',
},
{
label: 'Guinea',
value: 'GN',
},
{
label: 'Saudi Arabia',
value: 'SA',
},
{
label: 'Serbia',
value: 'RS',
},
{
label: 'The Bahamas',
value: 'BS',
},
{
label: 'Ecuador',
value: 'EC',
},
{
label: 'Grenada',
value: 'GD',
},
{
label: 'Bosnia and Herzegovina',
value: 'BA',
},
{
label: 'Brazil',
value: 'BR',
},
{
label: 'Germany',
value: 'DE',
},
{
label: 'Ghana',
value: 'GH',
},
{
label: 'Rwanda',
value: 'RW',
},
{
label: 'Senegal',
value: 'SN',
},
{
label: 'Thailand',
value: 'TH',
},
{
label: 'Tajikistan',
value: 'TJ',
},
{
label: 'Tanzania',
value: 'TZ',
},
{
label: 'Egypt',
value: 'EG',
},
{
label: 'El Salvador',
value: 'SV',
},
{
label: 'Gibraltar',
value: 'GI',
},
{
label: 'Saint Vincent And The Grenadines',
value: 'VC',
},
{
label: 'Slovakia',
value: 'SK',
},
{
label: 'Slovenia',
value: 'SI',
},
{
label: 'Trinidad And Tobago',
value: 'TT',
},
{
label: 'Tunisia',
value: 'TN',
},
{
label: 'Turkey',
value: 'TR',
},
{
label: 'Uganda',
value: 'UG',
},
{
label: 'Turks And Caicos Islands',
value: 'TC',
},
{
label: 'Ukraine',
value: 'UA',
},
{
label: 'Togo',
value: 'TG',
},
{
label: 'United States',
value: 'US',
},
{
label: 'Portugal',
value: 'PT',
},
{
label: 'Puerto Rico',
value: 'PR',
},
{
label: 'United Arab Emirates',
value: 'AE',
},
{
label: 'United Kingdom',
value: 'GB',
},
{
label: 'Vietnam',
value: 'VN',
},
{
label: 'Kazakhstan',
value: 'KZ',
},
{
label: 'Israel',
value: 'IL',
},
{
label: 'Jamaica',
value: 'JM',
},
{
label: 'Virgin Islands (US)',
value: 'VI',
},
{
label: 'Kyrgyzstan',
value: 'KG',
},
{
label: 'Latvia',
value: 'LV',
},
{
label: 'Nepal',
value: 'NP',
},
{
label: 'Zambia',
value: 'ZM',
},
{
label: 'Libya',
value: 'LY',
},
{
label: 'Namibia',
value: 'NA',
},
{
label: 'Reunion',
value: 'RE',
},
{
label: 'Qatar',
value: 'QA',
},
{
label: 'Russia',
value: 'RU',
},
{
label: 'Jersey',
value: 'JE',
},
{
label: 'Kuwait',
value: 'KW',
},
{
label: 'Lithuania',
value: 'LT',
},
{
label: 'Morocco',
value: 'MA',
},
{
label: 'Ireland',
value: 'IE',
},
{
label: 'Italy',
value: 'IT',
},
{
label: 'Myanmar',
value: 'MM',
},
{
label: 'Zimbabwe',
value: 'ZW',
},
{
label: 'Lesotho',
value: 'LS',
},
{
label: 'Yemen',
value: 'YE',
},
{
label: 'Romania',
value: 'RO',
},
{
label: 'Mozambique',
value: 'MZ',
},
{
label: 'Japan',
value: 'JP',
},
{
label: 'Jordan',
value: 'JO',
},
{
label: 'Kenya',
value: 'KE',
},
{
label: 'Venezuela',
value: 'VE',
},
{
label: 'Uzbekistan',
value: 'UZ',
},
{
label: 'Uruguay',
value: 'UY',
},
{
label: 'Sri Lanka',
value: 'LK',
},
{
label: 'Sudan',
value: 'SD',
},
{
label: 'Switzerland',
value: 'CH',
},
{
label: 'South Korea',
value: 'KR',
},
{
label: 'Suriname',
value: 'SR',
},
{
label: 'South Africa',
value: 'ZA',
},
{
label: 'Spain',
value: 'ES',
},
{
label: 'Taiwan',
value: 'TW',
},
{
label: 'Sweden',
value: 'SE',
},
{
label: 'Somalia',
value: 'SO',
},
{
label: 'Lebanon',
value: 'LB',
},
{
label: 'Singapore',
value: 'SG',
},
];

View File

@@ -0,0 +1,8 @@
import { ScrapelessClient } from "@scrapeless-ai/sdk";
export function createScrapelessClient(auth: string) {
return new ScrapelessClient({
apiKey: auth,
timeout: 2 * 60 * 1000,
});
}

View File

@@ -0,0 +1,4 @@
export interface ValidationResult {
isValid: boolean;
errors: string[];
}

View File

@@ -0,0 +1,31 @@
import { httpClient, HttpMethod } from "@activepieces/pieces-common";
import { ValidationResult } from "../types";
export class ScrapelessValidator {
static async validateApiKey(apiKey: string): Promise<ValidationResult> {
const errors: string[] = [];
if (!apiKey) {
errors.push('API key is required');
} else if (typeof apiKey !== 'string') {
errors.push('API key must be a string');
}
const response =await httpClient.sendRequest({
url: 'https://api.scrapeless.com/api/v1/me',
method: HttpMethod.GET,
headers:{
'x-api-token':apiKey
}
});
if(response.status !== 200) {
errors.push('Invalid API key');
}
return {
isValid: errors.length === 0,
errors,
};
}
}