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,33 @@
|
||||
{
|
||||
"extends": [
|
||||
"../../../../.eslintrc.base.json"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"!**/*"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# pieces-synthesia
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build pieces-synthesia` to build the library.
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@activepieces/piece-synthesia",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs",
|
||||
"main": "./src/index.js",
|
||||
"types": "./src/index.d.ts",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "pieces-synthesia",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/pieces/community/synthesia/src",
|
||||
"projectType": "library",
|
||||
"release": {
|
||||
"version": {
|
||||
"manifestRootsToUpdate": [
|
||||
"dist/{projectRoot}"
|
||||
],
|
||||
"currentVersionResolver": "git-tag",
|
||||
"fallbackCurrentVersionResolver": "disk"
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/pieces/community/synthesia",
|
||||
"tsConfig": "packages/pieces/community/synthesia/tsconfig.lib.json",
|
||||
"packageJson": "packages/pieces/community/synthesia/package.json",
|
||||
"main": "packages/pieces/community/synthesia/src/index.ts",
|
||||
"assets": [
|
||||
"packages/pieces/community/synthesia/*.md",
|
||||
{
|
||||
"input": "packages/pieces/community/synthesia/src/i18n",
|
||||
"output": "./src/i18n",
|
||||
"glob": "**/!(i18n.json)"
|
||||
}
|
||||
],
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies",
|
||||
"updateBuildableProjectDepsInPackageJson": true
|
||||
},
|
||||
"dependsOn": [
|
||||
"prebuild",
|
||||
"^build"
|
||||
]
|
||||
},
|
||||
"nx-release-publish": {
|
||||
"options": {
|
||||
"packageRoot": "dist/{projectRoot}"
|
||||
}
|
||||
},
|
||||
"prebuild": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "packages/pieces/community/synthesia",
|
||||
"command": "bun install --no-save --silent"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createPiece } from '@activepieces/pieces-framework';
|
||||
import { PieceCategory } from '@activepieces/shared';
|
||||
import { synthesiaAuth } from './lib/common/auth';
|
||||
import { createAVideoFromATemplate } from './lib/actions/create-a-video-from-a-template';
|
||||
import { createVideo } from './lib/actions/create-video';
|
||||
import { newVideoReady } from './lib/triggers/new-video-ready';
|
||||
|
||||
export const synthesia = createPiece({
|
||||
displayName: 'Synthesia',
|
||||
auth: synthesiaAuth,
|
||||
minimumSupportedRelease: '0.36.1',
|
||||
logoUrl: 'https://cdn.activepieces.com/pieces/synthesia.png',
|
||||
categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE],
|
||||
description: 'Create AI videos from text in minutes using Synthesia',
|
||||
authors: ['sanket-a11y'],
|
||||
actions: [createAVideoFromATemplate, createVideo],
|
||||
triggers: [newVideoReady],
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import { synthesiaAuth } from '../common/auth';
|
||||
import { templateIdDropdown } from '../common/props';
|
||||
|
||||
export const createAVideoFromATemplate = createAction({
|
||||
auth: synthesiaAuth,
|
||||
name: 'createAVideoFromATemplate',
|
||||
displayName: 'Create a video from a template',
|
||||
description: 'Create a video based on a template created in Synthesia',
|
||||
props: {
|
||||
templateId: templateIdDropdown,
|
||||
templateData: Property.Object({
|
||||
displayName: 'Template Data',
|
||||
description:
|
||||
'Key-value pairs for template variables. Keys must match variable names in the template (case-sensitive)',
|
||||
required: false,
|
||||
}),
|
||||
test: Property.Checkbox({
|
||||
displayName: 'Test Mode',
|
||||
description: 'If enabled, creates a test video with a watermark',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
title: Property.ShortText({
|
||||
displayName: 'Title',
|
||||
description: 'Custom title for the video',
|
||||
required: false,
|
||||
}),
|
||||
visibility: Property.StaticDropdown({
|
||||
displayName: 'Visibility',
|
||||
description: 'The visibility setting for the video',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Private', value: 'private' },
|
||||
{ label: 'Public', value: 'public' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'private',
|
||||
}),
|
||||
|
||||
description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
description: 'Custom description for the video',
|
||||
required: false,
|
||||
}),
|
||||
brandKitId: Property.ShortText({
|
||||
displayName: 'Brand Kit ID',
|
||||
description:
|
||||
'The ID of the brand kit to use (use "workspace_default" for default)',
|
||||
required: false,
|
||||
defaultValue: 'workspace_default',
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
templateId,
|
||||
templateData,
|
||||
test,
|
||||
visibility,
|
||||
title,
|
||||
description,
|
||||
brandKitId,
|
||||
} = context.propsValue;
|
||||
|
||||
const body: any = {
|
||||
templateId,
|
||||
test: test ? 'true' : 'false',
|
||||
visibility: visibility || 'private',
|
||||
};
|
||||
|
||||
if (templateData) {
|
||||
body.templateData = templateData;
|
||||
}
|
||||
|
||||
if (title) {
|
||||
body.title = title;
|
||||
}
|
||||
|
||||
if (description) {
|
||||
body.description = description;
|
||||
}
|
||||
|
||||
if (brandKitId) {
|
||||
body.brandKitId = brandKitId;
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: 'https://api.synthesia.io/v2/videos/fromTemplate',
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.auth.secret_text}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,172 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import { synthesiaAuth } from '../common/auth';
|
||||
|
||||
export const createVideo = createAction({
|
||||
auth: synthesiaAuth,
|
||||
name: 'createVideo',
|
||||
displayName: 'Create video',
|
||||
description:
|
||||
'Create a video within your Synthesia account with one or more scenes',
|
||||
props: {
|
||||
input_scriptText: Property.LongText({
|
||||
displayName: 'Input Script Text',
|
||||
description: 'An array of scenes to be included in the video',
|
||||
required: true,
|
||||
}),
|
||||
input_scriptAudioUrl: Property.ShortText({
|
||||
displayName: 'Input Script Audio URL',
|
||||
description:
|
||||
'URL of the audio file to be used for the video script (optional)',
|
||||
required: false,
|
||||
}),
|
||||
input_avatar: Property.ShortText({
|
||||
displayName: 'Input Avatar',
|
||||
description:
|
||||
'The avatar to be used in the video https://docs.synthesia.io/reference/avatars) ',
|
||||
required: true,
|
||||
defaultValue: 'anna_costume1_cameraA',
|
||||
}),
|
||||
|
||||
input_background: Property.StaticDropdown({
|
||||
displayName: 'Background',
|
||||
description:
|
||||
'Background for the video. Can be a stock background, custom asset ID, or URL',
|
||||
required: true,
|
||||
defaultValue: 'green_screen',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Green Screen (Transparent)', value: 'green_screen' },
|
||||
{ label: 'Off White (Solid)', value: 'off_white' },
|
||||
{ label: 'Warm White (Solid)', value: 'warm_white' },
|
||||
{ label: 'Light Pink (Solid)', value: 'light_pink' },
|
||||
{ label: 'Soft Pink (Solid)', value: 'soft_pink' },
|
||||
{ label: 'Light Blue (Solid)', value: 'light_blue' },
|
||||
{ label: 'Dark Blue (Solid)', value: 'dark_blue' },
|
||||
{ label: 'Soft Cyan (Solid)', value: 'soft_cyan' },
|
||||
{ label: 'Strong Cyan (Solid)', value: 'strong_cyan' },
|
||||
{ label: 'Light Orange (Solid)', value: 'light_orange' },
|
||||
{ label: 'Soft Orange (Solid)', value: 'soft_orange' },
|
||||
{ label: 'White Studio (Image)', value: 'white_studio' },
|
||||
{ label: 'White Cafe (Image)', value: 'white_cafe' },
|
||||
{ label: 'Luxury Lobby (Image)', value: 'luxury_lobby' },
|
||||
{ label: 'Large Window (Image)', value: 'large_window' },
|
||||
{
|
||||
label: 'White Meeting Room (Image)',
|
||||
value: 'white_meeting_room',
|
||||
},
|
||||
{ label: 'Open Office (Image)', value: 'open_office' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
test: Property.Checkbox({
|
||||
displayName: 'Test Mode',
|
||||
description: 'If enabled, creates a test video with a watermark',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
title: Property.ShortText({
|
||||
displayName: 'Title',
|
||||
description: 'Custom title for the video (displayed on the share page)',
|
||||
required: false,
|
||||
}),
|
||||
description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
description:
|
||||
'Custom description for the video (displayed on the share page)',
|
||||
required: false,
|
||||
}),
|
||||
visibility: Property.StaticDropdown({
|
||||
displayName: 'Visibility',
|
||||
description: 'Video visibility setting',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Private', value: 'private' },
|
||||
{ label: 'Public', value: 'public' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'private',
|
||||
}),
|
||||
brandKitId: Property.ShortText({
|
||||
displayName: 'Brand Kit ID',
|
||||
description:
|
||||
'The ID of the brand kit to use (use "workspace_default" for default)',
|
||||
required: false,
|
||||
defaultValue: 'workspace_default',
|
||||
}),
|
||||
callbackId: Property.ShortText({
|
||||
displayName: 'Callback ID',
|
||||
description: 'Arbitrary metadata/identifier for the video',
|
||||
required: false,
|
||||
}),
|
||||
pushServiceUrl: Property.ShortText({
|
||||
displayName: 'Webhook URL',
|
||||
description: 'Webhook URL for push notifications when video completes',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
input_avatar,
|
||||
input_background,
|
||||
input_scriptText,
|
||||
input_scriptAudioUrl,
|
||||
|
||||
test,
|
||||
title,
|
||||
description,
|
||||
visibility,
|
||||
brandKitId,
|
||||
callbackId,
|
||||
pushServiceUrl,
|
||||
} = context.propsValue;
|
||||
|
||||
const body: any = {
|
||||
input: {
|
||||
script: {
|
||||
type: 'text',
|
||||
text: input_scriptText,
|
||||
},
|
||||
avatar: input_avatar,
|
||||
background: input_background,
|
||||
audio: input_scriptAudioUrl,
|
||||
},
|
||||
test: test ? 'true' : 'false',
|
||||
visibility: visibility || 'private',
|
||||
};
|
||||
|
||||
if (title) {
|
||||
body.title = title;
|
||||
}
|
||||
|
||||
if (description) {
|
||||
body.description = description;
|
||||
}
|
||||
|
||||
if (brandKitId) {
|
||||
body.brandKitId = brandKitId;
|
||||
}
|
||||
|
||||
if (callbackId) {
|
||||
body.callbackId = callbackId;
|
||||
}
|
||||
|
||||
if (pushServiceUrl) {
|
||||
body.pushServiceUrl = pushServiceUrl;
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: 'https://api.synthesia.io/v2/videos',
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.auth.secret_text}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { PieceAuth } from '@activepieces/pieces-framework';
|
||||
|
||||
const markdownDescription = `
|
||||
To get your Synthesia API Key:
|
||||
|
||||
1. Go to the upper right corner of the Synthesia application and click on your account.
|
||||
2. Select **Integrations**
|
||||
3. Click on **Add** to add a new Synthesia API key.
|
||||
4. Copy this key using the 3-dot button on the right side of your API key.
|
||||
5. Paste it below.
|
||||
|
||||
**Note:** When you create an API key it will belong to your account, not the workspace.
|
||||
`;
|
||||
|
||||
export const synthesiaAuth = PieceAuth.SecretText({
|
||||
displayName: 'API Key',
|
||||
description: markdownDescription,
|
||||
required: true,
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Property } from '@activepieces/pieces-framework';
|
||||
import { synthesiaAuth } from './auth';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const templateIdDropdown = Property.Dropdown({
|
||||
auth: synthesiaAuth,
|
||||
displayName: 'Template',
|
||||
description: 'Select the template to use for video creation',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Connect your account',
|
||||
};
|
||||
}
|
||||
try {
|
||||
const apiKey = auth?.secret_text;
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: 'https://api.synthesia.io/v2/templates',
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.body.templates.map((template: any) => ({
|
||||
label: template.title,
|
||||
value: template.id,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Unable to fetch templates',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { synthesiaAuth } from '../common/auth';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
interface WebhookData {
|
||||
webhookId: string;
|
||||
}
|
||||
|
||||
export const newVideoReady = createTrigger({
|
||||
auth: synthesiaAuth,
|
||||
name: 'newVideoReady',
|
||||
displayName: 'New Video Ready',
|
||||
description: 'Trigger when a new video is completed in Synthesia',
|
||||
props: {},
|
||||
sampleData: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
async onEnable(context) {
|
||||
const webhookUrl = context.webhookUrl;
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: 'https://api.synthesia.io/v2/webhooks',
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.auth.secret_text}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
url: webhookUrl,
|
||||
events: ['video.completed'],
|
||||
},
|
||||
});
|
||||
|
||||
await context.store.put<WebhookData>('webhookData', {
|
||||
webhookId: response.body.id,
|
||||
});
|
||||
|
||||
},
|
||||
async onDisable(context) {
|
||||
const webhookData = await context.store.get<WebhookData>('webhookData');
|
||||
if (webhookData) {
|
||||
await httpClient.sendRequest({
|
||||
method: HttpMethod.DELETE,
|
||||
url: `https://api.synthesia.io/v2/webhooks/${webhookData.webhookId}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.auth.secret_text}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
await context.store.delete('webhookData');
|
||||
},
|
||||
async run(context) {
|
||||
const payload = context.payload as any;
|
||||
if (payload.type !== 'video.completed') {
|
||||
return [];
|
||||
}
|
||||
return [context.payload.body];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"importHelpers": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user