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,8 @@
|
||||
{
|
||||
"Facebook Leads": "Facebook Leads",
|
||||
"Capture leads from Facebook": "Capture leads from Facebook",
|
||||
"New Lead": "New Lead",
|
||||
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
|
||||
"Page": "Page",
|
||||
"Form": "Form"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Capture leads from Facebook": "Leads von Facebook aufnehmen",
|
||||
"New Lead": "Neuer Lead",
|
||||
"Triggers when a new lead is created.": "Wird ausgelöst, wenn ein neuer Lead erstellt wird.",
|
||||
"Page": "Seite",
|
||||
"Form": "Formular"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Capture leads from Facebook": "Capturar prospectos de Facebook",
|
||||
"New Lead": "Nuevo plomo",
|
||||
"Triggers when a new lead is created.": "Dispara cuando se crea un nuevo plomo.",
|
||||
"Page": "Pgina",
|
||||
"Form": "Forma"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Capture leads from Facebook": "Capturer des prospects depuis Facebook",
|
||||
"New Lead": "Nouveau prospect",
|
||||
"Triggers when a new lead is created.": "Déclenche lorsqu'un nouveau prospect est créé.",
|
||||
"Page": "Page",
|
||||
"Form": "Forme"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Facebook Leads": "Facebook Leads",
|
||||
"Capture leads from Facebook": "Capture leads from Facebook",
|
||||
"New Lead": "New Lead",
|
||||
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
|
||||
"Page": "Page",
|
||||
"Form": "Form"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Facebook Leads": "Facebook Leads",
|
||||
"Capture leads from Facebook": "Capture leads from Facebook",
|
||||
"New Lead": "New Lead",
|
||||
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
|
||||
"Page": "Page",
|
||||
"Form": "Form"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Capture leads from Facebook": "Facebookからリードをキャプチャ",
|
||||
"New Lead": "新しいリード",
|
||||
"Triggers when a new lead is created.": "新しいリードが作成されたときにトリガーします。",
|
||||
"Page": "ページ",
|
||||
"Form": "フォーム"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Capture leads from Facebook": "Neem leads van Facebook op",
|
||||
"New Lead": "Nieuwe Lead",
|
||||
"Triggers when a new lead is created.": "Triggert wanneer een nieuwe lead wordt gemaakt.",
|
||||
"Page": "Pagina",
|
||||
"Form": "Vorm"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Capture leads from Facebook": "Capture líderes do Facebook",
|
||||
"New Lead": "Novo Potencial",
|
||||
"Triggers when a new lead is created.": "Dispara quando um novo lead é criado.",
|
||||
"Page": "Página",
|
||||
"Form": "Formulário"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Facebook Leads": "Facebook предварительные контакты",
|
||||
"Capture leads from Facebook": "Захват проводов из Facebook",
|
||||
"New Lead": "Новый предв. контакт",
|
||||
"Triggers when a new lead is created.": "Триггеры при создании нового свинца.",
|
||||
"Page": "Страница",
|
||||
"Form": "Форма"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Capture leads from Facebook": "Capture leads from Facebook",
|
||||
"New Lead": "New Lead",
|
||||
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
|
||||
"Page": "Page",
|
||||
"Form": "Form"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Facebook Leads": "Facebook Leads",
|
||||
"Capture leads from Facebook": "Capture leads from Facebook",
|
||||
"New Lead": "New Lead",
|
||||
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
|
||||
"Page": "Page",
|
||||
"Form": "Form"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Capture leads from Facebook": "Capture leads from Facebook",
|
||||
"New Lead": "New Lead",
|
||||
"Triggers when a new lead is created.": "Triggers when a new lead is created.",
|
||||
"Page": "Page",
|
||||
"Form": "Form"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { PieceAuth, createPiece } from '@activepieces/pieces-framework';
|
||||
import { PieceCategory } from '@activepieces/shared';
|
||||
import { newLead } from './lib/triggers/new-lead';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
export const facebookLeadsAuth = PieceAuth.OAuth2({
|
||||
description: '',
|
||||
authUrl: 'https://graph.facebook.com/oauth/authorize',
|
||||
tokenUrl: 'https://graph.facebook.com/oauth/access_token',
|
||||
required: true,
|
||||
scope: [
|
||||
'pages_show_list',
|
||||
'pages_manage_ads',
|
||||
'leads_retrieval',
|
||||
'pages_manage_metadata',
|
||||
'business_management',
|
||||
],
|
||||
});
|
||||
|
||||
export const facebookLeads = createPiece({
|
||||
displayName: 'Facebook Leads',
|
||||
description: 'Capture leads from Facebook',
|
||||
minimumSupportedRelease: '0.30.0',
|
||||
logoUrl: 'https://cdn.activepieces.com/pieces/facebook.png',
|
||||
authors: ['kishanprmr', 'MoShizzle', 'khaledmashaly', 'abuaboud', 'AbdulTheActivePiecer'],
|
||||
categories: [PieceCategory.MARKETING],
|
||||
auth: facebookLeadsAuth,
|
||||
actions: [],
|
||||
triggers: [newLead],
|
||||
events: {
|
||||
parseAndReply: (context) => {
|
||||
const payload = context.payload;
|
||||
const payloadBody = payload.body as PayloadBody;
|
||||
if (payload.queryParams['hub.verify_token'] == 'activepieces') {
|
||||
return {
|
||||
reply: {
|
||||
body: payload.queryParams['hub.challenge'],
|
||||
headers: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
event: 'lead',
|
||||
identifierValue: payloadBody.entry[0].changes[0].value.page_id,
|
||||
};
|
||||
},
|
||||
verify: ({ webhookSecret, payload }) => {
|
||||
// https://developers.facebook.com/docs/messenger-platform/webhooks#validate-payloads
|
||||
|
||||
const signature = payload.headers['x-hub-signature-256'];
|
||||
const elements = signature.split('=');
|
||||
const signatureHash = elements[1];
|
||||
|
||||
const hmac = crypto.createHmac('sha256', webhookSecret as string);
|
||||
hmac.update(payload.rawBody as any);
|
||||
const computedSignature = hmac.digest('hex');
|
||||
|
||||
return signatureHash === computedSignature;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type PayloadBody = {
|
||||
entry: {
|
||||
changes: {
|
||||
value: {
|
||||
page_id: string;
|
||||
};
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
import { DropdownOption, OAuth2PropertyValue, Property } from '@activepieces/pieces-framework';
|
||||
import { HttpRequest, HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import {
|
||||
FacebookForm,
|
||||
FacebookLead,
|
||||
FacebookPage,
|
||||
FacebookPageDropdown,
|
||||
FacebookPaginatedResponse,
|
||||
} from './types';
|
||||
import { facebookLeadsAuth } from '../../index';
|
||||
|
||||
export const facebookLeadsCommon = {
|
||||
baseUrl: 'https://graph.facebook.com',
|
||||
page: Property.Dropdown({
|
||||
auth: facebookLeadsAuth,
|
||||
displayName: 'Page',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Connect your account first.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const authValue = auth as OAuth2PropertyValue;
|
||||
|
||||
const options: DropdownOption<FacebookPageDropdown>[] = [];
|
||||
|
||||
let nextUrl: string | null = `${facebookLeadsCommon.baseUrl}/me/accounts`;
|
||||
|
||||
do {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: nextUrl,
|
||||
queryParams: {
|
||||
access_token: authValue.access_token,
|
||||
},
|
||||
});
|
||||
|
||||
const { data, paging } = response.body as FacebookPaginatedResponse<FacebookPage>;
|
||||
|
||||
const items = data ?? [];
|
||||
for (const page of items) {
|
||||
options.push({
|
||||
label: page.name,
|
||||
value: {
|
||||
id: page.id,
|
||||
accessToken: page.access_token,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
nextUrl = paging?.next ?? null;
|
||||
} while (nextUrl);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Error occured while fetching pages.',
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
form: Property.Dropdown({
|
||||
auth: facebookLeadsAuth,
|
||||
displayName: 'Form',
|
||||
required: false,
|
||||
refreshers: ['page'],
|
||||
options: async ({ page }) => {
|
||||
if (!page) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Select page first.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const pageDeatils = page as {
|
||||
id: string;
|
||||
accessToken: string;
|
||||
};
|
||||
|
||||
const options: DropdownOption<string>[] = [
|
||||
{
|
||||
label: 'All Forms (Default)',
|
||||
value: 'all',
|
||||
},
|
||||
];
|
||||
|
||||
let nextUrl:
|
||||
| string
|
||||
| null = `${facebookLeadsCommon.baseUrl}/${pageDeatils.id}/leadgen_forms`;
|
||||
|
||||
do {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: nextUrl,
|
||||
queryParams: {
|
||||
access_token: pageDeatils.accessToken,
|
||||
},
|
||||
});
|
||||
|
||||
const { data, paging } = response.body as FacebookPaginatedResponse<FacebookForm>;
|
||||
|
||||
const items = data ?? [];
|
||||
for (const form of items) {
|
||||
options.push({
|
||||
label: form.name,
|
||||
value: form.id,
|
||||
});
|
||||
}
|
||||
|
||||
nextUrl = paging?.next ?? null;
|
||||
} while (nextUrl);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Error occured while fetching forms.',
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
subscribePageToApp: async (pageId: any, accessToken: string) => {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `${facebookLeadsCommon.baseUrl}/${pageId}/subscribed_apps`,
|
||||
body: {
|
||||
access_token: accessToken,
|
||||
subscribed_fields: ['leadgen'],
|
||||
},
|
||||
};
|
||||
|
||||
await httpClient.sendRequest(request);
|
||||
},
|
||||
|
||||
getPageForms: async (pageId: string, accessToken: string) => {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${facebookLeadsCommon.baseUrl}/${pageId}/leadgen_forms`,
|
||||
queryParams: {
|
||||
access_token: accessToken,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
return response.body.data;
|
||||
},
|
||||
|
||||
getLeadDetails: async (leadId: string, accessToken: string) => {
|
||||
const response = await httpClient.sendRequest<FacebookLead>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${facebookLeadsCommon.baseUrl}/${leadId}`,
|
||||
queryParams: {
|
||||
access_token: accessToken,
|
||||
fields:
|
||||
'field_data,created_time,ad_id,ad_name,adset_id,adset_name,campaign_id,campaign_name,form_id,platform',
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
|
||||
loadSampleData: async (formId: string, accessToken: string) => {
|
||||
const response = await httpClient.sendRequest<FacebookPaginatedResponse<FacebookLead>>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${facebookLeadsCommon.baseUrl}/${formId}/leads`,
|
||||
queryParams: {
|
||||
access_token: accessToken,
|
||||
fields:
|
||||
'field_data,created_time,ad_id,ad_name,adset_id,adset_name,campaign_id,campaign_name,form_id,platform',
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
|
||||
transformLeadData: (leadData: FacebookLead) => {
|
||||
return {
|
||||
lead_id: leadData.id,
|
||||
form_id: leadData.form_id,
|
||||
platform: leadData.platform,
|
||||
ad_id: leadData.ad_id,
|
||||
ad_name: leadData.ad_name,
|
||||
adset_id: leadData.adset_id,
|
||||
adset_name: leadData.adset_name,
|
||||
campaign_id: leadData.campaign_id,
|
||||
campaign_name: leadData.campaign_name,
|
||||
created_time: leadData.created_time,
|
||||
data: leadData.field_data.reduce(
|
||||
(acc, field) => ({
|
||||
...acc,
|
||||
[field.name]: field.values && field.values.length > 0 ? field.values[0] : null,
|
||||
}),
|
||||
{},
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
export interface FacebookPaginatedResponse<T> {
|
||||
data: T[];
|
||||
paging?: {
|
||||
next?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FacebookTriggerPayloadBody {
|
||||
entry: {
|
||||
changes: {
|
||||
value: {
|
||||
form_id: string;
|
||||
leadgen_id: string;
|
||||
};
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface FacebookPage {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
category_list: string[];
|
||||
access_token: string;
|
||||
tasks: string[];
|
||||
}
|
||||
|
||||
export interface FacebookPageDropdown {
|
||||
id: string;
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface FacebookForm {
|
||||
id: string;
|
||||
locale: string;
|
||||
name: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface FacebookLead {
|
||||
field_data: Array<{ name: string; values: any[] }>;
|
||||
created_time: string;
|
||||
ad_id: string;
|
||||
ad_name: string;
|
||||
adset_id: string;
|
||||
adset_name: string;
|
||||
campaign_id: string;
|
||||
campaign_name: string;
|
||||
form_id: string;
|
||||
platform: string;
|
||||
id: string;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
|
||||
import { facebookLeadsCommon } from '../common';
|
||||
import { facebookLeadsAuth } from '../..';
|
||||
import { FacebookTriggerPayloadBody, FacebookPageDropdown } from '../common/types';
|
||||
|
||||
export const newLead = createTrigger({
|
||||
auth: facebookLeadsAuth,
|
||||
name: 'new_lead',
|
||||
displayName: 'New Lead',
|
||||
description: 'Triggers when a new lead is created.',
|
||||
type: TriggerStrategy.APP_WEBHOOK,
|
||||
sampleData: {},
|
||||
props: {
|
||||
page: facebookLeadsCommon.page,
|
||||
form: facebookLeadsCommon.form,
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const page = context.propsValue['page'] as FacebookPageDropdown;
|
||||
await facebookLeadsCommon.subscribePageToApp(page.id, page.accessToken);
|
||||
|
||||
context.app.createListeners({ events: ['lead'], identifierValue: page.id });
|
||||
},
|
||||
|
||||
async onDisable() {
|
||||
//
|
||||
},
|
||||
async test(context) {
|
||||
let form = context.propsValue.form as string;
|
||||
const page = context.propsValue.page as FacebookPageDropdown;
|
||||
if (form == undefined || form == '' || form == null) {
|
||||
const forms = await facebookLeadsCommon.getPageForms(page.id, page.accessToken);
|
||||
|
||||
form = forms[0].id;
|
||||
}
|
||||
|
||||
const response = await facebookLeadsCommon.loadSampleData(form, context.auth.access_token);
|
||||
return response.data.map((lead) => facebookLeadsCommon.transformLeadData(lead));
|
||||
},
|
||||
|
||||
//Return new lead
|
||||
async run(context) {
|
||||
let leadPings: any[] = [];
|
||||
const leads: any[] = [];
|
||||
const form = context.propsValue.form;
|
||||
const payloadBody = context.payload.body as FacebookTriggerPayloadBody;
|
||||
|
||||
if (form !== undefined && form !== '' && form !== null) {
|
||||
for (const lead of payloadBody.entry) {
|
||||
if (form == lead.changes[0].value.form_id) {
|
||||
leadPings.push(lead);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
leadPings = payloadBody.entry;
|
||||
}
|
||||
|
||||
for (const lead of leadPings) {
|
||||
const leadData = await facebookLeadsCommon.getLeadDetails(
|
||||
lead.changes[0].value.leadgen_id,
|
||||
context.auth.access_token,
|
||||
);
|
||||
const transformLead = facebookLeadsCommon.transformLeadData(leadData);
|
||||
leads.push(transformLead);
|
||||
}
|
||||
|
||||
return leads;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user