{ "kind": "open-flow-workflow", "version": 1, "name": "Save Sheet Website Screenshots to Drive", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_0ecde2fe17afc305": { "name": "Check new spreadsheet rows", "kind": "poll", "config": { "spreadsheetId": { "kind": "value", "value": "YOUR_SPREADSHEET_ID" }, "sheet": { "kind": "value", "value": "Sheet1" }, "columnRange": { "kind": "value", "value": "A:ZZZ" }, "firstDataRow": { "kind": "value", "value": 2 }, "headerRow": { "kind": "value", "value": 1 }, "dateTimeRender": { "kind": "value", "value": "FORMATTED_STRING" }, "valueRender": { "kind": "value", "value": "UNFORMATTED_VALUE" }, "maxRowsPerPoll": { "kind": "value", "value": 100 } }, "definition": { "configInputs": [ { "jsonSchema": { "type": "string", "pattern": "^[A-Za-z]{1,3}(?::[A-Za-z]{1,3})?$" }, "nullable": false, "handle": "columnRange", "value": "A:ZZZ" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "sheet" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "spreadsheetId" }, { "collapsed": true, "group": "Advanced settings" }, { "jsonSchema": { "type": "string", "enum": [ "FORMATTED_STRING", "SERIAL_NUMBER" ] }, "nullable": false, "handle": "dateTimeRender", "value": "FORMATTED_STRING" }, { "jsonSchema": { "type": "integer", "minimum": 1 }, "nullable": false, "handle": "firstDataRow", "value": 2 }, { "jsonSchema": { "type": "integer", "minimum": 1 }, "nullable": false, "handle": "headerRow", "value": 1 }, { "jsonSchema": { "type": "integer", "minimum": 1, "maximum": 500 }, "nullable": false, "handle": "maxRowsPerPoll", "value": 100 }, { "jsonSchema": { "type": "string", "enum": [ "UNFORMATTED_VALUE", "FORMATTED_VALUE", "FORMULA" ] }, "nullable": false, "handle": "valueRender", "value": "UNFORMATTED_VALUE" } ], "definitionVersion": 2, "description": "Polls a sheet and triggers once for every new row appended below the last row already seen.", "displayName": "New Row Added", "key": "googlesheets.on_row_added", "name": "on_row_added", "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "row": { "type": "object" }, "rowNumber": { "type": "integer" }, "sheetId": { "type": "integer" }, "sheetTitle": { "type": "string" }, "spreadsheetId": { "type": "string" }, "values": { "type": "array", "items": {} } }, "additionalProperties": false, "required": [ "spreadsheetId", "sheetId", "sheetTitle", "rowNumber", "row", "values" ] } }, "nullable": false, "handle": "events" } ], "provider": "googlesheets", "type": "poll" }, "pollTimes": [ { "type": "every", "unit": "minute", "value": 1 } ] }, "n_987cbeba3b7858fa": { "inputs": { "events": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0ecde2fe17afc305", "output": "events" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_987cbeba3b7858fa", "output": "state" } ] }, "folderId": { "kind": "value", "value": "YOUR_DRIVE_FOLDER_ID" } }, "name": "Choose next website", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "row": { "type": "object" }, "rowNumber": { "type": "integer" }, "sheetId": { "type": "integer" }, "sheetTitle": { "type": "string" }, "spreadsheetId": { "type": "string" }, "values": { "type": "array", "items": {} } }, "additionalProperties": false, "required": [ "spreadsheetId", "sheetId", "sheetTitle", "rowNumber", "row", "values" ] } }, "nullable": false, "handle": "events" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "folderId" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string", "format": "uri", "description": "The complete HTTP or HTTPS webpage URL to capture." }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The name of the file." }, "nullable": false, "handle": "name" }, { "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "description": "Schema for nonEmptyString." }, "minItems": 1, "description": "A list of parent folder IDs to place the file in." }, "nullable": false, "handle": "parents" } ], "name": "Choose next website", "moduleId": "n_7029425a585e8ac4", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_b70252b91cf53622": { "inputs": {}, "name": "More rows?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_987cbeba3b7858fa", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_60c77fe88b409d76": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_987cbeba3b7858fa", "output": "url" } ] }, "format": { "kind": "value", "value": "png" }, "fullPage": { "kind": "value", "value": true }, "viewportWidth": { "kind": "value", "value": 1024 }, "deviceScaleFactor": { "kind": "value", "value": 1 } }, "name": "Capture the website", "kind": "task", "task": { "inputs": [ { "description": "The complete HTTP or HTTPS webpage URL to capture.", "jsonSchema": { "type": "string", "format": "uri", "description": "The complete HTTP or HTTPS webpage URL to capture." }, "nullable": false, "handle": "url" }, { "description": "The image format for the hosted screenshot.", "jsonSchema": { "type": "string", "enum": [ "jpg", "jpeg", "png", "gif", "webp" ], "description": "The image format for the hosted screenshot." }, "nullable": true, "handle": "format", "value": null }, { "description": "The image quality to use when the format is jpg.", "jsonSchema": { "type": "integer", "description": "The image quality to use when the format is jpg." }, "nullable": true, "handle": "quality", "value": null }, { "description": "Whether to capture the full scrollable page.", "jsonSchema": { "type": "boolean", "description": "Whether to capture the full scrollable page." }, "nullable": true, "handle": "fullPage", "value": null }, { "description": "The browser viewport width in pixels.", "jsonSchema": { "type": "integer", "description": "The browser viewport width in pixels." }, "nullable": true, "handle": "viewportWidth", "value": null }, { "description": "The browser viewport height in pixels.", "jsonSchema": { "type": "integer", "description": "The browser viewport height in pixels." }, "nullable": true, "handle": "viewportHeight", "value": null }, { "description": "The browser device pixel ratio.", "jsonSchema": { "type": "number", "minimum": 0.5, "maximum": 3, "description": "The browser device pixel ratio." }, "nullable": true, "handle": "deviceScaleFactor", "value": null }, { "description": "The lowercase ISO 3166-1 alpha-2 country code for the capture exit IP.", "jsonSchema": { "type": "string", "enum": [ "ae", "al", "am", "ar", "at", "au", "az", "ba", "bd", "be", "bg", "bh", "bn", "bo", "br", "bw", "by", "ca", "cg", "ch", "cl", "cm", "cn", "co", "cr", "cy", "cz", "de", "dk", "do", "dz", "ec", "ee", "eg", "es", "fi", "fr", "gb", "ge", "gh", "gr", "gt", "hk", "hr", "hu", "id", "ie", "il", "im", "in", "iq", "is", "it", "jm", "jo", "jp", "ke", "kg", "kh", "kr", "kw", "kz", "la", "li", "lk", "lt", "lu", "lv", "ma", "md", "mk", "mm", "mo", "mw", "mx", "my", "mz", "ng", "nl", "no", "nz", "om", "pa", "pe", "ph", "pk", "pl", "pr", "pt", "py", "qa", "ro", "rs", "ru", "sa", "sc", "se", "sg", "si", "sk", "sl", "th", "tj", "tm", "tn", "tr", "tw", "tz", "ua", "us", "uy", "uz", "ve", "vn", "za", "zm", "zw" ], "description": "The lowercase ISO 3166-1 alpha-2 country code for the capture exit IP." }, "nullable": true, "handle": "ipCountryCode", "value": null }, { "description": "The seconds to wait after page load before capture.", "jsonSchema": { "type": "integer", "minimum": 0, "maximum": 30, "description": "The seconds to wait after page load before capture." }, "nullable": true, "handle": "delay", "value": null }, { "description": "The navigation timeout in seconds.", "jsonSchema": { "type": "integer", "minimum": 5, "maximum": 60, "description": "The navigation timeout in seconds." }, "nullable": true, "handle": "timeout", "value": null }, { "description": "The browser lifecycle event to wait for before capture.", "jsonSchema": { "type": "string", "enum": [ "load", "domcontentloaded", "networkidle0", "networkidle2" ], "description": "The browser lifecycle event to wait for before capture." }, "nullable": true, "handle": "waitUntil", "value": null }, { "description": "Whether to attempt to hide cookie-consent banners.", "jsonSchema": { "type": "boolean", "description": "Whether to attempt to hide cookie-consent banners." }, "nullable": true, "handle": "blockCookieBanners", "value": null }, { "description": "Whether to attempt to block advertisements.", "jsonSchema": { "type": "boolean", "description": "Whether to attempt to block advertisements." }, "nullable": true, "handle": "blockAds", "value": null }, { "description": "Whether to attempt to hide chat widgets.", "jsonSchema": { "type": "boolean", "description": "Whether to attempt to hide chat widgets." }, "nullable": true, "handle": "blockChats", "value": null }, { "description": "The CSS selectors of elements to hide before capture.", "jsonSchema": { "type": "array", "items": { "type": "string", "maxLength": 255, "description": "A CSS selector to hide." }, "description": "The CSS selectors of elements to hide before capture." }, "nullable": true, "handle": "hideSelectors", "value": null }, { "description": "The custom CSS to inject before capture.", "jsonSchema": { "type": "string", "maxLength": 10000, "description": "The custom CSS to inject before capture." }, "nullable": true, "handle": "styles", "value": null }, { "description": "The output filename without an extension.", "jsonSchema": { "type": "string", "maxLength": 255, "description": "The output filename without an extension." }, "nullable": true, "handle": "attachmentName", "value": null } ], "outputs": [ { "description": "The public URL of the generated screenshot.", "jsonSchema": { "type": "string", "format": "uri", "description": "The public URL of the generated screenshot." }, "nullable": false, "handle": "url" } ], "name": "Capture the website", "executor": { "kind": "connector", "action": "screenshotbase.take_screenshot" } } }, "n_ca1013b2287dc243": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_60c77fe88b409d76", "output": "url" } ] } }, "name": "Read and check PNG bytes", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "format": "uri", "description": "The public URL of the generated screenshot." }, "nullable": false, "handle": "url" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1, "description": "Connector media upload content encoded as a Base64 string." }, "nullable": false, "handle": "contentBase64" } ], "name": "Read and check PNG bytes", "moduleId": "n_2c8b1436e0f3de4d", "capabilities": [ { "kind": "http", "hosts": [ "cache.screenshotbase.com", "cdn.screenshotbase.com" ] } ] } }, "n_3b5b48dfc5230b47": { "inputs": { "name": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_987cbeba3b7858fa", "output": "name" } ] }, "parents": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_987cbeba3b7858fa", "output": "parents" } ] }, "mimeType": { "kind": "value", "value": "image/png" }, "contentBase64": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ca1013b2287dc243", "output": "contentBase64" } ] } }, "name": "Save screenshot in Drive", "kind": "task", "task": { "inputs": [ { "description": "The name of the file.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The name of the file." }, "nullable": true, "handle": "name", "value": null }, { "description": "The MIME type of the file.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The MIME type of the file." }, "nullable": true, "handle": "mimeType", "value": null }, { "description": "A short description of the file.", "jsonSchema": { "type": "string", "minLength": 1, "description": "A short description of the file." }, "nullable": true, "handle": "description", "value": null }, { "description": "A list of parent folder IDs to place the file in.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "description": "Schema for nonEmptyString." }, "minItems": 1, "description": "A list of parent folder IDs to place the file in." }, "nullable": true, "handle": "parents", "value": null }, { "description": "Whether the user has starred the file.", "jsonSchema": { "type": "boolean", "description": "Whether the user has starred the file." }, "nullable": true, "handle": "starred", "value": null }, { "description": "A collection of arbitrary key-value pairs which are private to the requesting app.", "jsonSchema": { "type": "object", "additionalProperties": { "type": "string", "description": "A record value." }, "description": "A collection of arbitrary key-value pairs which are private to the requesting app." }, "nullable": true, "handle": "appProperties", "value": null }, { "description": "A collection of arbitrary key-value pairs which are visible to all apps.", "jsonSchema": { "type": "object", "additionalProperties": { "type": "string", "description": "A record value." }, "description": "A collection of arbitrary key-value pairs which are visible to all apps." }, "nullable": true, "handle": "properties", "value": null }, { "description": "Connector media upload content encoded as a Base64 string.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Connector media upload content encoded as a Base64 string." }, "nullable": true, "handle": "contentBase64", "value": null }, { "description": "Connector text media upload content.", "jsonSchema": { "type": "string", "description": "Connector text media upload content." }, "nullable": true, "handle": "text", "value": null } ], "outputs": [ { "description": "The unique identifier of the file.", "jsonSchema": { "type": "string", "description": "The unique identifier of the file." }, "nullable": false, "handle": "id" }, { "description": "The name of the file.", "jsonSchema": { "type": "string", "description": "The name of the file." }, "nullable": false, "handle": "name" }, { "description": "The MIME type of the file.", "jsonSchema": { "type": "string", "description": "The MIME type of the file." }, "nullable": false, "handle": "mimeType" }, { "description": "A link for opening the file in a relevant Google editor or viewer in a browser.", "jsonSchema": { "type": [ "string", "null" ], "description": "A link for opening the file in a relevant Google editor or viewer in a browser." }, "nullable": true, "handle": "webViewLink" }, { "description": "The time at which the file was created (RFC 3339 date-time).", "jsonSchema": { "type": [ "string", "null" ], "description": "The time at which the file was created (RFC 3339 date-time)." }, "nullable": true, "handle": "createdTime" }, { "description": "The last time the file was modified by anyone (RFC 3339 date-time).", "jsonSchema": { "type": [ "string", "null" ], "description": "The last time the file was modified by anyone (RFC 3339 date-time)." }, "nullable": true, "handle": "modifiedTime" }, { "description": "The size of the file's content in bytes.", "jsonSchema": { "type": [ "integer", "null" ], "description": "The size of the file's content in bytes." }, "nullable": true, "handle": "sizeBytes" }, { "description": "The ID of the shared drive the file belongs to.", "jsonSchema": { "type": [ "string", "null" ], "description": "The ID of the shared drive the file belongs to." }, "nullable": true, "handle": "driveId" }, { "description": "The IDs of the parent folders containing the file.", "jsonSchema": { "type": "array", "items": { "type": "string", "description": "An array item." }, "description": "The IDs of the parent folders containing the file." }, "nullable": true, "handle": "parents" }, { "description": "The owners of the file.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "displayName": { "type": [ "string", "null" ], "description": "The display name of the owner." }, "emailAddress": { "type": [ "string", "null" ], "description": "The email address of the owner." }, "permissionId": { "type": [ "string", "null" ], "description": "The permission ID of the owner." }, "photoLink": { "type": [ "string", "null" ], "description": "A link to the owner's profile photo." } }, "required": [ "displayName", "emailAddress", "permissionId", "photoLink" ], "additionalProperties": false, "description": "Schema for driveOwnerSchema." }, "description": "The owners of the file." }, "nullable": true, "handle": "owners" }, { "description": "Whether the file has been shared.", "jsonSchema": { "type": "boolean", "description": "Whether the file has been shared." }, "nullable": true, "handle": "shared" }, { "description": "Whether the user has starred the file.", "jsonSchema": { "type": "boolean", "description": "Whether the user has starred the file." }, "nullable": true, "handle": "starred" }, { "description": "Whether the file has been trashed.", "jsonSchema": { "type": "boolean", "description": "Whether the file has been trashed." }, "nullable": true, "handle": "trashed" }, { "description": "Whether the authenticated user owns the file.", "jsonSchema": { "type": "boolean", "description": "Whether the authenticated user owns the file." }, "nullable": true, "handle": "ownedByMe" }, { "description": "The projected Drive capabilities for the authenticated user.", "jsonSchema": { "type": "object", "properties": { "canShare": { "type": "boolean", "description": "Whether the authenticated user may modify sharing settings." } }, "required": [ "canShare" ], "additionalProperties": false, "description": "The projected Drive capabilities for the authenticated user." }, "nullable": true, "handle": "capabilities" } ], "name": "Save screenshot in Drive", "executor": { "kind": "connector", "action": "googledrive.files.create" } } }, "n_62fc67d749c08616": { "inputs": { "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3b5b48dfc5230b47", "output": "id" } ] } }, "name": "Confirm Drive file", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "description": "The unique identifier of the file." }, "nullable": false, "handle": "id" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "ok" } ], "name": "Confirm Drive file", "moduleId": "n_8ca626c10b0ca521", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } } }, "edges": [ { "source": "n_0ecde2fe17afc305", "target": "n_987cbeba3b7858fa" }, { "source": "n_987cbeba3b7858fa", "target": "n_b70252b91cf53622" }, { "source": "n_b70252b91cf53622", "target": "n_60c77fe88b409d76", "sourceHandle": "yes" }, { "source": "n_60c77fe88b409d76", "target": "n_ca1013b2287dc243" }, { "source": "n_ca1013b2287dc243", "target": "n_3b5b48dfc5230b47" }, { "source": "n_3b5b48dfc5230b47", "target": "n_62fc67d749c08616" }, { "source": "n_62fc67d749c08616", "target": "n_987cbeba3b7858fa" } ] } }, "modules": { "n_7029425a585e8ac4": { "name": "Choose next website", "imports": [], "source": "export default function next({events,previous,folderId}){if(!Array.isArray(events))throw Error('Invalid sheet events');if(typeof folderId!=='string'||!folderId.trim()||folderId.startsWith('YOUR_'))throw Error('Configure the Drive folder ID');const index=previous==null?0:previous.index+1;if(!Number.isInteger(index)||index<0||index>events.length)throw Error('Invalid row progress');if(index===events.length)return{state:{index},more:false,url:'https://example.com',name:'-',parents:[folderId]};const row=events[index]?.row;if(typeof row?.Url!=='string'||!row.Url.trim()||typeof row?.Title!=='string'||!row.Title.trim())throw Error('Each row needs Url and Title');return{state:{index},more:true,url:row.Url.trim(),name:row.Title.trim()+'.png',parents:[folderId]}}\n" }, "n_2c8b1436e0f3de4d": { "name": "Read and check PNG bytes", "imports": [], "source": " function validUrl(value) {\n if (typeof value !== 'string' || /[\\s\\x00-\\x1f\\x7f\\\\]/.test(value) || /%(?![0-9a-f]{2})/i.test(value)) return false;\n const match = /^https?:\\/\\/([^/?#]+)(?:[/?#].*)?$/i.exec(value);\n if (!match || match[1].includes('@')) return false;\n const authority = match[1];\n let host, port;\n if (authority.startsWith('[')) {\n const parts = /^\\[([0-9a-f:]+)\\](?::(\\d+))?$/i.exec(authority);\n if (!parts) return false;\n host = parts[1]; port = parts[2];\n const halves = host.split('::');\n if (halves.length > 2) return false;\n const groups = halves.flatMap(part => part ? part.split(':') : []);\n if (groups.some(group => !/^[0-9a-f]{1,4}$/i.test(group))) return false;\n if (halves.length === 1 ? groups.length !== 8 : groups.length >= 8) return false;\n } else {\n const parts = /^([^:]+)(?::(\\d+))?$/.exec(authority);\n if (!parts) return false;\n host = parts[1]; port = parts[2];\n if (host.length > 253) return false;\n const labels = host.replace(/\\.$/, '').split('.');\n if (!labels.every(label => label.length > 0 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label))) return false;\n if (labels.every(label => /^\\d+$/.test(label)) && (labels.length !== 4 || labels.some(label => Number(label) > 255))) return false;\n }\n return port === undefined || (port.length <= 5 && Number(port) <= 65535);\n }\n\nfunction checkPng(bytes){if(!bytes||bytes.length<24)throw Error('Screenshot PNG header is incomplete');const sig=[137,80,78,71,13,10,26,10];if(sig.some((x,i)=>bytes[i]!==x)||bytes[8]!==0||bytes[9]!==0||bytes[10]!==0||bytes[11]!==13||String.fromCharCode(...bytes.slice(12,16))!=='IHDR')throw Error('Screenshot is not a PNG with an IHDR header');const u=i=>bytes[i]*16777216+bytes[i+1]*65536+bytes[i+2]*256+bytes[i+3],width=u(16),height=u(20);if(width!==1024||height<1||height>=20000)throw Error('Screenshot dimensions may indicate truncation or scaling');return true;}\n\nimport{Buffer}from'node:buffer';\nexport default async function content({url},context){if(!validUrl(url))throw Error('Invalid screenshot URL');const u=new URL(url);if(u.protocol!=='https:'||u.port!==''||!['cache.screenshotbase.com','cdn.screenshotbase.com'].includes(u.hostname))throw Error('Unexpected screenshot host');const r=await context.fetch(url,{method:'GET'});if(!r.ok)throw Error('Screenshot download failed');const bytes=Buffer.from(await r.arrayBuffer());checkPng(bytes);if(bytes.length<36||bytes.subarray(bytes.length-8,bytes.length-4).toString('ascii')!=='IEND')throw Error('Screenshot PNG is incomplete');const length=r.headers.get('content-length');if(length!=null&&Number(length)!==bytes.length)throw Error('Screenshot download length mismatch');return{contentBase64:bytes.toString('base64')}}\n" }, "n_8ca626c10b0ca521": { "name": "Confirm Drive file", "imports": [], "source": "export default function attachmentReceipt({id}){if(typeof id!=='string'||!id)throw Error('Drive did not confirm file creation; inspect Drive before retrying');return {ok:true};}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }