{ "kind": "open-flow-workflow", "version": 1, "name": "Email Trending Show HN Stories", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_6a84493d0baffba6": { "name": "Every day at 13:00 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 13 * * *", "timezone": "UTC" } ] }, "n_027244151273b1dd": { "inputs": { "url": { "kind": "value", "value": "https://news.ycombinator.com/" }, "extractRules": { "kind": "value", "value": { "stories": { "selector": "tr.athing", "type": "list", "output": { "rank": ".rank", "title": ".titleline > a", "url": { "selector": ".titleline > a", "output": "@href" } } } } }, "renderJs": { "kind": "value", "value": false }, "transparentStatusCode": { "kind": "value", "value": true } }, "name": "Read Hacker News front page", "kind": "task", "task": { "inputs": [ { "description": "The public URL to extract data from with ScrapingBee.", "jsonSchema": { "type": "string", "format": "uri", "description": "The public URL to extract data from with ScrapingBee." }, "nullable": false, "handle": "url" }, { "description": "The extraction rules object serialized into the extract_rules query parameter.", "jsonSchema": { "type": "object", "additionalProperties": { "description": "A JSON-compatible value used in ScrapingBee extraction rules or responses." }, "description": "The extraction rules object serialized into the extract_rules query parameter." }, "nullable": false, "handle": "extractRules" }, { "description": "Whether ScrapingBee should render JavaScript before returning the page.", "jsonSchema": { "type": "boolean", "description": "Whether ScrapingBee should render JavaScript before returning the page." }, "nullable": true, "handle": "renderJs", "value": null }, { "description": "How many milliseconds ScrapingBee should wait before returning the page.", "jsonSchema": { "type": "integer", "minimum": 0, "description": "How many milliseconds ScrapingBee should wait before returning the page." }, "nullable": true, "handle": "waitMs", "value": null }, { "description": "The CSS selector ScrapingBee should wait for before returning the page.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The CSS selector ScrapingBee should wait for before returning the page." }, "nullable": true, "handle": "waitFor", "value": null }, { "description": "The device preset used for the request.", "jsonSchema": { "type": "string", "enum": [ "desktop", "mobile" ], "description": "The device preset used for the request." }, "nullable": true, "handle": "device", "value": null }, { "description": "Whether ScrapingBee should block ads on the page.", "jsonSchema": { "type": "boolean", "description": "Whether ScrapingBee should block ads on the page." }, "nullable": true, "handle": "blockAds", "value": null }, { "description": "Whether ScrapingBee should block images and CSS resources.", "jsonSchema": { "type": "boolean", "description": "Whether ScrapingBee should block images and CSS resources." }, "nullable": true, "handle": "blockResources", "value": null }, { "description": "The two-letter country code used for request geolocation.", "jsonSchema": { "type": "string", "minLength": 2, "maxLength": 2, "description": "The two-letter country code used for request geolocation." }, "nullable": true, "handle": "countryCode", "value": null }, { "description": "Whether ScrapingBee should use premium proxy routing.", "jsonSchema": { "type": "boolean", "description": "Whether ScrapingBee should use premium proxy routing." }, "nullable": true, "handle": "premiumProxy", "value": null }, { "description": "Whether ScrapingBee should use stealth proxy routing.", "jsonSchema": { "type": "boolean", "description": "Whether ScrapingBee should use stealth proxy routing." }, "nullable": true, "handle": "stealthProxy", "value": null }, { "description": "Whether ScrapingBee should return the target page status code transparently.", "jsonSchema": { "type": "boolean", "description": "Whether ScrapingBee should return the target page status code transparently." }, "nullable": true, "handle": "transparentStatusCode", "value": null }, { "description": "How many times ScrapingBee should retry the request on failure.", "jsonSchema": { "type": "integer", "exclusiveMinimum": 0, "description": "How many times ScrapingBee should retry the request on failure." }, "nullable": true, "handle": "retry", "value": null } ], "outputs": [ { "description": "The structured data object returned by ScrapingBee extract_rules.", "jsonSchema": { "type": "object", "additionalProperties": { "description": "A JSON-compatible value used in ScrapingBee extraction rules or responses." }, "description": "The structured data object returned by ScrapingBee extract_rules." }, "nullable": false, "handle": "data" }, { "description": "The HTTP status code returned by ScrapingBee.", "jsonSchema": { "type": "integer", "description": "The HTTP status code returned by ScrapingBee." }, "nullable": false, "handle": "statusCode" }, { "description": "The final resolved URL reported by ScrapingBee.", "jsonSchema": { "type": "string", "description": "The final resolved URL reported by ScrapingBee." }, "nullable": true, "handle": "resolvedUrl" }, { "description": "The request credit cost reported by ScrapingBee.", "jsonSchema": { "type": "number", "description": "The request credit cost reported by ScrapingBee." }, "nullable": true, "handle": "creditCost" } ], "name": "Read Hacker News front page", "executor": { "kind": "connector", "action": "scrapingbee.extract_data" } } }, "n_f854ad462fe3982a": { "inputs": { "data": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_027244151273b1dd", "output": "data" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_027244151273b1dd", "output": "statusCode" } ] } }, "name": "Select and format Show HN stories", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": { "description": "A JSON-compatible value used in ScrapingBee extraction rules or responses." }, "description": "The structured data object returned by ScrapingBee extract_rules." }, "nullable": false, "handle": "data" }, { "jsonSchema": { "type": "integer", "description": "The HTTP status code returned by ScrapingBee." }, "nullable": false, "handle": "statusCode" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "hasStories" }, { "jsonSchema": { "type": "string", "description": "The plain text email body." }, "nullable": true, "handle": "text" } ], "name": "Select and format Show HN stories", "moduleId": "n_ea45e95d6caa1ff9", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_bb3108aa8a033db5": { "inputs": {}, "name": "Any Show HN stories?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_f854ad462fe3982a", "output": "hasStories" } }, "operator": "isTrue" } ] } ] } ] }, "n_b51d64433025fdd0": { "inputs": { "to": { "kind": "value", "value": [ "you@example.com" ] }, "subject": { "kind": "value", "value": "Trending Show HN" }, "text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_f854ad462fe3982a", "output": "text" } ] } }, "name": "Email the digest", "kind": "task", "task": { "inputs": [ { "description": "The primary recipient email addresses.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "minItems": 1, "description": "The primary recipient email addresses." }, "nullable": false, "handle": "to" }, { "description": "The carbon-copy recipient email addresses.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "minItems": 1, "description": "The carbon-copy recipient email addresses." }, "nullable": true, "handle": "cc", "value": null }, { "description": "The blind-carbon-copy recipient email addresses.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "minItems": 1, "description": "The blind-carbon-copy recipient email addresses." }, "nullable": true, "handle": "bcc", "value": null }, { "description": "An email address.", "jsonSchema": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "nullable": true, "handle": "replyTo", "value": null }, { "description": "The email subject.", "jsonSchema": { "type": "string", "description": "The email subject." }, "nullable": false, "handle": "subject" }, { "description": "The plain text email body.", "jsonSchema": { "type": "string", "description": "The plain text email body." }, "nullable": true, "handle": "text", "value": null }, { "description": "The HTML email body.", "jsonSchema": { "type": "string", "description": "The HTML email body." }, "nullable": true, "handle": "html", "value": null }, { "description": "Attachments to fetch and include in the outgoing email.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "filename": { "type": "string", "minLength": 1, "description": "The filename to use for the outgoing attachment." }, "contentType": { "type": "string", "minLength": 1, "description": "The MIME content type of the attachment." }, "contentUrl": { "type": "string", "format": "uri", "description": "The public HTTP or HTTPS URL to fetch attachment content from." } }, "required": [ "filename", "contentUrl" ], "additionalProperties": false, "description": "An attachment to fetch and include in an outgoing email." }, "minItems": 1, "maxItems": 10, "description": "Attachments to fetch and include in the outgoing email." }, "nullable": true, "handle": "attachments", "value": null } ], "outputs": [ { "description": "The SMTP Message-ID returned by the mail client.", "jsonSchema": { "type": [ "string", "null" ], "description": "The SMTP Message-ID returned by the mail client." }, "nullable": true, "handle": "messageId" }, { "description": "Recipient addresses accepted by the SMTP server.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "description": "Recipient addresses accepted by the SMTP server." }, "nullable": false, "handle": "accepted" }, { "description": "Recipient addresses rejected by the SMTP server.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "description": "Recipient addresses rejected by the SMTP server." }, "nullable": false, "handle": "rejected" }, { "description": "The SMTP response text returned by the server.", "jsonSchema": { "type": "string", "description": "The SMTP response text returned by the server." }, "nullable": false, "handle": "response" } ], "name": "Email the digest", "executor": { "kind": "connector", "action": "generic_imap.send_email" } } }, "n_d5a3d71349dad4f5": { "inputs": { "accepted": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b51d64433025fdd0", "output": "accepted" } ] }, "rejected": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b51d64433025fdd0", "output": "rejected" } ] }, "response": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b51d64433025fdd0", "output": "response" } ] } }, "name": "Confirm SMTP acceptance", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "description": "Recipient addresses accepted by the SMTP server." }, "nullable": false, "handle": "accepted" }, { "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "description": "Recipient addresses rejected by the SMTP server." }, "nullable": false, "handle": "rejected" }, { "jsonSchema": { "type": "string", "description": "The SMTP response text returned by the server." }, "nullable": false, "handle": "response" } ], "outputs": [], "name": "Confirm SMTP acceptance", "moduleId": "n_7ed8fd0c0eba3e94", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } } }, "edges": [ { "source": "n_b51d64433025fdd0", "target": "n_d5a3d71349dad4f5" }, { "source": "n_6a84493d0baffba6", "target": "n_027244151273b1dd" }, { "source": "n_027244151273b1dd", "target": "n_f854ad462fe3982a" }, { "source": "n_f854ad462fe3982a", "target": "n_bb3108aa8a033db5" }, { "source": "n_bb3108aa8a033db5", "target": "n_b51d64433025fdd0", "sourceHandle": "yes" } ] } }, "modules": { "n_ea45e95d6caa1ff9": { "name": "Select and format Show HN stories", "imports": [], "source": "export default function main({data,statusCode}) {\n if(statusCode<200||statusCode>=300||!Array.isArray(data?.stories)||data.stories.length===0)throw Error('Hacker News page did not return a story list');\n const chosen=[];\n for(const story of data.stories){\n if(typeof story?.title!=='string'||typeof story.rank!=='string'||typeof story.url!=='string'||!story.url)throw Error('Incomplete story in page');\n if(!story.title.includes('Show HN:'))continue;\n const url=story.url.startsWith('item?id=')?'https://news.ycombinator.com/'+story.url:story.url;\n if(!/^https?:\\/\\//i.test(url))throw Error('Unsupported story URL');\n chosen.push(`${story.rank} ${story.title}\\n${url}`);\n }\n return {hasStories:chosen.length>0,text:'Currently trending \"Show HN\":\\n\\n'+chosen.join('\\n\\n')+'\\n'};\n}\n" }, "n_7ed8fd0c0eba3e94": { "name": "Confirm SMTP acceptance", "imports": [], "source": "export default function confirmEmail({accepted,rejected,response}){if(!Array.isArray(accepted)||!accepted.length||!Array.isArray(rejected)||rejected.length||typeof response!=='string')throw Error('Email was not accepted by SMTP');return {};}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }