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:
co-authored by
Claude Opus 4.5
parent
9848268d34
commit
3aa7199503
@@ -0,0 +1,108 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { foreplayCoApiCall } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { findAds as findAdsProperties } from '../properties';
|
||||
import { findAdsSchema } from '../schemas';
|
||||
import { foreplayCoAuth } from '../..';
|
||||
|
||||
export const findAds = createAction({
|
||||
auth: foreplayCoAuth,
|
||||
name: 'findAds',
|
||||
displayName: 'Find Ads',
|
||||
description:
|
||||
'Search and filter ads by text, dates, platforms, and categories.',
|
||||
props: findAdsProperties(),
|
||||
async run({ auth, propsValue }) {
|
||||
// Validate props using Zod schema
|
||||
const validation = findAdsSchema.safeParse(propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
const values = propsValue;
|
||||
|
||||
// Build query parameters properly handling arrays for API
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
// Add optional parameters if provided
|
||||
if (values['query']) {
|
||||
queryParams.append('query', String(values['query']));
|
||||
}
|
||||
if (values['start_date']) {
|
||||
queryParams.append('start_date', String(values['start_date']));
|
||||
}
|
||||
if (values['end_date']) {
|
||||
queryParams.append('end_date', String(values['end_date']));
|
||||
}
|
||||
if (values['order']) {
|
||||
queryParams.append('order', String(values['order']));
|
||||
}
|
||||
if (values['live']) {
|
||||
queryParams.append('live', String(values['live']));
|
||||
}
|
||||
|
||||
// Handle array parameters - repeat parameter name for each value
|
||||
if (values['display_format'] && values['display_format'].length > 0) {
|
||||
values['display_format'].forEach((format: unknown) => {
|
||||
queryParams.append('display_format', String(format));
|
||||
});
|
||||
}
|
||||
if (
|
||||
values['publisher_platform'] &&
|
||||
values['publisher_platform'].length > 0
|
||||
) {
|
||||
values['publisher_platform'].forEach((platform: unknown) => {
|
||||
queryParams.append('publisher_platform', String(platform));
|
||||
});
|
||||
}
|
||||
if (values['niches'] && values['niches'].length > 0) {
|
||||
values['niches'].forEach((niche: unknown) => {
|
||||
queryParams.append('niches', String(niche));
|
||||
});
|
||||
}
|
||||
if (values['market_target'] && values['market_target'].length > 0) {
|
||||
values['market_target'].forEach((target: unknown) => {
|
||||
queryParams.append('market_target', String(target));
|
||||
});
|
||||
}
|
||||
if (values['languages'] && values['languages'].length > 0) {
|
||||
values['languages'].forEach((language: unknown) => {
|
||||
queryParams.append('languages', String(language));
|
||||
});
|
||||
}
|
||||
|
||||
if (values['cursor']) {
|
||||
queryParams.append('cursor', String(values['cursor']));
|
||||
}
|
||||
if (values['limit']) {
|
||||
queryParams.append('limit', String(values['limit']));
|
||||
}
|
||||
|
||||
// Build the full URL with query parameters manually to handle arrays properly
|
||||
const queryString = queryParams.toString();
|
||||
const fullUrl = queryString
|
||||
? `/api/discovery/ads?${queryString}`
|
||||
: '/api/discovery/ads';
|
||||
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: fullUrl,
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
// Check if the response is successful
|
||||
if (responseBody.metadata && responseBody.metadata.success === true) {
|
||||
// Return just the ads data for clean automation workflows
|
||||
return responseBody.data;
|
||||
} else {
|
||||
// Handle error responses by throwing an error
|
||||
const errorMessage =
|
||||
responseBody.error ||
|
||||
responseBody.metadata?.message ||
|
||||
'Failed to find ads';
|
||||
throw new Error(`Foreplay.co API Error: ${errorMessage}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { foreplayCoApiCall } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { findBoards as findBoardsProperties } from '../properties';
|
||||
import { findBoardsSchema } from '../schemas';
|
||||
import { foreplayCoAuth } from '../..';
|
||||
|
||||
export const findBoards = createAction({
|
||||
name: 'findBoards',
|
||||
displayName: 'Find Boards',
|
||||
description: 'Get all boards for the authenticated user with pagination.',
|
||||
props: findBoardsProperties(),
|
||||
auth: foreplayCoAuth,
|
||||
async run({ auth, propsValue }) {
|
||||
// Validate props using Zod schema
|
||||
const validation = findBoardsSchema.safeParse(propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
const values = propsValue;
|
||||
const queryParams: Record<string, string> = {};
|
||||
|
||||
// Add optional parameters if provided
|
||||
if (values['offset'] !== undefined) {
|
||||
queryParams['offset'] = String(values['offset']);
|
||||
}
|
||||
if (values['limit'] !== undefined) {
|
||||
queryParams['limit'] = String(values['limit']);
|
||||
}
|
||||
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/api/boards',
|
||||
queryParams,
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
// Check if the response is successful
|
||||
if (responseBody.metadata && responseBody.metadata.success === true) {
|
||||
// Return just the boards data for clean automation workflows
|
||||
return responseBody.data;
|
||||
} else {
|
||||
// Handle error responses by throwing an error
|
||||
const errorMessage =
|
||||
responseBody.error ||
|
||||
responseBody.metadata?.message ||
|
||||
'Failed to retrieve boards';
|
||||
throw new Error(`Foreplay.co API Error: ${errorMessage}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { foreplayCoApiCall } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { findBrands as findBrandsProperties } from '../properties';
|
||||
import { findBrandsSchema } from '../schemas';
|
||||
import { foreplayCoAuth } from '../..';
|
||||
|
||||
export const findBrands = createAction({
|
||||
name: 'findBrands',
|
||||
displayName: 'Find Brands',
|
||||
description: 'Search for brands by name with fuzzy matching.',
|
||||
props: findBrandsProperties(),
|
||||
auth: foreplayCoAuth,
|
||||
async run({ auth, propsValue }) {
|
||||
// Validate props using Zod schema
|
||||
const validation = findBrandsSchema.safeParse(propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
const values = propsValue;
|
||||
const queryParams: Record<string, string> = {
|
||||
query: String(values['query']),
|
||||
};
|
||||
|
||||
// Add optional limit parameter if provided
|
||||
if (values['limit']) {
|
||||
queryParams['limit'] = String(values['limit']);
|
||||
}
|
||||
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/api/discovery/brands',
|
||||
queryParams,
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
// Check if the response is successful
|
||||
if (responseBody.metadata && responseBody.metadata.success === true) {
|
||||
// Return just the brands data for clean automation workflows
|
||||
return responseBody.data;
|
||||
} else {
|
||||
// Handle error responses by throwing an error
|
||||
const errorMessage =
|
||||
responseBody.error ||
|
||||
responseBody.metadata?.message ||
|
||||
'Failed to find brands';
|
||||
throw new Error(`Foreplay.co API Error: ${errorMessage}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { foreplayCoApiCall } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { getAdById as getAdByIdProperties } from '../properties';
|
||||
import { getAdByIdSchema } from '../schemas';
|
||||
import { foreplayCoAuth } from '../..';
|
||||
|
||||
export const getAdById = createAction({
|
||||
name: 'getAdById',
|
||||
displayName: 'Get Ad by ID',
|
||||
description: 'Get detailed information about a specific ad by its ID.',
|
||||
props: getAdByIdProperties(),
|
||||
auth: foreplayCoAuth,
|
||||
async run({ auth, propsValue }) {
|
||||
// Validate props using Zod schema
|
||||
const validation = getAdByIdSchema.safeParse(propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
const adId = propsValue.ad_id;
|
||||
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: `/api/ad/${adId}`,
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
// Check if the response is successful
|
||||
if (responseBody.metadata && responseBody.metadata.success === true) {
|
||||
// Return just the ad data for clean automation workflows
|
||||
return responseBody.data;
|
||||
} else {
|
||||
// Handle error responses by throwing an error
|
||||
const errorMessage =
|
||||
responseBody.error ||
|
||||
responseBody.metadata?.message ||
|
||||
'Failed to retrieve ad';
|
||||
throw new Error(`Foreplay.co API Error: ${errorMessage}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { foreplayCoApiCall } from '../common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { getAdsByPage as getAdsByPageProperties } from '../properties';
|
||||
import { getAdsByPageSchema } from '../schemas';
|
||||
import { foreplayCoAuth } from '../..';
|
||||
|
||||
export const getAdsByPage = createAction({
|
||||
name: 'getAdsByPage',
|
||||
displayName: 'Get Ads by Page',
|
||||
description:
|
||||
'Get all ads for a Facebook Page ID with filtering and pagination.',
|
||||
props: getAdsByPageProperties(),
|
||||
auth: foreplayCoAuth,
|
||||
async run({ auth, propsValue }) {
|
||||
// Validate props using Zod schema
|
||||
const validation = getAdsByPageSchema.safeParse(propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
const values = propsValue;
|
||||
|
||||
// Build query parameters properly handling arrays for API
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('page_id', String(values.page_id));
|
||||
|
||||
// Add optional parameters if provided
|
||||
if (values.start_date) {
|
||||
queryParams.append('start_date', String(values.start_date));
|
||||
}
|
||||
if (values.end_date) {
|
||||
queryParams.append('end_date', String(values.end_date));
|
||||
}
|
||||
if (values.order) {
|
||||
queryParams.append('order', String(values.order));
|
||||
}
|
||||
if (values.live) {
|
||||
queryParams.append('live', String(values.live));
|
||||
}
|
||||
|
||||
// Handle array parameters - repeat parameter name for each value
|
||||
if (values.display_format && values.display_format.length > 0) {
|
||||
values.display_format.forEach((format: unknown) => {
|
||||
queryParams.append('display_format', String(format));
|
||||
});
|
||||
}
|
||||
if (values.publisher_platform && values.publisher_platform.length > 0) {
|
||||
values.publisher_platform.forEach((platform: unknown) => {
|
||||
queryParams.append('publisher_platform', String(platform));
|
||||
});
|
||||
}
|
||||
if (values.niches && values.niches.length > 0) {
|
||||
values.niches.forEach((niche: unknown) => {
|
||||
queryParams.append('niches', String(niche));
|
||||
});
|
||||
}
|
||||
if (values.market_target && values.market_target.length > 0) {
|
||||
values.market_target.forEach((target: unknown) => {
|
||||
queryParams.append('market_target', String(target));
|
||||
});
|
||||
}
|
||||
if (values.languages && values.languages.length > 0) {
|
||||
values.languages.forEach((language: unknown) => {
|
||||
queryParams.append('languages', String(language));
|
||||
});
|
||||
}
|
||||
|
||||
if (values.cursor) {
|
||||
queryParams.append('cursor', String(values.cursor));
|
||||
}
|
||||
if (values.limit) {
|
||||
queryParams.append('limit', String(values.limit));
|
||||
}
|
||||
|
||||
// Build the full URL with query parameters manually to handle arrays properly
|
||||
const queryString = queryParams.toString();
|
||||
const fullUrl = queryString
|
||||
? `/api/brand/getAdsByPageId?${queryString}`
|
||||
: '/api/brand/getAdsByPageId';
|
||||
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: fullUrl,
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
// Check if the response is successful
|
||||
if (responseBody.metadata && responseBody.metadata.success === true) {
|
||||
// Return just the ads data for clean automation workflows
|
||||
return responseBody.data;
|
||||
} else {
|
||||
// Handle error responses by throwing an error
|
||||
const errorMessage =
|
||||
responseBody.error ||
|
||||
responseBody.metadata?.message ||
|
||||
'Failed to retrieve ads for page';
|
||||
throw new Error(`Foreplay.co API Error: ${errorMessage}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
export { getAdById } from './get-ad-by-id';
|
||||
export { getAdsByPage } from './get-ads-by-page';
|
||||
export { findBrands } from './find-brands';
|
||||
export { findAds } from './find-ads';
|
||||
export { findBoards } from './find-boards';
|
||||
@@ -0,0 +1,32 @@
|
||||
import { httpClient, HttpMethod, AuthenticationType } from "@activepieces/pieces-common";
|
||||
import { AppConnectionValueForAuthProperty } from "@activepieces/pieces-framework";
|
||||
import { foreplayCoAuth } from "..";
|
||||
|
||||
export interface ForeplayCoApiCallProps {
|
||||
apiKey: AppConnectionValueForAuthProperty<typeof foreplayCoAuth>;
|
||||
method: HttpMethod;
|
||||
resourceUri: string;
|
||||
queryParams?: Record<string, string>;
|
||||
body?: any;
|
||||
}
|
||||
|
||||
export async function foreplayCoApiCall({
|
||||
apiKey,
|
||||
method,
|
||||
resourceUri,
|
||||
queryParams,
|
||||
body,
|
||||
}: ForeplayCoApiCallProps) {
|
||||
const baseUrl = "https://public.api.foreplay.co";
|
||||
|
||||
return httpClient.sendRequest({
|
||||
method,
|
||||
url: `${baseUrl}${resourceUri}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: apiKey.secret_text,
|
||||
},
|
||||
queryParams,
|
||||
body,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
import { Property } from '@activepieces/pieces-framework';
|
||||
import { foreplayCoApiCall } from './common';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { foreplayCoAuth } from '..';
|
||||
|
||||
// Common dropdown options (keeping existing functionality)
|
||||
const orderOptions = () => ({
|
||||
options: [
|
||||
{ label: 'Newest', value: 'newest' },
|
||||
{ label: 'Oldest', value: 'oldest' },
|
||||
{ label: 'Longest Running', value: 'longest_running' },
|
||||
{ label: 'Most Relevant', value: 'most_relevant' },
|
||||
],
|
||||
});
|
||||
|
||||
const liveStatusOptions = () => ({
|
||||
options: [
|
||||
{ label: 'Active Only', value: 'true' },
|
||||
{ label: 'Inactive Only', value: 'false' },
|
||||
],
|
||||
});
|
||||
|
||||
const displayFormatOptions = () => ({
|
||||
options: [
|
||||
{ label: 'Video', value: 'video' },
|
||||
{ label: 'Carousel', value: 'carousel' },
|
||||
{ label: 'Image', value: 'image' },
|
||||
{ label: 'DCO', value: 'dco' },
|
||||
{ label: 'DPA', value: 'dpa' },
|
||||
{ label: 'Multi Images', value: 'multi_images' },
|
||||
{ label: 'Multi Videos', value: 'multi_videos' },
|
||||
{ label: 'Multi Medias', value: 'multi_medias' },
|
||||
{ label: 'Event', value: 'event' },
|
||||
{ label: 'Text', value: 'text' },
|
||||
],
|
||||
});
|
||||
|
||||
const publisherPlatformOptions = () => ({
|
||||
options: [
|
||||
{ label: 'Facebook', value: 'facebook' },
|
||||
{ label: 'Instagram', value: 'instagram' },
|
||||
{ label: 'Audience Network', value: 'audience_network' },
|
||||
{ label: 'Messenger', value: 'messenger' },
|
||||
{ label: 'TikTok', value: 'tiktok' },
|
||||
{ label: 'YouTube', value: 'youtube' },
|
||||
{ label: 'LinkedIn', value: 'linkedin' },
|
||||
{ label: 'Threads', value: 'threads' },
|
||||
],
|
||||
});
|
||||
|
||||
const nicheOptions = () => ({
|
||||
options: [
|
||||
{ label: 'Accessories', value: 'accessories' },
|
||||
{ label: 'App/Software', value: 'app/software' },
|
||||
{ label: 'Beauty', value: 'beauty' },
|
||||
{ label: 'Business/Professional', value: 'business/professional' },
|
||||
{ label: 'Education', value: 'education' },
|
||||
{ label: 'Entertainment', value: 'entertainment' },
|
||||
{ label: 'Fashion', value: 'fashion' },
|
||||
{ label: 'Finance', value: 'finance' },
|
||||
{ label: 'Food', value: 'food' },
|
||||
{ label: 'Health', value: 'health' },
|
||||
{ label: 'Home', value: 'home' },
|
||||
{ label: 'Pets', value: 'pets' },
|
||||
{ label: 'Sports', value: 'sports' },
|
||||
{ label: 'Technology', value: 'technology' },
|
||||
{ label: 'Travel', value: 'travel' },
|
||||
{ label: 'Automotive', value: 'automotive' },
|
||||
{ label: 'Other', value: 'other' },
|
||||
],
|
||||
});
|
||||
|
||||
const marketTargetOptions = () => ({
|
||||
options: [
|
||||
{ label: 'B2B (Business-to-Business)', value: 'b2b' },
|
||||
{ label: 'B2C (Business-to-Consumer)', value: 'b2c' },
|
||||
],
|
||||
});
|
||||
|
||||
const languageOptions = () => ({
|
||||
options: [
|
||||
{ label: 'English', value: 'english' },
|
||||
{ label: 'French', value: 'french' },
|
||||
{ label: 'German', value: 'german' },
|
||||
{ label: 'Italian', value: 'italian' },
|
||||
{ label: 'Dutch/Flemish', value: 'dutch, flemish' },
|
||||
{ label: 'Spanish', value: 'spanish' },
|
||||
{ label: 'Portuguese', value: 'portuguese' },
|
||||
{ label: 'Romanian', value: 'romanian' },
|
||||
{ label: 'Russian', value: 'russian' },
|
||||
{ label: 'Chinese', value: 'chinese' },
|
||||
{ label: 'Japanese', value: 'japanese' },
|
||||
{ label: 'Korean', value: 'korean' },
|
||||
{ label: 'Arabic', value: 'arabic' },
|
||||
{ label: 'Hindi', value: 'hindi' },
|
||||
],
|
||||
});
|
||||
|
||||
// Action Properties
|
||||
export const findAds = () => ({
|
||||
query: Property.ShortText({
|
||||
displayName: 'Search Query',
|
||||
description:
|
||||
'Search text for ad name or description. Leave empty to search all ads with filters only.',
|
||||
required: false,
|
||||
}),
|
||||
start_date: Property.DateTime({
|
||||
displayName: 'Start Date',
|
||||
description:
|
||||
'Start date (inclusive). Format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS',
|
||||
required: false,
|
||||
}),
|
||||
end_date: Property.DateTime({
|
||||
displayName: 'End Date',
|
||||
description:
|
||||
'End date (inclusive). Format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS',
|
||||
required: false,
|
||||
}),
|
||||
order: Property.StaticDropdown({
|
||||
displayName: 'Order',
|
||||
description:
|
||||
'Order of results: newest (default), oldest, longest_running, or most_relevant',
|
||||
required: false,
|
||||
options: orderOptions(),
|
||||
}),
|
||||
live: Property.StaticDropdown({
|
||||
displayName: 'Live Status',
|
||||
description:
|
||||
'Filter ads by live status. true means currently active ads, false means inactive ads.',
|
||||
required: false,
|
||||
options: liveStatusOptions(),
|
||||
}),
|
||||
display_format: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Display Format',
|
||||
description: 'Filter by one or more display formats',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => displayFormatOptions(),
|
||||
}),
|
||||
publisher_platform: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Publisher Platform',
|
||||
description: 'Filter by one or more publisher platforms',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => publisherPlatformOptions(),
|
||||
}),
|
||||
niches: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Niches',
|
||||
description: 'Filter by one or more niches',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => nicheOptions(),
|
||||
}),
|
||||
market_target: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Market Target',
|
||||
description: 'Filter by market target',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => marketTargetOptions(),
|
||||
}),
|
||||
languages: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Languages',
|
||||
description: 'Filter by languages. Accepts various language formats.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => languageOptions(),
|
||||
}),
|
||||
cursor: Property.ShortText({
|
||||
displayName: 'Cursor',
|
||||
description:
|
||||
'Cursor for pagination. Use the cursor value from the previous response.',
|
||||
required: false,
|
||||
}),
|
||||
limit: Property.Number({
|
||||
displayName: 'Limit',
|
||||
description:
|
||||
'Pagination limit (default 10, max 250). Controls the number of ads returned per request.',
|
||||
required: false,
|
||||
defaultValue: 10,
|
||||
}),
|
||||
});
|
||||
|
||||
export const getAdById = () => ({
|
||||
ad_id: Property.ShortText({
|
||||
displayName: 'Ad ID',
|
||||
description:
|
||||
'The unique identifier of the ad (e.g., "ad_1234567890"). You can find this ID from other Foreplay actions or the platform.',
|
||||
required: true,
|
||||
}),
|
||||
});
|
||||
|
||||
export const getAdsByPage = () => ({
|
||||
page_id: Property.ShortText({
|
||||
displayName: 'Page ID',
|
||||
description:
|
||||
'The numeric Facebook page ID (e.g., "123456789"). You can find this in your Facebook page settings or from other Foreplay actions.',
|
||||
required: true,
|
||||
}),
|
||||
start_date: Property.DateTime({
|
||||
displayName: 'Start Date',
|
||||
description:
|
||||
'Start date (inclusive). Format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS',
|
||||
required: false,
|
||||
}),
|
||||
end_date: Property.DateTime({
|
||||
displayName: 'End Date',
|
||||
description:
|
||||
'End date (inclusive). Format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS',
|
||||
required: false,
|
||||
}),
|
||||
order: Property.StaticDropdown({
|
||||
displayName: 'Order',
|
||||
description:
|
||||
'Order of results: newest (default), oldest, longest_running, or most_relevant',
|
||||
required: false,
|
||||
options: orderOptions(),
|
||||
}),
|
||||
live: Property.StaticDropdown({
|
||||
displayName: 'Live Status',
|
||||
description:
|
||||
'Filter ads by live status. true means currently active ads, false means inactive ads.',
|
||||
required: false,
|
||||
options: liveStatusOptions(),
|
||||
}),
|
||||
display_format: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Display Format',
|
||||
description: 'Filter by one or more display formats',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => displayFormatOptions(),
|
||||
}),
|
||||
publisher_platform: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Publisher Platform',
|
||||
description: 'Filter by one or more publisher platforms',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => publisherPlatformOptions(),
|
||||
}),
|
||||
niches: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Niches',
|
||||
description: 'Filter by one or more niches',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => nicheOptions(),
|
||||
}),
|
||||
market_target: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Market Target',
|
||||
description: 'Filter by market target',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => marketTargetOptions(),
|
||||
}),
|
||||
languages: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Languages',
|
||||
description: 'Filter by languages. Accepts various language formats.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => languageOptions(),
|
||||
}),
|
||||
cursor: Property.ShortText({
|
||||
displayName: 'Cursor',
|
||||
description:
|
||||
'Cursor for pagination. Use the cursor value from the previous response.',
|
||||
required: false,
|
||||
}),
|
||||
limit: Property.Number({
|
||||
displayName: 'Limit',
|
||||
description:
|
||||
'Pagination limit (default 10, max 250). Controls the number of ads returned per request.',
|
||||
required: false,
|
||||
defaultValue: 10,
|
||||
}),
|
||||
});
|
||||
|
||||
export const findBrands = () => ({
|
||||
query: Property.ShortText({
|
||||
displayName: 'Brand Name',
|
||||
description:
|
||||
'Brand name to search for (e.g., "Nike", "Apple"). Supports fuzzy matching for partial names.',
|
||||
required: true,
|
||||
}),
|
||||
limit: Property.Number({
|
||||
displayName: 'Limit',
|
||||
description: 'Number of brands to return (max 10).',
|
||||
required: false,
|
||||
defaultValue: 10,
|
||||
}),
|
||||
});
|
||||
|
||||
export const findBoards = () => ({
|
||||
offset: Property.Number({
|
||||
displayName: 'Offset',
|
||||
description: 'The offset for pagination (default 0).',
|
||||
required: false,
|
||||
defaultValue: 0,
|
||||
}),
|
||||
limit: Property.Number({
|
||||
displayName: 'Limit',
|
||||
description: 'The limit for pagination (default 10, max 10).',
|
||||
required: false,
|
||||
defaultValue: 10,
|
||||
}),
|
||||
});
|
||||
|
||||
// Trigger Properties
|
||||
export const newAdInBoard = () => ({
|
||||
board_id: Property.Dropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Board',
|
||||
description: 'Select the board to monitor for new ads.',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/api/boards',
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
if (responseBody.metadata && responseBody.metadata.success === true) {
|
||||
const boards = responseBody.data || [];
|
||||
return {
|
||||
options: boards.map((board: any) => ({
|
||||
label: board.name || board.title || `Board ${board.id}`,
|
||||
value: board.id,
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Failed to load boards',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Error loading boards',
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
polling_interval: Property.Number({
|
||||
displayName: 'Polling Interval (minutes)',
|
||||
description: 'How often to check for new ads (in minutes).',
|
||||
required: false,
|
||||
defaultValue: 5,
|
||||
}),
|
||||
live: Property.StaticDropdown({
|
||||
displayName: 'Live Status',
|
||||
description:
|
||||
'Filter ads by live status. true means currently active ads, false means inactive ads.',
|
||||
required: false,
|
||||
options: liveStatusOptions(),
|
||||
}),
|
||||
display_format: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Display Format',
|
||||
description: 'Filter by one or more display formats',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => displayFormatOptions(),
|
||||
}),
|
||||
publisher_platform: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Publisher Platform',
|
||||
description: 'Filter by one or more publisher platforms',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => publisherPlatformOptions(),
|
||||
}),
|
||||
niches: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Niches',
|
||||
description: 'Filter by one or more niches',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => nicheOptions(),
|
||||
}),
|
||||
market_target: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Market Target',
|
||||
description: 'Filter by market target',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => marketTargetOptions(),
|
||||
}),
|
||||
languages: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Languages',
|
||||
description: 'Filter by languages. Accepts various language formats.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => languageOptions(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const newAdInSpyder = () => ({
|
||||
brand_id: Property.Dropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Brand',
|
||||
description: 'Select the brand to monitor for new ads.',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/api/spyder/brands',
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
if (responseBody.metadata && responseBody.metadata.success === true) {
|
||||
const brands = responseBody.data || [];
|
||||
return {
|
||||
options: brands.map((brand: any) => ({
|
||||
label: brand.name || brand.id,
|
||||
value: brand.id,
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Failed to load brands',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Error loading brands',
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
polling_interval: Property.Number({
|
||||
displayName: 'Polling Interval (minutes)',
|
||||
description: 'How often to check for new ads (in minutes).',
|
||||
required: false,
|
||||
defaultValue: 5,
|
||||
}),
|
||||
live: Property.StaticDropdown({
|
||||
displayName: 'Live Status',
|
||||
description:
|
||||
'Filter ads by live status. true means currently active ads, false means inactive ads.',
|
||||
required: false,
|
||||
options: liveStatusOptions(),
|
||||
}),
|
||||
display_format: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Display Format',
|
||||
description: 'Filter by one or more display formats',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => displayFormatOptions(),
|
||||
}),
|
||||
publisher_platform: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Publisher Platform',
|
||||
description: 'Filter by one or more publisher platforms',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => publisherPlatformOptions(),
|
||||
}),
|
||||
niches: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Niches',
|
||||
description: 'Filter by one or more niches',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => nicheOptions(),
|
||||
}),
|
||||
market_target: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Market Target',
|
||||
description: 'Filter by market target',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => marketTargetOptions(),
|
||||
}),
|
||||
languages: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Languages',
|
||||
description: 'Filter by languages. Accepts various language formats.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => languageOptions(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const newSwipefileAd = () => ({
|
||||
polling_interval: Property.Number({
|
||||
displayName: 'Polling Interval (minutes)',
|
||||
description: 'How often to check for new ads (in minutes).',
|
||||
required: false,
|
||||
defaultValue: 5,
|
||||
}),
|
||||
start_date: Property.DateTime({
|
||||
displayName: 'Start Date',
|
||||
description: 'Filter ads published after this date.',
|
||||
required: false,
|
||||
}),
|
||||
end_date: Property.DateTime({
|
||||
displayName: 'End Date',
|
||||
description: 'Filter ads published before this date.',
|
||||
required: false,
|
||||
}),
|
||||
live: Property.StaticDropdown({
|
||||
displayName: 'Live Status',
|
||||
description: 'Filter by ad status (active/inactive).',
|
||||
required: false,
|
||||
options: liveStatusOptions(),
|
||||
}),
|
||||
display_format: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Display Format',
|
||||
description: 'Filter by ad format (video, image, carousel, etc.).',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => displayFormatOptions(),
|
||||
}),
|
||||
publisher_platform: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Publisher Platform',
|
||||
description: 'Filter by platform (Facebook, Instagram, etc.).',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => publisherPlatformOptions(),
|
||||
}),
|
||||
niches: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Niches',
|
||||
description: 'Filter by industry/category.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => nicheOptions(),
|
||||
}),
|
||||
market_target: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Market Target',
|
||||
description: 'Filter by target audience (B2B, B2C).',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => marketTargetOptions(),
|
||||
}),
|
||||
languages: Property.MultiSelectDropdown({
|
||||
auth: foreplayCoAuth,
|
||||
displayName: 'Languages',
|
||||
description: 'Filter by ad language.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async () => languageOptions(),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
import z from 'zod';
|
||||
|
||||
// Common validation schemas for dropdown options
|
||||
const orderOptions = z.enum([
|
||||
'newest',
|
||||
'oldest',
|
||||
'longest_running',
|
||||
'most_relevant',
|
||||
]);
|
||||
const liveStatusOptions = z.enum(['true', 'false']);
|
||||
const displayFormatOptions = z.enum([
|
||||
'video',
|
||||
'carousel',
|
||||
'image',
|
||||
'dco',
|
||||
'dpa',
|
||||
'multi_images',
|
||||
'multi_videos',
|
||||
'multi_medias',
|
||||
'event',
|
||||
'text',
|
||||
]);
|
||||
const publisherPlatformOptions = z.enum([
|
||||
'facebook',
|
||||
'instagram',
|
||||
'audience_network',
|
||||
'messenger',
|
||||
'tiktok',
|
||||
'youtube',
|
||||
'linkedin',
|
||||
'threads',
|
||||
]);
|
||||
const nicheOptions = z.enum([
|
||||
'accessories',
|
||||
'app/software',
|
||||
'beauty',
|
||||
'business/professional',
|
||||
'education',
|
||||
'entertainment',
|
||||
'fashion',
|
||||
'finance',
|
||||
'food',
|
||||
'health',
|
||||
'home',
|
||||
'pets',
|
||||
'sports',
|
||||
'technology',
|
||||
'travel',
|
||||
'automotive',
|
||||
'other',
|
||||
]);
|
||||
const marketTargetOptions = z.enum(['b2b', 'b2c']);
|
||||
const languageOptions = z.enum([
|
||||
'english',
|
||||
'french',
|
||||
'german',
|
||||
'italian',
|
||||
'dutch, flemish',
|
||||
'spanish',
|
||||
'portuguese',
|
||||
'romanian',
|
||||
'russian',
|
||||
'chinese',
|
||||
'japanese',
|
||||
'korean',
|
||||
'arabic',
|
||||
'hindi',
|
||||
]);
|
||||
const brandOrderOptions = z.enum(['most_ranked', 'least_ranked']);
|
||||
|
||||
// Action Schemas (Zod objects for validation)
|
||||
export const findAdsSchema = z.object({
|
||||
query: z.string().optional(),
|
||||
start_date: z.string().optional(),
|
||||
end_date: z.string().optional(),
|
||||
order: orderOptions.optional(),
|
||||
live: liveStatusOptions.optional(),
|
||||
display_format: z.array(displayFormatOptions).optional(),
|
||||
publisher_platform: z.array(publisherPlatformOptions).optional(),
|
||||
niches: z.array(nicheOptions).optional(),
|
||||
market_target: z.array(marketTargetOptions).optional(),
|
||||
languages: z.array(languageOptions).optional(),
|
||||
cursor: z.string().optional(),
|
||||
limit: z.number().min(1).max(250).optional(),
|
||||
});
|
||||
|
||||
export const getAdByIdSchema = z.object({
|
||||
ad_id: z.string().min(1, 'Ad ID is required'),
|
||||
});
|
||||
|
||||
export const getAdsByPageSchema = z.object({
|
||||
page_id: z.string().min(1, 'Page ID is required'),
|
||||
start_date: z.string().optional(),
|
||||
end_date: z.string().optional(),
|
||||
order: orderOptions.optional(),
|
||||
live: liveStatusOptions.optional(),
|
||||
display_format: z.array(displayFormatOptions).optional(),
|
||||
publisher_platform: z.array(publisherPlatformOptions).optional(),
|
||||
niches: z.array(nicheOptions).optional(),
|
||||
market_target: z.array(marketTargetOptions).optional(),
|
||||
languages: z.array(languageOptions).optional(),
|
||||
cursor: z.string().optional(),
|
||||
limit: z.number().min(1).max(250).optional(),
|
||||
});
|
||||
|
||||
export const findBrandsSchema = z.object({
|
||||
query: z.string().min(1, 'Brand name is required'),
|
||||
limit: z.number().min(1).max(10).optional(),
|
||||
});
|
||||
|
||||
export const findBoardsSchema = z.object({
|
||||
offset: z.number().min(0).optional(),
|
||||
limit: z.number().min(1).max(10).optional(),
|
||||
});
|
||||
|
||||
// Trigger Schemas
|
||||
export const newAdInBoardSchema = z.object({
|
||||
board_id: z.string().min(1, 'Board ID is required'),
|
||||
polling_interval: z.number().min(1).max(1440).optional(),
|
||||
live: liveStatusOptions.optional(),
|
||||
display_format: z.array(displayFormatOptions).optional(),
|
||||
publisher_platform: z.array(publisherPlatformOptions).optional(),
|
||||
niches: z.array(nicheOptions).optional(),
|
||||
market_target: z.array(marketTargetOptions).optional(),
|
||||
languages: z.array(languageOptions).optional(),
|
||||
});
|
||||
|
||||
export const newAdInSpyderSchema = z.object({
|
||||
brand_id: z.string().min(1, 'Brand ID is required'),
|
||||
polling_interval: z.number().min(1).max(1440).optional(),
|
||||
live: liveStatusOptions.optional(),
|
||||
display_format: z.array(displayFormatOptions).optional(),
|
||||
publisher_platform: z.array(publisherPlatformOptions).optional(),
|
||||
niches: z.array(nicheOptions).optional(),
|
||||
market_target: z.array(marketTargetOptions).optional(),
|
||||
languages: z.array(languageOptions).optional(),
|
||||
});
|
||||
|
||||
export const newSwipefileAdSchema = z.object({
|
||||
polling_interval: z.number().min(1).max(1440).optional(),
|
||||
start_date: z.string().optional(),
|
||||
end_date: z.string().optional(),
|
||||
live: liveStatusOptions.optional(),
|
||||
display_format: z.array(displayFormatOptions).optional(),
|
||||
publisher_platform: z.array(publisherPlatformOptions).optional(),
|
||||
niches: z.array(nicheOptions).optional(),
|
||||
market_target: z.array(marketTargetOptions).optional(),
|
||||
languages: z.array(languageOptions).optional(),
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export { newAdInSpyder } from './new-ad-in-spyder';
|
||||
export { newAdInBoard } from './new-ad-in-board';
|
||||
export { newSwipefileAd } from './new-swipefile-ad';
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
import { createTrigger, TriggerStrategy, Property, AppConnectionValueForAuthProperty } from "@activepieces/pieces-framework";
|
||||
import { foreplayCoApiCall } from "../common";
|
||||
import { HttpMethod, Polling, DedupeStrategy, pollingHelper } from "@activepieces/pieces-common";
|
||||
import { newAdInBoard as newAdInBoardProperties } from "../properties";
|
||||
import { newAdInBoardSchema } from "../schemas";
|
||||
import { foreplayCoAuth } from "../..";
|
||||
|
||||
const getBoardsDropdown = async (auth: AppConnectionValueForAuthProperty<typeof foreplayCoAuth>) => {
|
||||
try {
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/api/boards',
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
if (responseBody.metadata && responseBody.metadata.success === true && responseBody.data) {
|
||||
return {
|
||||
options: responseBody.data.map((board: any) => ({
|
||||
label: board.name || board.title || `Board ${board.id}`,
|
||||
value: board.id
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
return { options: [] };
|
||||
} catch (error) {
|
||||
console.error('Error fetching boards for dropdown:', error);
|
||||
return { options: [] };
|
||||
}
|
||||
};
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof foreplayCoAuth>, Record<string, any>> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
const { board_id } = propsValue;
|
||||
|
||||
console.log(`[New Ad in Board Polling] Fetching ads for board: ${board_id}, lastFetch: ${new Date(lastFetchEpochMS || 0).toISOString()}`);
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
board_id: board_id,
|
||||
limit: String(250), // Max limit to get more ads
|
||||
order: 'newest'
|
||||
};
|
||||
|
||||
// Add optional filters if provided
|
||||
if (propsValue['live'] !== undefined) {
|
||||
queryParams['live'] = String(propsValue['live'] === 'true');
|
||||
}
|
||||
if (propsValue['display_format'] && propsValue['display_format'].length > 0) {
|
||||
(queryParams as any).display_format = propsValue['display_format'];
|
||||
}
|
||||
if (propsValue['publisher_platform'] && propsValue['publisher_platform'].length > 0) {
|
||||
(queryParams as any).publisher_platform = propsValue['publisher_platform'];
|
||||
}
|
||||
if (propsValue['niches'] && propsValue['niches'].length > 0) {
|
||||
(queryParams as any).niches = propsValue['niches'];
|
||||
}
|
||||
if (propsValue['market_target'] && propsValue['market_target'].length > 0) {
|
||||
(queryParams as any).market_target = propsValue['market_target'];
|
||||
}
|
||||
if (propsValue['languages'] && propsValue['languages'].length > 0) {
|
||||
(queryParams as any).languages = propsValue['languages'];
|
||||
}
|
||||
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/api/board/ads',
|
||||
queryParams,
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
if (!responseBody.metadata || !responseBody.metadata.success) {
|
||||
console.log(`[New Ad in Board Polling] API call failed:`, responseBody);
|
||||
return [];
|
||||
}
|
||||
|
||||
const ads = responseBody.data || [];
|
||||
console.log(`[New Ad in Board Polling] Found ${ads.length} ads for board ${board_id}`);
|
||||
|
||||
return ads.map((ad: any) => ({
|
||||
epochMilliSeconds: new Date(ad.created_at).getTime(),
|
||||
data: ad,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
export const newAdInBoard = createTrigger({
|
||||
name: 'newAdInBoard',
|
||||
displayName: 'New Ad in Board',
|
||||
auth: foreplayCoAuth,
|
||||
description: 'Triggers when a new ad is added to the selected board.',
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: {
|
||||
id: "ad_789",
|
||||
board_id: "board_456",
|
||||
brand_id: "brand_456",
|
||||
title: "New Campaign Ad",
|
||||
description: "Latest marketing campaign",
|
||||
live: true,
|
||||
display_format: "video",
|
||||
publisher_platform: ["facebook"],
|
||||
niches: ["fashion"],
|
||||
market_target: "b2c",
|
||||
languages: ["en"],
|
||||
created_at: "2024-01-15T10:30:00Z",
|
||||
updated_at: "2024-01-15T10:30:00Z"
|
||||
},
|
||||
|
||||
props: newAdInBoardProperties(),
|
||||
|
||||
async test(context) {
|
||||
// Validate props using Zod schema
|
||||
const validation = newAdInBoardSchema.safeParse(context.propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
return await pollingHelper.test(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
files: context.files,
|
||||
});
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
await pollingHelper.onDisable(polling, {
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
auth: context.auth,
|
||||
});
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
// Validate props using Zod schema
|
||||
const validation = newAdInBoardSchema.safeParse(context.propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
const result = await pollingHelper.poll(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
files: context.files,
|
||||
});
|
||||
|
||||
// Transform the result to match our expected format
|
||||
return result.map((item: any) => ({
|
||||
id: item.data.id,
|
||||
board_id: context.propsValue.board_id,
|
||||
brand_id: item.data.brand_id,
|
||||
title: item.data.title || item.data.name,
|
||||
description: item.data.description,
|
||||
live: item.data.live,
|
||||
display_format: item.data.display_format,
|
||||
publisher_platform: item.data.publisher_platform,
|
||||
niches: item.data.niches,
|
||||
market_target: item.data.market_target,
|
||||
languages: item.data.languages,
|
||||
created_at: item.data.created_at,
|
||||
updated_at: item.data.updated_at,
|
||||
metadata: { success: true, message: 'New ad detected' }
|
||||
}));
|
||||
}
|
||||
});
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
import { AppConnectionValueForAuthProperty, createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { foreplayCoApiCall } from '../common';
|
||||
import {
|
||||
HttpMethod,
|
||||
Polling,
|
||||
DedupeStrategy,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { newAdInSpyder as newAdInSpyderProperties } from '../properties';
|
||||
import { newAdInSpyderSchema } from '../schemas';
|
||||
import { foreplayCoAuth } from '../..';
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof foreplayCoAuth>, Record<string, any>> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue }) => {
|
||||
const { brand_id } = propsValue;
|
||||
|
||||
// Build query parameters with user's filter preferences
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('brand_id', brand_id);
|
||||
queryParams.append('limit', String(50)); // Reasonable default for polling
|
||||
queryParams.append('order', 'newest');
|
||||
|
||||
// Add optional filters if provided
|
||||
if (propsValue['live']) {
|
||||
queryParams.append('live', String(propsValue['live']));
|
||||
}
|
||||
if (
|
||||
propsValue['display_format'] &&
|
||||
propsValue['display_format'].length > 0
|
||||
) {
|
||||
propsValue['display_format'].forEach((format: unknown) => {
|
||||
queryParams.append('display_format', String(format));
|
||||
});
|
||||
}
|
||||
if (
|
||||
propsValue['publisher_platform'] &&
|
||||
propsValue['publisher_platform'].length > 0
|
||||
) {
|
||||
propsValue['publisher_platform'].forEach((platform: unknown) => {
|
||||
queryParams.append('publisher_platform', String(platform));
|
||||
});
|
||||
}
|
||||
if (propsValue['niches'] && propsValue['niches'].length > 0) {
|
||||
propsValue['niches'].forEach((niche: unknown) => {
|
||||
queryParams.append('niches', String(niche));
|
||||
});
|
||||
}
|
||||
if (propsValue['market_target'] && propsValue['market_target'].length > 0) {
|
||||
propsValue['market_target'].forEach((target: unknown) => {
|
||||
queryParams.append('market_target', String(target));
|
||||
});
|
||||
}
|
||||
if (propsValue['languages'] && propsValue['languages'].length > 0) {
|
||||
propsValue['languages'].forEach((language: unknown) => {
|
||||
queryParams.append('languages', String(language));
|
||||
});
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
const fullUrl = `/api/spyder/brand/ads?${queryString}`;
|
||||
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: fullUrl,
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
if (!responseBody.metadata || !responseBody.metadata.success) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ads = responseBody.data || [];
|
||||
|
||||
return ads.map((ad: any) => ({
|
||||
epochMilliSeconds: new Date(ad.created_at).getTime(),
|
||||
data: ad,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
export const newAdInSpyder = createTrigger({
|
||||
name: 'newAdInSpyder',
|
||||
displayName: 'New Ad in Spyder',
|
||||
description: 'Triggers when new ads are added for a brand in Spyder.',
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: {
|
||||
id: 'ad_123456789',
|
||||
brand_id: 'brand_987654321',
|
||||
title: 'New Summer Sale Ad',
|
||||
description: 'A great summer sale ad.',
|
||||
live: true,
|
||||
display_format: 'video',
|
||||
publisher_platform: ['facebook'],
|
||||
niches: ['fashion'],
|
||||
market_target: 'b2c',
|
||||
languages: ['en'],
|
||||
created_at: '2024-01-15T10:30:00Z',
|
||||
updated_at: '2024-01-15T10:30:00Z',
|
||||
},
|
||||
|
||||
props: newAdInSpyderProperties(),
|
||||
auth: foreplayCoAuth,
|
||||
async test(context) {
|
||||
// Validate props using Zod schema
|
||||
const validation = newAdInSpyderSchema.safeParse(context.propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
return await pollingHelper.test(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
files: context.files,
|
||||
});
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
await pollingHelper.onDisable(polling, {
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
auth: context.auth,
|
||||
});
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
// Validate props using Zod schema
|
||||
const validation = newAdInSpyderSchema.safeParse(context.propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
const result = await pollingHelper.poll(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
files: context.files,
|
||||
});
|
||||
|
||||
// Return clean ad data for automation workflows
|
||||
return result.map((item: any) => item.data);
|
||||
},
|
||||
});
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
import { createTrigger, TriggerStrategy, Property, AppConnectionValueForAuthProperty } from "@activepieces/pieces-framework";
|
||||
import { foreplayCoApiCall } from "../common";
|
||||
import { HttpMethod, Polling, DedupeStrategy, pollingHelper } from "@activepieces/pieces-common";
|
||||
import { newSwipefileAd as newSwipefileAdProperties } from "../properties";
|
||||
import { newSwipefileAdSchema } from "../schemas";
|
||||
import { foreplayCoAuth } from "../..";
|
||||
|
||||
const polling: Polling<AppConnectionValueForAuthProperty<typeof foreplayCoAuth>, Record<string, any>> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
console.log(`[New Swipefile Ad Polling] Fetching swipefile ads, lastFetch: ${new Date(lastFetchEpochMS || 0).toISOString()}`);
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
limit: String(250), // Max limit to get more ads
|
||||
order: 'newest'
|
||||
};
|
||||
|
||||
// Add optional filters if provided
|
||||
if (propsValue['start_date']) {
|
||||
queryParams['start_date'] = propsValue['start_date'];
|
||||
}
|
||||
if (propsValue['end_date']) {
|
||||
queryParams['end_date'] = propsValue['end_date'];
|
||||
}
|
||||
if (propsValue['live'] !== undefined) {
|
||||
queryParams['live'] = String(propsValue['live'] === 'true');
|
||||
}
|
||||
if (propsValue['display_format'] && propsValue['display_format'].length > 0) {
|
||||
(queryParams as any).display_format = propsValue['display_format'];
|
||||
}
|
||||
if (propsValue['publisher_platform'] && propsValue['publisher_platform'].length > 0) {
|
||||
(queryParams as any).publisher_platform = propsValue['publisher_platform'];
|
||||
}
|
||||
if (propsValue['niches'] && propsValue['niches'].length > 0) {
|
||||
(queryParams as any).niches = propsValue['niches'];
|
||||
}
|
||||
if (propsValue['market_target'] && propsValue['market_target'].length > 0) {
|
||||
(queryParams as any).market_target = propsValue['market_target'];
|
||||
}
|
||||
if (propsValue['languages'] && propsValue['languages'].length > 0) {
|
||||
(queryParams as any).languages = propsValue['languages'];
|
||||
}
|
||||
|
||||
const response = await foreplayCoApiCall({
|
||||
apiKey: auth,
|
||||
method: HttpMethod.GET,
|
||||
resourceUri: '/api/swipefile/ads',
|
||||
queryParams,
|
||||
});
|
||||
|
||||
const responseBody = response.body;
|
||||
|
||||
if (!responseBody.metadata || !responseBody.metadata.success) {
|
||||
console.log(`[New Swipefile Ad Polling] API call failed:`, responseBody);
|
||||
return [];
|
||||
}
|
||||
|
||||
const ads = responseBody.data || [];
|
||||
console.log(`[New Swipefile Ad Polling] Found ${ads.length} swipefile ads`);
|
||||
|
||||
return ads.map((ad: any) => ({
|
||||
epochMilliSeconds: new Date(ad.created_at).getTime(),
|
||||
data: ad,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
export const newSwipefileAd = createTrigger({
|
||||
name: 'newSwipefileAd',
|
||||
displayName: 'New Swipefile Ad',
|
||||
description: 'Triggers when a new ad is added to your swipefile collection.',
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: {
|
||||
id: "ad_123456789",
|
||||
brand_id: "brand_987654321",
|
||||
brand_name: "Nike",
|
||||
title: "Just Do It - New Collection",
|
||||
description: "Discover our latest athletic wear collection",
|
||||
live: true,
|
||||
display_format: "video",
|
||||
publisher_platform: ["facebook"],
|
||||
niches: ["sports", "fashion"],
|
||||
market_target: "b2c",
|
||||
languages: ["en"],
|
||||
created_at: "2024-01-15T10:30:00Z",
|
||||
updated_at: "2024-01-15T10:30:00Z",
|
||||
media_urls: [
|
||||
"https://example.com/video1.mp4",
|
||||
"https://example.com/image1.jpg"
|
||||
],
|
||||
ad_library_id: "123456789",
|
||||
ad_library_url: "https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=US&view_all_page_id=123456789"
|
||||
},
|
||||
|
||||
props: newSwipefileAdProperties(),
|
||||
auth: foreplayCoAuth,
|
||||
async test(context) {
|
||||
// Validate props using Zod schema
|
||||
const validation = newSwipefileAdSchema.safeParse(context.propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
return await pollingHelper.test(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
files: context.files,
|
||||
});
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
await pollingHelper.onDisable(polling, {
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
auth: context.auth,
|
||||
});
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
// Validate props using Zod schema
|
||||
const validation = newSwipefileAdSchema.safeParse(context.propsValue);
|
||||
if (!validation.success) {
|
||||
throw new Error(`Validation failed: ${validation.error.message}`);
|
||||
}
|
||||
|
||||
const result = await pollingHelper.poll(polling, {
|
||||
auth: context.auth,
|
||||
store: context.store,
|
||||
propsValue: context.propsValue,
|
||||
files: context.files,
|
||||
});
|
||||
|
||||
// Transform the result to match our expected format
|
||||
return result.map((item: any) => ({
|
||||
id: item.data.id,
|
||||
brand_id: item.data.brand_id,
|
||||
brand_name: item.data.brand_name,
|
||||
title: item.data.title,
|
||||
description: item.data.description,
|
||||
live: item.data.live,
|
||||
display_format: item.data.display_format,
|
||||
publisher_platform: item.data.publisher_platform,
|
||||
niches: item.data.niches,
|
||||
market_target: item.data.market_target,
|
||||
languages: item.data.languages,
|
||||
created_at: item.data.created_at,
|
||||
updated_at: item.data.updated_at,
|
||||
media_urls: item.data.media_urls,
|
||||
ad_library_id: item.data.ad_library_id,
|
||||
ad_library_url: item.data.ad_library_url,
|
||||
metadata: { success: true, message: 'New ad detected' }
|
||||
}));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user