├── .editorconfig ├── .github └── workflows │ └── sync.yml ├── .prettierrc.json ├── readme.md └── templates ├── air_table ├── config.js ├── index.js └── simulator.js ├── basic-google-search ├── config.js ├── index.js ├── readme.md └── simulator.js ├── crawl-website-apify ├── config.js ├── index.js └── simulator.js ├── discord-webhook ├── config.js ├── index.js └── simulator.js ├── fetch-calendly-availability ├── config.js ├── index.js └── simulator.js ├── fetch-get ├── config.js ├── index.js └── simulator.js ├── full-example ├── config.js ├── index.js └── simulator.js ├── g2-scraper ├── config.js ├── index.js └── simulator.js ├── generateFile ├── config.js ├── index.js └── simulator.js ├── hello-world ├── config.js ├── index.js └── simulator.js ├── ifttt ├── config.js ├── index.js └── simulator.js ├── json-parser ├── config.js ├── index.js └── simulator.js ├── mailchimp ├── config.js ├── index.js └── simulator.js ├── mailgun-email ├── config.js ├── index.js └── simulator.js ├── makeDotCom ├── config.js ├── index.js └── simulator.js ├── normalize-url ├── config.js ├── index.js └── simulator.js ├── postgresql ├── config.js ├── index.js └── simulator.js ├── telegram-message ├── config.js ├── index.js └── simulator.js ├── text-encoding ├── config.js ├── index.js └── simulator.js ├── text-replace ├── config.js ├── index.js └── simulator.js ├── text-transform ├── config.js ├── index.js └── simulator.js ├── text-validate ├── config.js ├── index.js └── simulator.js ├── twilio-sms ├── config.js ├── index.js └── simulator.js ├── url-scraper ├── config.js ├── index.js └── simulator.js └── zapier ├── config.js ├── index.js └── simulator.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # we recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [{package,bower}.json] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: 'Sync with MindStudio' 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | secrets: 8 | HOOK_URL: 9 | required: true 10 | workflow_dispatch: 11 | secrets: 12 | HOOK_URL: 13 | required: true 14 | 15 | jobs: 16 | deploy: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Make POST request to sync functions 20 | run: | 21 | curl -X POST -H "Content-Type: application/json" -d '{}' ${{ secrets.HOOK_URL }} 22 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth":80, 3 | "tabWidth":2, 4 | "useTabs":false, 5 | "semi":true, 6 | "singleQuote":true, 7 | "trailingComma":"all", 8 | "bracketSpacing":true, 9 | "arrowParens":"always" 10 | } 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # MindStudio Custom Function Templates 2 | 3 | ## Introduction 4 | Welcome to the MindStudio Custom Function Template repository, where developers contribute JavaScript functions specifically designed for non-technical people to use inside MindStudio. This repository is a hub where creativity meets functionality, enabling users to extend the capabilities of the MindStudio platform with custom JavaScript functions. 5 | 6 | ## Contribution Guidelines 7 | 8 | ### Development Guidelines 9 | 1. **Function Requirements**: Each JavaScript function should be self-contained, well-commented, and adhere to standard coding practices. Ensure your function is tested and works seamlessly within MindStudio. 10 | 3. **Single-Purpose**: Don't try to do too much inside a single function. Do one thing and do it well. This will help others to use your function effectively within their own apps. 11 | 4. **Naming Conventions**: Use clear and descriptive names for your functions and variables. Avoid abbreviations and overly complex terminology. 12 | 5. **API Keys**: If your function relies on third-party APIs that require API keys, include instructions to help users register for those services and find their keys. 13 | 14 | ### Submitting Your Contribution 15 | 1. **Create a New Branch**: For each new function, create a separate branch in your forked repository. 16 | 2. **Commit Your Changes**: Make your changes in the new branch and commit them with a clear message describing the addition or change. 17 | 3. **Create a Pull Request (PR)**: Once your function is ready, create a pull request to the main repository. Ensure the PR title and description clearly state the purpose and functionality of your contribution. 18 | 4. **Code Review**: Your PR will be reviewed by the community. Be open to suggestions and required changes. 19 | 5. **Merge**: Once your PR is approved, it will be merged into the main repository, making your function available to the community. 20 | 21 | ### Pull Request Review Process 22 | - **Code Quality**: Ensure your code is well-written, efficient, and follows best practices. 23 | - **Functionality**: The function should work as intended without causing any issues in MindStudio. 24 | - **Documentation**: Proper documentation for understanding and implementing the function is essential. 25 | - **Community Standards**: Respect the community while interacting and contributing. 26 | 27 | ## Community and Support 28 | - **Issues**: For bugs and feature requests, open an issue in the repository. 29 | - **Discussions**: Join our community on [Discord](https://discord.gg/youai-mindstudio) for support, to share ideas, and to collaborate. 30 | - **Contribution**: Everyone is welcome to contribute. Your knowledge and skills can greatly benefit others. 31 | -------------------------------------------------------------------------------- /templates/air_table/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Airtable Integration", 3 | description: "Script to create a new record in an Airtable base", 4 | author: 'Kevin L', 5 | thumbnailUrl: 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454405412.png', 6 | blockStyle: { 7 | backgroundImageUrl: 8 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454435200.png', 9 | foregroundColor: '#000000', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Success value', 18 | variable: 'successOut', 19 | type: 'text', 20 | }, 21 | { 22 | label: 'Airtable API KEY', 23 | variable: 'airtable_api_key', 24 | helpText: 'API key from Airtable. ', 25 | type: 'secret', 26 | }, 27 | { 28 | label: 'Airtable base id', 29 | variable: 'airtable_base_id', 30 | helpText: 'API key from Airtable. ', 31 | type: 'text', 32 | }, 33 | { 34 | label: 'Airtable Table Name', 35 | variable: 'airtable_table_name', 36 | helpText: 'API key from Airtable. ', 37 | type: 'text', 38 | }, 39 | { 40 | label: 'Title', 41 | variable: 'title', 42 | helpText: 'Extract title from text', 43 | type: 'text', 44 | }, 45 | { 46 | label: 'Description', 47 | variable: 'description', 48 | helpText: 'extract description from text ', 49 | type: 'text', 50 | }, 51 | { 52 | label: 'Keywords', 53 | variable: 'keywords', 54 | helpText: 'extract keywords from text ', 55 | type: 'text', 56 | }, 57 | ], 58 | } 59 | ], 60 | } 61 | -------------------------------------------------------------------------------- /templates/air_table/index.js: -------------------------------------------------------------------------------- 1 | // Presuming that the ai.vars object has the relevant Airtable API credentials and data 2 | const AIRTABLE_API_KEY = ai.getConfig('airtable_api_key'); 3 | const AIRTABLE_BASE_ID = ai.getConfig('airtable_base_id'); 4 | const AIRTABLE_TABLE_NAME = ai.getConfig('airtable_table_name'); 5 | 6 | const headers = { 7 | 'Authorization': `Bearer ${AIRTABLE_API_KEY}`, 8 | 'Content-Type': 'application/json' 9 | }; 10 | 11 | const data = { 12 | "records": [ 13 | { 14 | "fields": { 15 | "title": ai.getConfig('title'), 16 | "description": ai.getConfig('description'), 17 | "keywords": ai.getConfig('keywords'), 18 | } 19 | } 20 | ] 21 | }; 22 | const url = `https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/${AIRTABLE_TABLE_NAME}`; 23 | const successOut = ai.config.success 24 | // Make a POST request to Airtable 25 | const request = await fetch(url, { 26 | method: 'POST', 27 | headers: headers, 28 | body: JSON.stringify(data) 29 | }); 30 | 31 | // Check if the request was successful 32 | if (request.ok) { 33 | // The POST request was successful 34 | ai.vars.success = "done"; 35 | } else { 36 | // There was an error with the request, log it and/or set an error flag 37 | console.error("Error during POST request to Airtable."); 38 | ai.vars.success = "failed"; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /templates/air_table/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | successOut: "", 4 | airtable_api_key:"API_KEY", 5 | airtable_base_id:"BASE_ID", 6 | airtable_table_name:"TABLE", 7 | title:"title", 8 | description:"description", 9 | keywords:"keywords", 10 | }, 11 | vars: {} 12 | } 13 | -------------------------------------------------------------------------------- /templates/basic-google-search/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Google Search - Basic", 3 | description: "Search Google and save results as a variable", 4 | author: 'MindStudio', 5 | thumbnailUrl: 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454492491.png', 6 | blockStyle: { 7 | backgroundImageUrl: 8 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454546507.png', 9 | foregroundColor: '#000000', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Search Query', 18 | variable: 'query', 19 | helpText: 'Search query can be a string or a {{variable}}', 20 | type: 'text', 21 | }, 22 | { 23 | label: 'Output Variable', 24 | variable: 'outputVariable', 25 | type: 'text', 26 | }, 27 | ], 28 | }, 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /templates/basic-google-search/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the query. 3 | * 4 | * Note: While we could use ai.config.query to get the query, 5 | * ai.getConfig(configVariableName) will automatically resolve 6 | * the value if the provided value references another variable. 7 | * 8 | * For example, if you just wanted to search for "cats" every 9 | * time this function runs, you could set query to "cats" in 10 | * the function configuration and get it with ai.config.query. 11 | * 12 | * But if you to use a User Input block to get a query from 13 | * a user and save it as variable myQuery, you would type 14 | * {{myQuery}} in the function configuration. But then 15 | * ai.config.query would return the literal value "{{myQuery}}". 16 | * ai.getConfig('query'), on the other hand, will automatically 17 | * resolve {{myQuery}} to whatever the user's query is. 18 | */ 19 | const query = ai.getConfig('query'); 20 | 21 | // Make sure there is a query. Otherwise, we have nothing to 22 | // search for. 23 | if (!query) { 24 | console.log('No query defined'); 25 | return; 26 | } 27 | 28 | ai.log("Searching..."); 29 | 30 | // Execute the search 31 | const result = await ai.searchGoogle(query); 32 | 33 | /** 34 | * Searching returns an object with two keys: 35 | * - text: The search result page structured as text, which 36 | * makes it easy to use as a variable in a message 37 | * - results: The results structured as an array of 38 | * { title, description, url }, for more advanced 39 | * processing. 40 | */ 41 | console.log(result.text); 42 | console.log(result.results); 43 | 44 | // Assign the text result to the variable 45 | const { outputVariable } = ai.config; 46 | ai.vars[outputVariable] = result.text; 47 | -------------------------------------------------------------------------------- /templates/basic-google-search/readme.md: -------------------------------------------------------------------------------- 1 | # Google Search 2 | 3 | Basic Google Search using custom function. 4 | -------------------------------------------------------------------------------- /templates/basic-google-search/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | query: "cats", 4 | outputVariable: "searchResult" 5 | }, 6 | vars: {} 7 | } 8 | -------------------------------------------------------------------------------- /templates/crawl-website-apify/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Crawl website - Apify', 3 | description: 'Crawls your website, scrapes all content in the pages, and saves it as markdown in a file.', 4 | author: 'Giorgio', 5 | thumbnailUrl: 'https://i.ibb.co/Jm8fLFp/Crawl-URL.jpg', 6 | blockStyle: { 7 | backgroundImageUrl: 'https://i.ibb.co/Jm8fLFp/Crawl-URL.jpg', 8 | foregroundColor: '#111111', 9 | label: ' ' 10 | }, 11 | configurationSections: [ 12 | { 13 | title: 'Apify Configuration', 14 | items: [ 15 | { 16 | label: 'Apify Token', 17 | variable: 'apify_token', 18 | helpText: 'Your Apify API token', 19 | type: 'secret', 20 | }, 21 | { 22 | label: 'URL to Crawl', 23 | variable: 'url', 24 | helpText: 'The starting URL for the crawler', 25 | type: 'text', 26 | }, 27 | { 28 | label: 'Max Crawl Depth', 29 | variable: 'maxCrawlDepth', 30 | helpText: 'Maximum depth for crawling (enter a number)', 31 | type: 'text', 32 | }, 33 | { 34 | label: 'Max Crawl Pages', 35 | variable: 'maxCrawlPages', 36 | helpText: 'Maximum number of pages to crawl (enter a number)', 37 | type: 'text', 38 | }, 39 | { 40 | label: 'Output Variable', 41 | variable: 'outputVar', 42 | helpText: 'Variable to store the uploaded CSV file URL', 43 | type: 'text', 44 | }, 45 | ], 46 | }, 47 | ], 48 | } -------------------------------------------------------------------------------- /templates/crawl-website-apify/index.js: -------------------------------------------------------------------------------- 1 | const token = ai.getConfig('apify_token'); 2 | const maxCrawlDepth = ai.getConfig('maxCrawlDepth'); 3 | const maxCrawlPages = ai.getConfig('maxCrawlPages'); 4 | const url = ai.getConfig('url'); 5 | const outputVar = ai.getConfig('outputVar'); 6 | 7 | ai.log('Starting Apify crawler...'); 8 | 9 | // Check if any required config is missing 10 | if (!token || !url || !outputVar) { 11 | ai.crmLog('Missing required configuration: token, url, or outputVar.'); 12 | return; 13 | } 14 | 15 | const apiUrl = `https://api.apify.com/v2/acts/apify~website-content-crawler/run-sync-get-dataset-items?token=${token}`; 16 | 17 | const body = { 18 | aggressivePrune: false, 19 | clickElementsCssSelector: '[aria-expanded="false"]', 20 | clientSideMinChangePercentage: 15, 21 | crawlerType: 'playwright:adaptive', 22 | debugLog: false, 23 | debugMode: false, 24 | ignoreCanonicalUrl: false, 25 | maxCrawlDepth: maxCrawlDepth ? Number(maxCrawlDepth) : 5, // default to 5 if not provided 26 | maxCrawlPages: maxCrawlPages ? Number(maxCrawlPages) : 100, // default to 100 if not provided 27 | proxyConfiguration: { 28 | useApifyProxy: true, 29 | }, 30 | readableTextCharThreshold: 100, 31 | removeCookieWarnings: true, 32 | removeElementsCssSelector: 33 | 'nav, footer, script, style, noscript, svg,\n[role="alert"],\n[role="banner"],\n[role="dialog"],\n[role="alertdialog"],\n[role="region"][aria-label*="skip" i],\n[aria-modal="true"]', 34 | renderingTypeDetectionPercentage: 10, 35 | saveFiles: false, 36 | saveHtml: false, 37 | saveHtmlAsFile: false, 38 | saveMarkdown: true, 39 | saveScreenshots: false, 40 | startUrls: [ 41 | { 42 | url: url, 43 | }, 44 | ], 45 | useSitemaps: false, 46 | }; 47 | 48 | const toBase64 = (str) => { 49 | try { 50 | return btoa(unescape(encodeURIComponent(str))); 51 | } catch (e) { 52 | console.error('Failed to convert to Base64: ', e); 53 | return null; 54 | } 55 | }; 56 | 57 | const escapeCSV = (str) => { 58 | if (str == null) return ''; 59 | return `"${str.replace(/"/g, '""')}"`; 60 | }; 61 | 62 | try { 63 | ai.log('Making API call to Apify...'); 64 | const response = await fetch(apiUrl, { 65 | method: 'POST', 66 | headers: { 67 | 'Content-Type': 'application/json', 68 | }, 69 | body: JSON.stringify(body), 70 | }); 71 | 72 | if (!response.ok) { 73 | throw new Error(`HTTP error! status: ${response.status}`); 74 | } 75 | 76 | const data = await response.json(); 77 | ai.log('API call successful. Generating CSV...'); 78 | 79 | // Create CSV content 80 | let csvContent = 'URL,Markdown Content\n'; 81 | 82 | data.forEach((result) => { 83 | const url = escapeCSV(result.url); 84 | const markdown = escapeCSV(result.markdown); 85 | csvContent += `${url},${markdown}\n`; 86 | }); 87 | 88 | // Convert to Base64 89 | const base64String = toBase64(csvContent); 90 | 91 | // Upload file 92 | ai.log('Uploading CSV file...'); 93 | const fileUrl = await ai.uploadFile(base64String, 'text/csv', 'base64'); 94 | 95 | ai.vars[outputVar] = fileUrl; 96 | ai.log('CSV file generated and uploaded successfully.'); 97 | } catch (error) { 98 | ai.crmLog(`Error during process: ${error.message}`); 99 | ai.vars[outputVar] = 'Error occurred during the process'; 100 | } 101 | -------------------------------------------------------------------------------- /templates/crawl-website-apify/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | apify_token: "", 4 | maxCrawlDepth: "3", 5 | maxCrawlPages: "1", 6 | url: "https://docs.mindstudio.ai", 7 | outputVar: "csvFileUrl" 8 | }, 9 | vars: {} 10 | } -------------------------------------------------------------------------------- /templates/discord-webhook/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Discord Webhook', 3 | description: 'Send a Discord message through a webhook url', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701851940449.jpg', 7 | blockStyle: { 8 | backgroundImageUrl: 9 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861165885.jpg', 10 | foregroundColor: '#ffffff', 11 | label: ' ', 12 | }, 13 | configurationSections: [ 14 | { 15 | title: 'Configuration', 16 | items: [ 17 | { 18 | label: 'Discord Webhook URL', 19 | variable: 'discord_webhook_url', 20 | helpText: 'Webhook URL provided by Discord.', 21 | type: 'text', 22 | }, 23 | { 24 | label: 'Input', 25 | variable: 'discord_input', 26 | type: 'text', 27 | helpText: 28 | 'Text sent to the webhook. Can be a string or a {{variable}}.', 29 | }, 30 | ], 31 | }, 32 | ], 33 | } 34 | -------------------------------------------------------------------------------- /templates/discord-webhook/index.js: -------------------------------------------------------------------------------- 1 | const webhookUrl = ai.getConfig('discord_webhook_url'); 2 | const input = ai.getConfig('discord_input'); 3 | 4 | if (!input) { 5 | ai.crmLog('No input to send to Discord.'); 6 | return; 7 | } 8 | 9 | if (!webhookUrl) { 10 | ai.crmLog('Discord Webhook URL is required.'); 11 | return; 12 | } 13 | 14 | ai.log("Sending message..."); 15 | 16 | // Make a POST request to the Discord webhook URL 17 | const request = await fetch(webhookUrl, { 18 | method: 'POST', 19 | headers: { 20 | 'Content-Type': 'application/json', 21 | }, 22 | body: JSON.stringify({ content: input }), 23 | }); 24 | 25 | // Check if the request was successful 26 | if (request.ok) { 27 | ai.crmLog(`Message sent to Discord: ${input}`); 28 | } else { 29 | ai.crmLog(`Error during POST request to Discord. Data: ${input}`); 30 | } 31 | -------------------------------------------------------------------------------- /templates/discord-webhook/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | discord_webhook_url: '', 4 | discord_input: '{{input}}', 5 | }, 6 | vars: { 7 | input: 'test message' 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /templates/fetch-calendly-availability/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Calendly: Fetch Availability', 3 | description: 'Fetch the availability schedules.', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861038204.jpg', 7 | blockStyle: { 8 | backgroundImageUrl: 9 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861003652.jpg', 10 | foregroundColor: '#366BFF', 11 | label: ' ', 12 | }, 13 | configurationSections: [ 14 | { 15 | title: 'Configuration', 16 | items: [ 17 | { 18 | label: 'Calendly Personal Token', 19 | variable: 'calendly_token', 20 | helpText: 21 | 'See https://developer.calendly.com/how-to-authenticate-with-personal-access-tokens', 22 | type: 'secret', 23 | }, 24 | { 25 | label: 'Availability Output Variable', 26 | variable: 'availabilityOutputVar', 27 | helpText: 'Variable to assign the availability output to.', 28 | type: 'text', 29 | }, 30 | { 31 | label: 'Events Output Variable', 32 | variable: 'eventsOutputVar', 33 | helpText: 'Variable to assign the information about scheduled events to.', 34 | type: 'text', 35 | }, 36 | { 37 | label: 'Scheduling Link Output Variable', 38 | variable: 'schedulingOutputVar', 39 | helpText: 'Variable to assign the scheduling link output to.', 40 | type: 'text', 41 | }, 42 | ], 43 | }, 44 | ], 45 | } 46 | -------------------------------------------------------------------------------- /templates/fetch-calendly-availability/index.js: -------------------------------------------------------------------------------- 1 | const calendlyToken = ai.getConfig('calendly_token'); 2 | 3 | const availabilityOutputVar = 4 | ai.getConfig('availabilityOutputVar') || 'calendlyAvailability'; 5 | 6 | const eventsOutputVar = ai.getConfig('eventsOutputVar') || 'calendlyEvents'; 7 | 8 | const schedulingOutputVar = 9 | ai.getConfig('schedulingOutputVar') || 'calendlySchedulingLink'; 10 | 11 | if (!calendlyToken) { 12 | ai.crmLog('Calendly Personal Access Token is required.'); 13 | return; 14 | } 15 | 16 | const useMockupUrls = false; 17 | 18 | const headers = { 19 | 'Content-Type': 'application/json', 20 | Authorization: `Bearer ${calendlyToken}`, 21 | }; 22 | 23 | /** 24 | * Fetch current user 25 | */ 26 | let currentUserUri = ''; 27 | let schedulingLink = ''; 28 | 29 | ai.log("Fetching availability..."); 30 | 31 | try { 32 | const request = await fetch( 33 | useMockupUrls 34 | ? `https://stoplight.io/mocks/calendly/api-docs/395/users/me` 35 | : `https://api.calendly.com/users/me`, 36 | { 37 | method: 'GET', 38 | headers, 39 | }, 40 | ); 41 | 42 | const response = await request.json(); 43 | 44 | currentUserUri = response.resource.uri; 45 | schedulingLink = response.resource.scheduling_url; 46 | } catch (err) { 47 | console.error(`Error during "GetCurrentUser" request.`); 48 | console.log(err); 49 | return; 50 | } 51 | 52 | if (!currentUserUri || !schedulingLink) { 53 | ai.crmLog('User URI & Scheduling Link are required'); 54 | return; 55 | } 56 | 57 | /** 58 | * Fetch availability 59 | */ 60 | let availability = ''; 61 | let events = ''; 62 | 63 | try { 64 | const now = new Date(); 65 | 66 | const endTime = new Date(); 67 | endTime.setDate(now.getDate() + 7); 68 | 69 | const startTimeFormatted = now.toISOString(); 70 | const endTimeFormatted = endTime.toISOString(); 71 | 72 | const calendlyEventsUrl = `https://api.calendly.com/user_busy_times?user=${currentUserUri}&start_time=${encodeURIComponent( 73 | startTimeFormatted, 74 | )}&end_time=${encodeURIComponent(endTimeFormatted)}`; 75 | 76 | const eventsRequest = await fetch( 77 | useMockupUrls 78 | ? `https://stoplight.io/mocks/calendly/api-docs/395/user_availability_schedules?user=${currentUserUri}` 79 | : calendlyEventsUrl, 80 | { 81 | method: 'GET', 82 | headers, 83 | }, 84 | ); 85 | 86 | events = await eventsRequest.text(); 87 | 88 | const availabilityRequest = await fetch( 89 | useMockupUrls 90 | ? `https://stoplight.io/mocks/calendly/api-docs/395/user_availability_schedules?user=${currentUserUri}` 91 | : `https://api.calendly.com/user_availability_schedules?user=${currentUserUri}`, 92 | { 93 | method: 'GET', 94 | headers, 95 | }, 96 | ); 97 | 98 | availability = await availabilityRequest.text(); 99 | } catch (err) { 100 | console.error(`Error during "FetchAvailability" request.`); 101 | console.log(err); 102 | return; 103 | } 104 | 105 | /** 106 | * Assign vars 107 | */ 108 | ai.vars[schedulingOutputVar] = schedulingLink; 109 | ai.vars[availabilityOutputVar] = availability; 110 | ai.vars[eventsOutputVar] = events; 111 | -------------------------------------------------------------------------------- /templates/fetch-calendly-availability/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | vars: {}, 3 | config: { 4 | calendly_token: '', 5 | availabilityOutputVar: 'availability', 6 | schedulingOutputVar: 'schedulingLink', 7 | eventsOutputVar: 'events', 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /templates/fetch-get/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Fetch Request (GET)", 3 | description: "Perform a GET request to the provided url", 4 | author: "Marko", 5 | thumbnailUrl: 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454324594.png', 6 | blockStyle: { 7 | backgroundImageUrl: 8 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454278826.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'URL', 18 | variable: 'url', 19 | helpText: 'Full URL of your GET request.', 20 | type: 'text', 21 | }, 22 | { 23 | label: 'Authorization', 24 | variable: 'authorization', 25 | helpText: 'Authorization to include in the header. Usually an API key.', 26 | type: 'secret', 27 | }, 28 | { 29 | label: 'Output Variable', 30 | variable: 'outputVar', 31 | helpText: 'Variable to assign the output to.', 32 | type: 'text', 33 | }, 34 | ], 35 | }, 36 | ], 37 | } -------------------------------------------------------------------------------- /templates/fetch-get/index.js: -------------------------------------------------------------------------------- 1 | const url = ai.getConfig('url'); 2 | const authorization = ai.getConfig('authorization'); 3 | const outputVarName = ai.getConfig('outputVar') || 'output'; 4 | 5 | if (!url) { 6 | ai.crmLog('No url provided.'); 7 | ai.vars[outputVarName] = 'No url provided.'; 8 | return; 9 | } 10 | 11 | let authorizationObject = {}; 12 | 13 | if (authorization) { 14 | authorizationObject = { 15 | Authorization: authorization, 16 | }; 17 | } 18 | 19 | const headers = { 20 | 'Content-Type': 'application/json', 21 | ...authorizationObject, 22 | }; 23 | 24 | const successOut = ai.config.success; 25 | // Make a GET request to the provided url 26 | const request = await fetch(url, { 27 | method: 'GET', 28 | headers: headers, 29 | }); 30 | 31 | ai.log("Performing request..."); 32 | 33 | const response = await request.json(); 34 | 35 | if (request.ok) { 36 | // The request was successful 37 | const result = JSON.stringify(response); 38 | ai.vars[outputVarName] = result; 39 | ai.crmLog(`GET request result: ${result}`); 40 | } else { 41 | // There was an error with the request, log it and/or set an error flag 42 | ai.vars[ 43 | outputVarName 44 | ] = `Error during GET request to ${url}.\n${JSON.stringify(response)}`; 45 | } 46 | -------------------------------------------------------------------------------- /templates/fetch-get/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | url: "", 4 | authorization: "", 5 | outputVar: "output", 6 | }, 7 | vars: { 8 | output: "" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /templates/full-example/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Custom Function Sample", 3 | description: "Sample showing all the APIs currently available in functions", 4 | author: 'MindStudio', 5 | thumbnailUrl: 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454649830.png', 6 | blockStyle: { 7 | backgroundImageUrl: 8 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454681979.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Temperature Destination', 18 | variable: 'temperatureOutputVar', 19 | type: 'text', 20 | }, 21 | { 22 | label: 'Text Destination', 23 | variable: 'textOutputVar', 24 | type: 'text', 25 | }, 26 | { 27 | label: 'Data Source', 28 | variable: 'dataSourceId', 29 | type: 'dataSource', 30 | }, 31 | ], 32 | }, 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /templates/full-example/index.js: -------------------------------------------------------------------------------- 1 | // Read a variable that was set by another automation 2 | console.log(ai.vars.myVariable); 3 | 4 | // Set a variable 5 | ai.vars.myVariable = "New Value" 6 | 7 | // Make a fetch request to an API 8 | // Note that this weather API is not reliable—it's just a sample! 9 | // Please do not use it in production!!!! 10 | const url = `https://goweather.herokuapp.com/weather/${ai.vars.cityName}`; 11 | const request = await fetch(url) 12 | const result = await request.json(); 13 | 14 | // Save the value to a variable name defined in the config 15 | const temperatureOutputVar = ai.config.temperatureOutputVar; 16 | ai.vars[temperatureOutputVar] = result.temperature; 17 | 18 | // Scrape a URL and save the first 100 words as a variable 19 | const urlResult = await ai.scrapeUrl('https://en.wikipedia.org/wiki/Preserved_Fish'); 20 | const textResult = urlResult.text; 21 | const snippet = textResult.split(' ').slice(0, 100).join(' '); 22 | ai.vars[ai.config.textOutputVar] = snippet; 23 | 24 | // Search Google and log all the URLs in the results 25 | const search = await ai.searchGoogle('cats'); 26 | const googleResults = search.results.map(({ url }) => url).join(', '); 27 | console.log(googleResults); 28 | 29 | // Query a data source (replace with IDs from variables) 30 | if (ai.config.dataSourceId) { 31 | const dataSourceResult = await ai.queryDataSource( 32 | ai.config.dataSourceId, 33 | "query text", 34 | numResults, 35 | ); 36 | 37 | console.log(dataSourceResult); 38 | } 39 | -------------------------------------------------------------------------------- /templates/full-example/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | temperatureOutputVar: "temp", 4 | textOutputVar: "textOutput", 5 | dataSourceId: "" 6 | }, 7 | vars: { 8 | myVariable: 'Existing Value', 9 | cityName: "New York" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /templates/g2-scraper/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'G2 Scraper', 3 | description: 'Scrape reviews for a product on G2.', 4 | author: 'Giorgio', 5 | thumbnailUrl: 'https://i.ibb.co/yPL7ZGs/G2-reviews.png', 6 | blockStyle: { 7 | backgroundImageUrl: 'https://i.ibb.co/yPL7ZGs/G2-reviews.png', 8 | foregroundColor: '#111111', 9 | label: ' ', 10 | }, 11 | configurationSections: [ 12 | { 13 | title: 'Configuration', 14 | items: [ 15 | { 16 | label: 'Apify API Token', 17 | variable: 'apify_token', 18 | helpText: 'Your Apify API token', 19 | type: 'secret', 20 | }, 21 | { 22 | label: 'Query', 23 | variable: 'query', 24 | helpText: 'The search query for G2 Explorer', 25 | type: 'text', 26 | }, 27 | { 28 | label: 'Limit', 29 | variable: 'limit', 30 | helpText: 'Maximum number of results to fetch (default: 50)', 31 | type: 'text', 32 | }, 33 | { 34 | label: 'Output Variable', 35 | variable: 'outputVar', 36 | helpText: 'Variable to store the CSV file URL', 37 | type: 'text', 38 | }, 39 | ], 40 | }, 41 | ], 42 | } -------------------------------------------------------------------------------- /templates/g2-scraper/index.js: -------------------------------------------------------------------------------- 1 | const token = ai.getConfig('apify_token'); 2 | const query = ai.getConfig('query'); 3 | const outputVar = ai.getConfig('outputVar'); 4 | const limit = parseInt(ai.getConfig('limit')) || 50; // Default to 50 if not provided 5 | 6 | if (!token || !query || !outputVar) { 7 | ai.crmLog('Missing required configuration: apify_token, query, or outputVar.'); 8 | return; 9 | } 10 | 11 | const apiUrl = `https://api.apify.com/v2/acts/jupri~g2-explorer/run-sync-get-dataset-items?token=${token}`; 12 | 13 | const body = { 14 | "dev_dataset_clear": false, 15 | "dev_dataset_enable": false, 16 | "dev_transform_enable": false, 17 | "include_article_content": false, 18 | "limit": limit, 19 | "no_parse": false, 20 | "query": query, 21 | "strict_search": false 22 | }; 23 | 24 | const escapeCSV = (value) => { 25 | if (value == null) return ''; 26 | value = String(value).replace(/"/g, '""'); 27 | return `"${value}"`; 28 | }; 29 | 30 | try { 31 | ai.log("Making API call to Apify..."); 32 | const response = await fetch(apiUrl, { 33 | method: 'POST', 34 | headers: { 35 | 'Content-Type': 'application/json' 36 | }, 37 | body: JSON.stringify(body) 38 | }); 39 | 40 | if (!response.ok) { 41 | throw new Error(`HTTP error! status: ${response.status}`); 42 | } 43 | 44 | const data = await response.json(); 45 | ai.log("API call successful. Generating CSV..."); 46 | 47 | // Create CSV content 48 | let csvContent = 'Answer 0,Answer 1,Answer 2,Answer 3,Date Published,Industry,Location Country,Location Primary,Location Region,Name,Product Slug,Role,Score,Segment,Source Review,Title,Type,URL\n'; 49 | 50 | data.forEach(result => { 51 | const answer0 = escapeCSV(result.answers && result.answers[0] || ''); 52 | const answer1 = escapeCSV(result.answers && result.answers[1] || ''); 53 | const answer2 = escapeCSV(result.answers && result.answers[2] || ''); 54 | const answer3 = escapeCSV(result.answers && result.answers[3] || ''); 55 | const datePublished = escapeCSV(result.date && result.date.published || ''); 56 | const industry = escapeCSV(result.industry || ''); 57 | const locationCountry = escapeCSV(result.location && result.location.country || ''); 58 | const locationPrimary = escapeCSV(result.location && result.location.primary || ''); 59 | const locationRegion = escapeCSV(result.location && result.location.region || ''); 60 | const name = escapeCSV(result.name || ''); 61 | const productSlug = escapeCSV(result.product && result.product.slug || ''); 62 | const role = escapeCSV(result.role || ''); 63 | const score = escapeCSV(result.score || ''); 64 | const segment = escapeCSV(result.segment || ''); 65 | const sourceReview = escapeCSV(result.source && result.source.review || ''); 66 | const title = escapeCSV(result.title || ''); 67 | const type = escapeCSV(result.type || ''); 68 | const url = escapeCSV(result.url || ''); 69 | 70 | csvContent += `${answer0},${answer1},${answer2},${answer3},${datePublished},${industry},${locationCountry},${locationPrimary},${locationRegion},${name},${productSlug},${role},${score},${segment},${sourceReview},${title},${type},${url}\n`; 71 | }); 72 | 73 | // Upload file 74 | ai.log("Uploading CSV file..."); 75 | const fileUrl = await ai.uploadFile(csvContent, 'text/csv', 'utf8'); 76 | 77 | ai.vars[outputVar] = fileUrl; 78 | ai.log('CSV file generated and uploaded successfully.'); 79 | } catch (error) { 80 | ai.crmLog(`Error during process: ${error.message}`); 81 | ai.vars[outputVar] = 'Error occurred during the process'; 82 | } -------------------------------------------------------------------------------- /templates/g2-scraper/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | apify_token: '', 4 | query: 'apify', 5 | limit: "3", 6 | outputVar: 'g2ExplorerResults', 7 | }, 8 | vars: {} 9 | } -------------------------------------------------------------------------------- /templates/generateFile/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | author: 'Marko', 3 | name: 'Generate File', 4 | description: 'Generate CSV, JSON, HTML, XML, or Markdown files from an input string.', 5 | thumbnailUrl: '', 6 | blockStyle: { 7 | backgroundImageUrl: '', 8 | backgroundColor: '#007AFF', 9 | foregroundColor: '#ffffff', 10 | label: 'Generate File', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Input', 18 | variable: 'input', 19 | type: 'text', 20 | helpText: 'Can be a string or {{variable}}.', 21 | }, 22 | { 23 | label: 'Output Variable', 24 | variable: 'outputVar', 25 | type: 'text', 26 | helpText: 'Variable to output the resulting file URL to.', 27 | }, 28 | { 29 | label: 'Output File Type', 30 | variable: 'fileType', 31 | type: 'select', 32 | selectOptions: [ 33 | { 34 | label: 'Plain Text (.txt)', 35 | value: 'txt', 36 | }, 37 | { 38 | label: 'CSV', 39 | value: 'csv', 40 | }, 41 | { 42 | label: 'HTML', 43 | value: 'html', 44 | }, 45 | { 46 | label: 'XML', 47 | value: 'xml', 48 | }, 49 | { 50 | label: 'JSON', 51 | value: 'json', 52 | }, 53 | { 54 | label: 'Markdown (.md)', 55 | value: 'markdown', 56 | }, 57 | ], 58 | }, 59 | ], 60 | }, 61 | ], 62 | } 63 | -------------------------------------------------------------------------------- /templates/generateFile/index.js: -------------------------------------------------------------------------------- 1 | // Read values from config 2 | const input = ai.getConfig('input'); 3 | const fileType = ai.getConfig('fileType') || 'txt'; 4 | const outputVar = ai.getConfig('outputVar'); 5 | 6 | const toBase64 = (str) => { 7 | try { 8 | return btoa(str); 9 | } catch (e) { 10 | console.error('Failed to convert to Base64: ', e); 11 | return null; 12 | } 13 | }; 14 | 15 | //=== Error handling 16 | if (!input) { 17 | ai.crmLog('No input defined.'); 18 | return; 19 | } 20 | 21 | if (!outputVar) { 22 | ai.crmLog('No output var defined.'); 23 | return; 24 | } 25 | //=== 26 | 27 | const resolveType = () => { 28 | if (fileType === 'csv') return 'text/csv'; 29 | if (fileType === 'json') return 'application/json'; 30 | if (fileType === 'html') return 'text/html'; 31 | if (fileType === 'xml') return 'application/xml'; 32 | if (fileType === 'markdown') return 'text/markdown'; 33 | return 'text/plain'; 34 | }; 35 | 36 | ai.log('Generating file...'); 37 | 38 | // Upload file and get the url 39 | try { 40 | const base64String = toBase64(input); 41 | const url = await ai.uploadFile(base64String, resolveType(), 'base64'); 42 | 43 | ai.vars[ai.config.outputVar] = url; 44 | } catch { 45 | ai.crmLog('Error during upload.'); 46 | ai.vars[ai.config.outputVar] = 'Error during upload.'; 47 | } 48 | -------------------------------------------------------------------------------- /templates/generateFile/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | input: '{{input}}', 4 | outputVar: 'output', 5 | fileType: 'html', 6 | }, 7 | vars: { 8 | input: 'test message', 9 | output: 'output', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /templates/hello-world/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Hello World", 3 | description: "A sample custom functions", 4 | author: 'MindStudio', 5 | thumbnailUrl: 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702453515478.png', 6 | blockStyle: { 7 | backgroundImageUrl: 8 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702453567587.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: "Configuration", 15 | items: [ 16 | { 17 | label: "Output Variable", 18 | variable: "outputVar", 19 | type: "text", 20 | }, 21 | ], 22 | }, 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /templates/hello-world/index.js: -------------------------------------------------------------------------------- 1 | ai.vars[ai.config.outputVar] = 'Hello World!'; -------------------------------------------------------------------------------- /templates/hello-world/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | outputVar: "myVariable", 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /templates/ifttt/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'IFTTT Webhook', 3 | description: 'Send data to a IFTTT webhook.', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861362996.jpg', 7 | blockStyle: { 8 | backgroundImageUrl: 9 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861323672.jpg', 10 | foregroundColor: '#ffffff', 11 | label: ' ', 12 | }, 13 | configurationSections: [ 14 | { 15 | title: 'Configuration', 16 | items: [ 17 | { 18 | label: 'Webhook Key', 19 | variable: 'ifttt_webhook_key', 20 | helpText: 'Webhook key provided by IFTTT', 21 | type: 'secret', 22 | }, 23 | { 24 | label: 'Webhook Event Name', 25 | variable: 'ifttt_event_name', 26 | helpText: 'Webhook event name created by you', 27 | type: 'text', 28 | }, 29 | { 30 | label: 'Input', 31 | variable: 'ifttt_input', 32 | type: 'text', 33 | helpText: 'Input can be a string or a {{variable}}.', 34 | }, 35 | ], 36 | }, 37 | ], 38 | } 39 | -------------------------------------------------------------------------------- /templates/ifttt/index.js: -------------------------------------------------------------------------------- 1 | const iftttKey = ai.getConfig('ifttt_webhook_key'); 2 | const eventName = ai.getConfig('ifttt_event_name'); 3 | const input = ai.getConfig('ifttt_input'); 4 | 5 | if (!input) { 6 | ai.crmLog('No input to send to IFTTT.'); 7 | return; 8 | } 9 | 10 | if (!eventName) { 11 | ai.crmLog('Event name is required'); 12 | return; 13 | } 14 | 15 | if (!iftttKey) { 16 | ai.crmLog('Webhook key is required'); 17 | return; 18 | } 19 | 20 | // Build webhook URL 21 | const url = `https://maker.ifttt.com/trigger/${eventName}/json/with/key/${iftttKey}` 22 | 23 | // Make a POST request to the IFTTT webhook URL 24 | const request = await fetch(url, { 25 | method: 'POST', 26 | headers: { 27 | 'Content-Type': 'application/json', 28 | }, 29 | body: JSON.stringify({ data: input }), 30 | }); 31 | 32 | // Check if the request was successful 33 | if (request.ok) { 34 | ai.crmLog(`Data sent to IFTTT: ${input}`); 35 | } else { 36 | console.error('Error during POST request to IFTTT.'); 37 | ai.crmLog(`Error during POST request to IFTTT. Data: ${input}`); 38 | } 39 | -------------------------------------------------------------------------------- /templates/ifttt/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | ifttt_webhook_key: '', 4 | ifttt_event_name: '', 5 | ifttt_input: '{{input}}', 6 | }, 7 | vars: {}, 8 | } 9 | -------------------------------------------------------------------------------- /templates/json-parser/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'JSON Parser', 3 | description: 'Parse JSON string and access it\'s properties', 4 | author: 'Victor', 5 | blockStyle: { 6 | backgroundColor: '#111111', 7 | foregroundColor: '#ffffff', 8 | label: 'JSON Parser', 9 | }, 10 | configurationSections: [ 11 | { 12 | title: 'Configuration', 13 | items: [ 14 | { 15 | label: 'JSON', 16 | variable: 'json_input', 17 | type: 'text', 18 | helpText: 'JSON to parse. Can be a string or a {{variable}}.', 19 | }, 20 | { 21 | label: 'Property', 22 | variable: 'json_property', 23 | type: 'text', 24 | helpText: 'You can use dot notation. Ex: name.first', 25 | }, 26 | { 27 | label: 'Output Variable', 28 | variable: 'outputVar', 29 | type: 'text', 30 | }, 31 | ], 32 | }, 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /templates/json-parser/index.js: -------------------------------------------------------------------------------- 1 | const jsonString = ai.getConfig('json_input') || ''; 2 | const jsonProperty = ai.getConfig('json_property'); 3 | 4 | function getNestedProperty(obj, path) { 5 | return path.split('.').reduce(function (acc, key) { 6 | return acc && acc[key]; 7 | }, obj); 8 | } 9 | 10 | let parsed = null; 11 | 12 | try { 13 | parsed = JSON.parse(jsonString); 14 | } catch (e) { 15 | ai.crmLog('Error during parsing the JSON string'); 16 | return; 17 | } 18 | 19 | if (!parsed) { 20 | return; 21 | } 22 | 23 | let result = null; 24 | 25 | try { 26 | result = getNestedProperty(parsed, jsonProperty); 27 | } catch (e) { 28 | ai.crmLog('Error accessing property using dot notation'); 29 | return; 30 | } 31 | 32 | ai.vars[ai.config.outputVar] = result; 33 | -------------------------------------------------------------------------------- /templates/json-parser/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | json_input: '{"status":"enabled","name":{"first":"Victor","middle":"Demin","last":"Dev"}}', 4 | json_property: 'name.first', 5 | outputVar: 'test', 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /templates/mailchimp/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Mailchimp - Collect Emails", 3 | description: "Collect emails for your Mailchimp mailing list.", 4 | author: "Marko", 5 | tutorialUrl: "https://www.youtube.com/watch?v=giWd2rA_Yus", 6 | thumbnailUrl: 7 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861432162.jpg', 8 | blockStyle: { 9 | backgroundImageUrl: 10 | "https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861401626.jpg", 11 | foregroundColor: "#111111", 12 | label: " ", 13 | }, 14 | configurationSections: [ 15 | { 16 | title: "Configuration", 17 | items: [ 18 | { 19 | label: "API Key", 20 | variable: "api_key", 21 | helpText: "You can find this in your account settings.", 22 | type: "secret", 23 | }, 24 | { 25 | label: "List ID", 26 | variable: "list_id", 27 | helpText: "You can find this in your Audience settings.", 28 | type: "text", 29 | }, 30 | { 31 | label: "Data Center", 32 | variable: "data_center", 33 | helpText: 34 | "Your API endpoint URL depends on the data center associated with your account. For example, if your API key is 1234567890-us1, your data center is us1.", 35 | type: "text", 36 | }, 37 | { 38 | label: "Input", 39 | variable: "input", 40 | type: "text", 41 | helpText: "Email address to be sent to Mailchimp. Can be a string or a {{variable}}.", 42 | }, 43 | ], 44 | }, 45 | ], 46 | } 47 | -------------------------------------------------------------------------------- /templates/mailchimp/index.js: -------------------------------------------------------------------------------- 1 | const listId = ai.getConfig('list_id'); 2 | const apiKey = ai.getConfig('api_key'); 3 | const dataCenter = ai.getConfig('data_center'); 4 | 5 | const url = `https://${dataCenter}.api.mailchimp.com/3.0/lists/${listId}/members/`; 6 | 7 | // Read the variable provided to get the input data 8 | const email = ai.getConfig("input"); 9 | 10 | if (!email) { 11 | ai.crmLog("No input to send to Mailchimp."); 12 | return; 13 | } 14 | 15 | const subscriber = { 16 | email_address: email, 17 | status: 'subscribed' 18 | }; 19 | 20 | 21 | // Make a POST request to the Mailchimp API 22 | try { 23 | await fetch(url, { 24 | method: 'POST', 25 | headers: { 26 | 'Content-Type': 'application/json', 27 | 'Authorization': `apikey ${apiKey}` 28 | }, 29 | body: JSON.stringify(subscriber) 30 | }); 31 | 32 | ai.crmLog(`Email subscribed to your mailchimp list: ${email}`); 33 | } catch (err) { 34 | console.error("Error during POST request to Mailchimp."); 35 | ai.crmLog(`Error during POST request to Mailchimp. Data: ${email}`); 36 | } 37 | -------------------------------------------------------------------------------- /templates/mailchimp/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | list_id: "", 4 | api_key: "", 5 | data_center: "", 6 | input: "", 7 | }, 8 | vars: { 9 | email: "" 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /templates/mailgun-email/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Mailgun - Send Email', 3 | description: 'Send an email with Mailgun', 4 | author: 'Marko', 5 | thumbnailUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702597577451.png', 6 | blockStyle: { 7 | backgroundImageUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702597594565.png', 8 | foregroundColor: '#984043', 9 | label: ' ', 10 | }, 11 | configurationSections: [ 12 | { 13 | title: 'Configuration', 14 | items: [ 15 | { 16 | label: 'Mailgun API Key', 17 | variable: 'apiKey', 18 | type: 'secret', 19 | }, 20 | { 21 | label: 'Mailgun Domain', 22 | variable: 'domain', 23 | type: 'text', 24 | helpText: 'This usually looks like yourdomainname.mailgun.org', 25 | }, 26 | { 27 | label: 'Sender Email', 28 | variable: 'from', 29 | type: 'text', 30 | }, 31 | { 32 | label: 'Receiver Email', 33 | variable: 'to', 34 | type: 'text', 35 | }, 36 | { 37 | label: 'Subject', 38 | variable: 'subject', 39 | type: 'text', 40 | helpText: 'Email subject.', 41 | }, 42 | { 43 | label: 'Text', 44 | variable: 'text', 45 | type: 'text', 46 | helpText: 'Email text.', 47 | }, 48 | { 49 | label: 'Success Output Variable', 50 | variable: 'text', 51 | type: 'text', 52 | helpText: '(Optional) Variable to assign the success output to.', 53 | }, 54 | ], 55 | }, 56 | ], 57 | } 58 | -------------------------------------------------------------------------------- /templates/mailgun-email/index.js: -------------------------------------------------------------------------------- 1 | //=== Config Data 2 | const apiKey = ai.getConfig('apiKey'); 3 | const domain = ai.getConfig('domain'); 4 | const from = ai.getConfig('from'); 5 | const to = ai.getConfig('to'); 6 | const subject = ai.getConfig('subject'); 7 | const text = ai.getConfig('text'); 8 | const successOutput = ai.getConfig('successVar'); 9 | //==== 10 | 11 | async function sendEmail(apiKey, domain, from, to, subject, text) { 12 | const url = `https://api.mailgun.net/v3/${domain}/messages`; 13 | 14 | // Manually build the URL-encoded form data 15 | const formData = 16 | 'from=' + 17 | encodeURIComponent(from) + 18 | '&to=' + 19 | encodeURIComponent(to) + 20 | '&subject=' + 21 | encodeURIComponent(subject) + 22 | '&text=' + 23 | encodeURIComponent(text); 24 | 25 | // Encode API key for Basic Auth using btoa 26 | const base64ApiKey = btoa(`api:${apiKey}`); 27 | 28 | const headers = { 29 | Authorization: `Basic ${base64ApiKey}`, 30 | 'Content-Type': 'application/x-www-form-urlencoded', 31 | }; 32 | 33 | try { 34 | const response = await fetch(url, { 35 | method: 'POST', 36 | headers: headers, 37 | body: formData, 38 | }); 39 | 40 | const data = await response.json(); 41 | return data; 42 | } catch (error) { 43 | console.error('Error sending email with Mailgun:', error); 44 | return null; 45 | } 46 | } 47 | 48 | if (!apiKey) { 49 | const errorMessage = 'ERROR: No API key provided.'; 50 | ai.crmLog(errorMessage); 51 | ai.vars[successOutput] = errorMessage; 52 | return; 53 | } 54 | 55 | if (!domain) { 56 | const errorMessage = 'ERROR: No domain provided.'; 57 | ai.crmLog(errorMessage); 58 | ai.vars[successOutput] = errorMessage; 59 | return; 60 | } 61 | 62 | if (!from || !to || !subject || !text) { 63 | const errorMessage = 'ERROR: Insufficient email data provided.'; 64 | ai.crmLog(errorMessage); 65 | ai.vars[successOutput] = errorMessage; 66 | return; 67 | } 68 | 69 | const success = await sendEmail(apiKey, domain, from, to, subject, text); 70 | 71 | if (!successOutput) return; 72 | 73 | if (success) { 74 | ai.vars[successOutput] = success.message; 75 | } 76 | -------------------------------------------------------------------------------- /templates/mailgun-email/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | apiKey: '', 4 | domain: '', 5 | from: 'marko@youai.ai', 6 | to: 'marko@youai.ai', 7 | subject: 'Test', 8 | text: 'Testing email.', 9 | successVar: 'success', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /templates/makeDotCom/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Make Webhook", 3 | description: "Send data to a Make webhook.", 4 | author: "Marko", 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861514359.jpg', 7 | blockStyle: { 8 | backgroundImageUrl: 9 | "https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861466111.jpg", 10 | foregroundColor: "#000000", 11 | label: " ", 12 | }, 13 | configurationSections: [ 14 | { 15 | title: "Configuration", 16 | items: [ 17 | { 18 | label: "Make Webhook URL", 19 | variable: "make_webhook_url", 20 | helpText: "Webhook URL provided by Make.", 21 | type: "text", 22 | }, 23 | { 24 | label: "Input", 25 | variable: "input", 26 | type: "text", 27 | helpText: 28 | "Text sent to the webhook. Can be a string or a {{variable}}.", 29 | }, 30 | ], 31 | }, 32 | ], 33 | } 34 | -------------------------------------------------------------------------------- /templates/makeDotCom/index.js: -------------------------------------------------------------------------------- 1 | // Presuming that the ai.config object has the relevant Make webhook URL 2 | const MAKE_WEBHOOK_URL = ai.getConfig("make_webhook_url"); 3 | 4 | // Read the variable provided to get the input data 5 | const input = ai.getConfig("input"); 6 | 7 | if (!input) { 8 | ai.crmLog("No input to send to Make."); 9 | return; 10 | } 11 | 12 | // Make a POST request to the Make webhook URL 13 | const request = await fetch(MAKE_WEBHOOK_URL, { 14 | method: "POST", 15 | headers: { 16 | "Content-Type": "application/json", 17 | }, 18 | body: JSON.stringify({ data: input }), 19 | }); 20 | 21 | // Check if the request was successful 22 | if (request.ok) { 23 | ai.crmLog(`Data sent to Make: ${input}`); 24 | } else { 25 | console.error("Error during POST request to Make."); 26 | ai.crmLog(`Error during POST request to Make. Data: ${input}`); 27 | } 28 | -------------------------------------------------------------------------------- /templates/makeDotCom/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | make_webhook_url: "", 4 | input: "", 5 | }, 6 | vars: {}, 7 | } 8 | -------------------------------------------------------------------------------- /templates/normalize-url/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Normalize URL', 3 | description: 4 | 'Standardizes URLs by correcting protocols, simplifying domain format, and maintaining consistent trailing slashes', 5 | author: 'Marko', 6 | thumbnailUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702581218270.png', 7 | blockStyle: { 8 | backgroundImageUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702581231593.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Input', 18 | variable: 'input', 19 | type: 'text', 20 | helpText: 'URL to normalize. Can be a string or a {{variable}}.', 21 | }, 22 | { 23 | label: 'Output Variable', 24 | variable: 'outputVar', 25 | type: 'text', 26 | }, 27 | ], 28 | }, 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /templates/normalize-url/index.js: -------------------------------------------------------------------------------- 1 | const normalizeUrl = (input) => { 2 | // Ensure the protocol is present and correct. Default to 'https://' if missing. 3 | let url = /^https?:\/\//i.test(input) ? input : `https://${input}`; 4 | 5 | // Remove 'www.' for a cleaner URL 6 | url = url.replace(/^(https?:\/\/)www\./i, '$1'); 7 | 8 | // Ensure URL ends with a trailing slash for consistency 9 | if (!/\/$/.test(url)) { 10 | url += '/'; 11 | } 12 | 13 | return url; 14 | }; 15 | 16 | //==== Config Data 17 | const input = ai.getConfig('input') || ''; 18 | const outputVar = ai.getConfig('outputVar'); 19 | //==== 20 | 21 | const fixedUrl = normalizeUrl(input); 22 | ai.vars[ai.config.outputVar] = `${fixedUrl}`; 23 | -------------------------------------------------------------------------------- /templates/normalize-url/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | input: 'example.com', 4 | outputVar: 'test', 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /templates/postgresql/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name:"PostGreSQL", 3 | description:"Talk to your postgresql database, public endpoint at https://github.com/braincorporg/YouAIPostGre", 4 | author:"Kevin L", 5 | thumbnailUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1706904267377.png', 6 | blockStyle: { 7 | backgroundImageUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1706904267377.png', 8 | foregroundColor: '#000000', 9 | label: ' ', 10 | }, 11 | configurationSections: [ 12 | { 13 | title: 'Configuration', 14 | items: [ 15 | { 16 | label: 'Database URL', 17 | variable: 'database_url', 18 | helpText: 'The URL of your PostgreSQL database.', 19 | type: 'text', 20 | }, 21 | { 22 | label: 'SQL Query', 23 | variable: 'query', 24 | helpText: 'The SQL query to execute.', 25 | type: 'text', 26 | }, 27 | { 28 | label: 'Response Data Variable', 29 | type: 'text', 30 | variable: 'queryResult', 31 | helpText: 'Store query result, name this as queryResult', 32 | }, 33 | ], 34 | }, 35 | ], 36 | } 37 | -------------------------------------------------------------------------------- /templates/postgresql/index.js: -------------------------------------------------------------------------------- 1 | // Presuming that the ai.vars object has the relevant database URL and query 2 | const DATABASE_URL = ai.getConfig('database_url'); 3 | const QUERY = ai.getConfig('query'); 4 | 5 | const headers = { 6 | 'Content-Type': 'application/json' 7 | }; 8 | 9 | const data = { 10 | "database_url": DATABASE_URL, 11 | "query": QUERY 12 | }; 13 | 14 | const url = "https://youai-postgre.onrender.com/query"; 15 | 16 | ai.log("Querying..."); 17 | 18 | // Make a POST request to your Flask application 19 | const request = await fetch(url, { 20 | method: 'POST', 21 | headers: headers, 22 | body: JSON.stringify(data) 23 | }); 24 | 25 | // Check if the request was successful 26 | if (request.ok) { 27 | const jsonData = await request.json(); 28 | console.log("Query successful."); 29 | console.log(jsonData); 30 | ai.vars.queryResult = jsonData; 31 | } else { 32 | console.error("Query failed with status:", request.status); 33 | ai.vars.queryResult = request.status; 34 | } 35 | -------------------------------------------------------------------------------- /templates/postgresql/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | database_url: "YOUR_EXTERNAL_DATABASE_URL", 4 | query:"SELECT * FROM users WHERE username ILIKE '%ANTHONY%';", 5 | queryResult: 'queryResult', 6 | }, 7 | vars: {} 8 | } 9 | -------------------------------------------------------------------------------- /templates/telegram-message/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Telegram: Send a Message', 3 | description: 'Send a message to your Telegram channel', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1703054868463.png', 7 | blockStyle: { 8 | backgroundImageUrl: 9 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1703054868463.png', 10 | foregroundColor: '#ffffff', 11 | label: ' ', 12 | }, 13 | configurationSections: [ 14 | { 15 | title: 'Configuration', 16 | items: [ 17 | { 18 | label: 'Telegram Bot API Key', 19 | variable: 'telegram_api_key', 20 | helpText: 'API Key generated by BotFather when you created your bot', 21 | type: 'secret', 22 | }, 23 | { 24 | label: 'Telegram Channel', 25 | variable: 'telegram_channel', 26 | helpText: 'The handle of your channel (e.g. @my_channel_name). The channel must be public', 27 | type: 'text', 28 | }, 29 | { 30 | label: 'Message', 31 | variable: 'telegram_message', 32 | helpText: 'Message can be a string or a {{variable}}.', 33 | type: 'text', 34 | }, 35 | ], 36 | }, 37 | ], 38 | } 39 | -------------------------------------------------------------------------------- /templates/telegram-message/index.js: -------------------------------------------------------------------------------- 1 | const apiKey = ai.getConfig('telegram_api_key'); 2 | const channel = ai.getConfig('telegram_channel'); 3 | const message = ai.getConfig('telegram_message'); 4 | 5 | ai.log("Sending message..."); 6 | 7 | const request = await fetch( 8 | `https://api.telegram.org/bot${apiKey}/sendMessage?chat_id=${channel}&text=${message}`, 9 | ); 10 | 11 | const response = await request.json(); 12 | 13 | if (!response.ok) { 14 | console.log(response.description); 15 | } 16 | -------------------------------------------------------------------------------- /templates/telegram-message/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | vars: {}, 3 | config: { 4 | telegram_api_key: '', 5 | telegram_channel: '@yourchannel', 6 | telegram_message: 'Hello World', 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /templates/text-encoding/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Text Encode', 3 | description: 'Common text encoding operations', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702485823414.png', 7 | blockStyle: { 8 | backgroundImageUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702485865759.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Text', 18 | variable: 'encoding_text', 19 | type: 'text', 20 | helpText: 'Text to encode. Can be a string or a {{variable}}.', 21 | }, 22 | { 23 | label: 'Operation', 24 | variable: 'encoding_operation', 25 | type: 'select', 26 | selectOptions: [ 27 | { 28 | label: 'Encode', 29 | value: 'encode', 30 | }, 31 | { 32 | label: 'Decode', 33 | value: 'decode', 34 | }, 35 | ], 36 | }, 37 | { 38 | label: 'Encoding', 39 | variable: 'encoding_method', 40 | type: 'select', 41 | selectOptions: [ 42 | { 43 | label: 'Base64', 44 | value: 'base64', 45 | }, 46 | { 47 | label: 'Uniform Resource Identifier', 48 | value: 'uri', 49 | }, 50 | ], 51 | }, 52 | { 53 | label: 'Output Variable', 54 | variable: 'outputVar', 55 | type: 'text', 56 | }, 57 | ], 58 | }, 59 | ], 60 | } 61 | -------------------------------------------------------------------------------- /templates/text-encoding/index.js: -------------------------------------------------------------------------------- 1 | const text = ai.getConfig('encoding_text') || ''; 2 | const operation = ai.getConfig('encoding_operation') || 'encode'; 3 | const method = ai.getConfig('encoding_method'); 4 | 5 | let transformed = text; 6 | 7 | if (method === 'uri') { 8 | transformed = 9 | operation === 'encode' 10 | ? encodeURIComponent(text) 11 | : decodeURIComponent(text); 12 | } 13 | 14 | if (method === 'base64') { 15 | transformed = 16 | operation === 'encode' 17 | ? btoa(text) 18 | : atob(text); 19 | } 20 | 21 | ai.vars[ai.config.outputVar] = transformed; 22 | -------------------------------------------------------------------------------- /templates/text-encoding/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | encoding_text: 'Hello World', 4 | encoding_operation: 'encode', 5 | encoding_method: 'uri', 6 | outputVar: 'test', 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /templates/text-replace/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Text Replace', 3 | description: 'Common text replacement operations', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702485823414.png', 7 | blockStyle: { 8 | backgroundImageUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702485845498.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Text', 18 | variable: 'replace_text', 19 | type: 'text', 20 | helpText: 'Initial Text. Can be a string or a {{variable}}.', 21 | }, 22 | { 23 | label: 'Replace Operation', 24 | variable: 'replace_operation', 25 | type: 'select', 26 | selectOptions: [ 27 | { 28 | label: 'Only First Occurance', 29 | value: 'first_occurance', 30 | }, 31 | { 32 | label: 'All Occurances', 33 | value: 'all_occurances', 34 | }, 35 | { 36 | label: 'Only First Occurance (Regex)', 37 | value: 'first_occurance_regex', 38 | }, 39 | { 40 | label: 'All Occurances (Regex)', 41 | value: 'all_occurances_regex', 42 | }, 43 | { 44 | label: 'Stop Words by comma', 45 | value: 'stop_words', 46 | }, 47 | ], 48 | }, 49 | { 50 | label: 'Pattern', 51 | variable: 'replace_pattern', 52 | type: 'text', 53 | }, 54 | { 55 | label: 'Replacement', 56 | variable: 'replace_replacement', 57 | type: 'text', 58 | }, 59 | { 60 | label: 'Output Variable', 61 | variable: 'outputVar', 62 | type: 'text', 63 | }, 64 | ], 65 | }, 66 | ], 67 | } 68 | -------------------------------------------------------------------------------- /templates/text-replace/index.js: -------------------------------------------------------------------------------- 1 | const text = ai.getConfig('replace_text') || ''; 2 | const operation = ai.getConfig('replace_operation') || 'first_occurance'; 3 | const pattern = ai.getConfig('replace_pattern'); 4 | const replacement = ai.getConfig('replace_replacement') || ''; 5 | 6 | let transformed = text; 7 | 8 | if (operation === 'first_occurance') { 9 | transformed = text.replace(pattern, replacement); 10 | } 11 | 12 | if (operation === 'all_occurances') { 13 | transformed = text.replaceAll(pattern, replacement); 14 | } 15 | 16 | if (operation === 'first_occurance_regex') { 17 | transformed = text.replace(new RegExp(pattern), replacement); 18 | } 19 | 20 | if (operation === 'all_occurances_regex') { 21 | transformed = text.replaceAll(new RegExp(pattern), replacement); 22 | } 23 | 24 | if (operation === 'stop_words') { 25 | const stopWords = pattern.split(','); 26 | 27 | transformed = stopWords.reduce((result, stopWord) => { 28 | return result.replaceAll(stopWord, replacement); 29 | }, text); 30 | } 31 | 32 | ai.vars[ai.config.outputVar] = transformed; 33 | -------------------------------------------------------------------------------- /templates/text-replace/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | replace_text: 'Hello world, world', 4 | replace_operation: 'first_occurance', 5 | replace_pattern: 'world', 6 | replace_replacement: 'universe', 7 | outputVar: 'test', 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /templates/text-transform/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Text Transform', 3 | description: 'Common text transform operations', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702485823414.png', 7 | blockStyle: { 8 | backgroundImageUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702485851917.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Text', 18 | variable: 'transform_text', 19 | type: 'text', 20 | helpText: 'Text to transform. Can be a string or a {{variable}}.', 21 | }, 22 | { 23 | label: 'Transform Operation', 24 | variable: 'transform_operation', 25 | type: 'select', 26 | selectOptions: [ 27 | { 28 | label: 'Uppercase', 29 | value: 'uppercase', 30 | }, 31 | { 32 | label: 'Lowercase', 33 | value: 'lowercase', 34 | }, 35 | { 36 | label: 'Trim', 37 | value: 'trim', 38 | }, 39 | { 40 | label: 'Trim Start', 41 | value: 'trimStart', 42 | }, 43 | { 44 | label: 'Trim End', 45 | value: 'trimEnd', 46 | }, 47 | ], 48 | }, 49 | { 50 | label: 'Output Variable', 51 | variable: 'outputVar', 52 | type: 'text', 53 | }, 54 | ], 55 | }, 56 | ], 57 | } 58 | -------------------------------------------------------------------------------- /templates/text-transform/index.js: -------------------------------------------------------------------------------- 1 | const text = ai.getConfig('transform_text') || ''; 2 | const operation = ai.getConfig('transform_operation'); 3 | 4 | let transformed = text; 5 | 6 | if (operation === 'uppercase') { 7 | transformed = text.toUpperCase(); 8 | } 9 | 10 | if (operation === 'lowercase') { 11 | transformed = text.toLowerCase(); 12 | } 13 | 14 | if (operation === 'trim') { 15 | transformed = text.trim(); 16 | } 17 | 18 | if (operation === 'trimStart') { 19 | transformed = text.trimStart(); 20 | } 21 | 22 | if (operation === 'trimEnd') { 23 | transformed = text.trimEnd(); 24 | } 25 | 26 | ai.vars[ai.config.outputVar] = transformed; 27 | -------------------------------------------------------------------------------- /templates/text-transform/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | transform_text: 'Hello World', 4 | transform_operation: 'uppercase', 5 | outputVar: 'test', 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /templates/text-validate/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Text Validate', 3 | description: 'Common text validate operations', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702485823414.png', 7 | blockStyle: { 8 | backgroundImageUrl: 'https://youai.imgix.net/images/a8284d01-9ec8-4683-a15d-7f4d7d922e5f_1702485839475.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Text', 18 | variable: 'validate_text', 19 | type: 'text', 20 | helpText: 'Text to validate. Can be a string or a {{variable}}.', 21 | }, 22 | { 23 | label: 'Validate Operation', 24 | variable: 'validate_operation', 25 | type: 'select', 26 | selectOptions: [ 27 | { 28 | label: 'Email Validation', 29 | value: 'email', 30 | }, 31 | { 32 | label: 'URL Validation', 33 | value: 'url', 34 | }, 35 | { 36 | label: 'Regex Validation', 37 | value: 'regex', 38 | }, 39 | ], 40 | }, 41 | { 42 | label: 'Regex (Regex Validation)', 43 | variable: 'validate_regex', 44 | type: 'text', 45 | }, 46 | { 47 | label: 'Output Variable', 48 | variable: 'outputVar', 49 | type: 'text', 50 | }, 51 | ], 52 | }, 53 | ], 54 | } 55 | -------------------------------------------------------------------------------- /templates/text-validate/index.js: -------------------------------------------------------------------------------- 1 | const text = ai.getConfig('validate_text') || ''; 2 | const operation = ai.getConfig('validate_operation'); 3 | const customRegex = ai.getConfig('validate_regex'); 4 | 5 | let isValid = null; 6 | 7 | if (operation === 'email') { 8 | const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 9 | isValid = regex.test(text); 10 | } 11 | 12 | if (operation === 'regex') { 13 | const regex = new RegExp(customRegex); 14 | isValid = regex.test(text); 15 | } 16 | 17 | if (operation === 'url') { 18 | const regex = new RegExp( 19 | '^([a-zA-Z]+:\\/\\/)?' + // protocol 20 | '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name 21 | '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR IP (v4) address 22 | '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path 23 | '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string 24 | '(\\#[-a-z\\d_]*)?$', // fragment locator 25 | 'i', 26 | ); 27 | 28 | isValid = regex.test(text); 29 | } 30 | 31 | ai.vars[ai.config.outputVar] = isValid; 32 | -------------------------------------------------------------------------------- /templates/text-validate/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | validate_text: 'Hello World', 4 | validate_operation: 'email', 5 | validate_regex: '', 6 | outputVar: 'test', 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /templates/twilio-sms/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: 'Twilio - Send SMS', 3 | description: 'Send SMS with Twilio', 4 | author: 'Victor', 5 | thumbnailUrl: 6 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1703609903923.jpg', 7 | blockStyle: { 8 | backgroundImageUrl: 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1703609975739.jpg', 9 | foregroundColor: '#111111', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'Twilio Account SID', 18 | variable: 'twilio_account_sid', 19 | type: 'secret', 20 | }, 21 | { 22 | label: 'Twilio Auth Token', 23 | variable: 'twilio_auth_token', 24 | type: 'secret', 25 | }, 26 | { 27 | label: 'SMS Text', 28 | variable: 'twilio_text', 29 | type: 'text', 30 | helpText: 'Text to send. Can be a string or a {{variable}}.', 31 | }, 32 | { 33 | label: 'FROM Number', 34 | variable: 'twilio_from', 35 | type: 'text', 36 | helpText: 'Number to send from.', 37 | }, 38 | { 39 | label: 'TO Number', 40 | variable: 'twilio_to', 41 | type: 'text', 42 | helpText: 'Number to send to.', 43 | }, 44 | ], 45 | }, 46 | ], 47 | } 48 | -------------------------------------------------------------------------------- /templates/twilio-sms/index.js: -------------------------------------------------------------------------------- 1 | const accountSid = ai.getConfig('twilio_account_sid'); 2 | const authToken = ai.getConfig('twilio_auth_token'); 3 | 4 | const text = ai.getConfig('twilio_text'); 5 | const from = ai.getConfig('twilio_from'); 6 | const to = ai.getConfig('twilio_to'); 7 | 8 | const data = `From=${encodeURIComponent(from)}&Body=${encodeURIComponent( 9 | text, 10 | )}&To=${encodeURIComponent(to)}`; 11 | 12 | const auth = 'Basic ' + btoa(`${accountSid}:${authToken}`); 13 | 14 | /** 15 | * Provide task specifications 16 | */ 17 | try { 18 | await fetch( 19 | `https://api.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, 20 | { 21 | method: 'POST', 22 | headers: { 23 | 'Content-Type': 'application/x-www-form-urlencoded', 24 | Authorization: auth, 25 | }, 26 | body: data, 27 | }, 28 | ); 29 | } catch (err) { 30 | console.log(err); 31 | console.error('Error during POST request.'); 32 | } 33 | -------------------------------------------------------------------------------- /templates/twilio-sms/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | twilio_account_sid: '', 4 | twilio_auth_token: '', 5 | twilio_text: 'Hello World', 6 | twilio_from: '', 7 | twilio_to: '', 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /templates/url-scraper/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Scrape URL", 3 | description: "Scrape a URL and save the resulting text to a variable.", 4 | author: 'Marko', 5 | thumbnailUrl: 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454101495.png', 6 | blockStyle: { 7 | backgroundImageUrl: 8 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1702454163990.png', 9 | foregroundColor: '#ffffff', 10 | label: ' ', 11 | }, 12 | configurationSections: [ 13 | { 14 | title: 'Configuration', 15 | items: [ 16 | { 17 | label: 'URL to scrape', 18 | variable: 'url', 19 | type: 'text', 20 | }, 21 | { 22 | label: 'Word count limit', 23 | variable: 'word_count', 24 | type: 'text', 25 | helpText: '(Optional) If empty, all available text will be scraped.' 26 | }, 27 | { 28 | label: 'Output Variable', 29 | variable: 'textOutputVar', 30 | type: 'text', 31 | }, 32 | ], 33 | }, 34 | ], 35 | } -------------------------------------------------------------------------------- /templates/url-scraper/index.js: -------------------------------------------------------------------------------- 1 | const url = ai.getConfig('url'); 2 | const wordCount = ai.getConfig('word_count'); 3 | 4 | ai.log('Fetching text...'); 5 | 6 | const urlResult = await ai.scrapeUrl(url); 7 | const textResult = urlResult.text; 8 | 9 | if (wordCount && Number(wordCount)) { 10 | const snippet = textResult.split(' ').slice(0, Number(wordCount)).join(' '); 11 | ai.vars[ai.config.textOutputVar] = snippet; 12 | } else { 13 | ai.vars[ai.config.textOutputVar] = textResult; 14 | } 15 | -------------------------------------------------------------------------------- /templates/url-scraper/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | url: "", 4 | word_count: "", 5 | textOutputVar: "" 6 | }, 7 | vars: {} 8 | } 9 | -------------------------------------------------------------------------------- /templates/zapier/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | name: "Zapier Webhook", 3 | description: "Send data to a Zapier webhook.", 4 | author: "Marko", 5 | tutorialUrl: "https://www.youtube.com/watch?v=b0La4G3HY1I", 6 | thumbnailUrl: 7 | 'https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861786958.jpg', 8 | blockStyle: { 9 | backgroundImageUrl: 10 | "https://youai.imgix.net/images/9e603bae-0732-4f04-8136-2eeec1f0a9fe_1701861733735.jpg", 11 | foregroundColor: "#111111", 12 | label: " ", 13 | }, 14 | configurationSections: [ 15 | { 16 | title: "Configuration", 17 | items: [ 18 | { 19 | label: "Zapier Webhook URL", 20 | variable: "zapier_webhook_url", 21 | helpText: "Webhook URL provided by Zapier.", 22 | type: "text", 23 | }, 24 | { 25 | label: "Input", 26 | variable: "input", 27 | type: "text", 28 | helpText: 29 | "Text sent to the webhook. Can be a string or a {{variable}}.", 30 | }, 31 | ], 32 | }, 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /templates/zapier/index.js: -------------------------------------------------------------------------------- 1 | // Presuming that the ai.config object has the relevant Zapier webhook URL 2 | const ZAPIER_WEBHOOK_URL = ai.getConfig("zapier_webhook_url"); 3 | 4 | // Read the variable provided to get the input data 5 | const input = ai.getConfig("input"); 6 | 7 | if (!input) { 8 | ai.crmLog("No input to send to Zapier."); 9 | return; 10 | } 11 | 12 | // Make a POST request to the Zapier webhook URL 13 | const request = await fetch(ZAPIER_WEBHOOK_URL, { 14 | method: "POST", 15 | headers: { 16 | "Content-Type": "application/json", 17 | }, 18 | body: JSON.stringify({ data: input }), 19 | }); 20 | 21 | // Check if the request was successful 22 | if (request.ok) { 23 | ai.crmLog(`Data sent to Zapier: ${input}`); 24 | } else { 25 | console.error("Error during POST request to Zapier."); 26 | ai.crmLog(`Error during POST request to Zapier. Data: ${input}`); 27 | } 28 | -------------------------------------------------------------------------------- /templates/zapier/simulator.js: -------------------------------------------------------------------------------- 1 | environment = { 2 | config: { 3 | zapier_webhook_url: "", 4 | input: "{{input}}", 5 | }, 6 | vars: {}, 7 | } 8 | --------------------------------------------------------------------------------