{ "kind": "open-flow-workflow", "version": 1, "name": "Send Daily German Vocabulary", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_1dceb4dfd8f6648a": { "name": "Every day at 08:00 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 8 * * *", "timezone": "UTC" } ] }, "n_ba9d1ba0e6020aab": { "inputs": { "baseId": { "kind": "value", "value": "YOUR_AIRTABLE_BASE_ID" }, "table": { "kind": "value", "value": "YOUR_AIRTABLE_TABLE" }, "from": { "kind": "value", "value": "YOUR_SMS_SENDER" }, "to": { "kind": "value", "value": "YOUR_SMS_DESTINATION" } }, "name": "Configure the destinations", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "baseId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "table" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "from" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "to" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "baseId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "table" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "from" }, { "jsonSchema": { "type": "string", "minLength": 7, "maxLength": 15, "pattern": "^[0-9]{7,15}$", "description": "The destination number in E.164 format without a leading plus sign." }, "nullable": false, "handle": "to" } ], "name": "Configure the destinations", "moduleId": "n_3e717e0b6c623ba2", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_b474bfff4bd339bc": { "inputs": {}, "name": "Get top story IDs", "kind": "task", "task": { "inputs": [ { "description": "The pretty-print flag for Firebase responses.", "jsonSchema": { "const": "pretty", "description": "The pretty-print flag for Firebase responses." }, "nullable": true, "handle": "print", "value": null } ], "outputs": [ { "description": "The list of story IDs.", "jsonSchema": { "type": "array", "items": { "type": "integer", "description": "A story ID." }, "description": "The list of story IDs." }, "nullable": false, "handle": "story_ids" }, { "description": "The total number of story IDs returned.", "jsonSchema": { "type": "integer", "description": "The total number of story IDs returned." }, "nullable": false, "handle": "count" } ], "name": "Get top story IDs", "executor": { "kind": "connector", "action": "hackernews.get_top_stories" } } }, "n_5db5625f09ea6476": { "inputs": { "ids": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b474bfff4bd339bc", "output": "story_ids" } ] } }, "name": "Extract unique title words", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "integer", "description": "A story ID." }, "description": "The list of story IDs." }, "nullable": false, "handle": "ids" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string" } }, "nullable": false, "handle": "words" } ], "name": "Extract unique title words", "moduleId": "n_12eea03a105fcbf7", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "hackernews.get_item" } ] } ] } }, "n_ac21ebf399873404": { "inputs": { "words": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5db5625f09ea6476", "output": "words" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ac21ebf399873404", "output": "state" } ] } }, "name": "Translate each word", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string" } }, "nullable": false, "handle": "words" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" } ], "name": "Translate each word", "moduleId": "n_fb73890b694dcf7c", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "lingvanex_translation_api.translate_text" } ] } ] } }, "n_9416758ad97c0f8c": { "inputs": {}, "name": "More words to translate?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_ac21ebf399873404", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_22c9048f8334fd5a": { "inputs": { "state": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ac21ebf399873404", "output": "state" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_22c9048f8334fd5a", "output": "state" } ] } }, "name": "Prepare Airtable records", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Field values for the new Airtable record." } }, "required": [ "fields" ], "additionalProperties": false, "description": "Record payload used when creating Airtable records." }, "minItems": 1, "maxItems": 10, "description": "Records to create in the Airtable table." }, "nullable": false, "handle": "records" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "expected" } ], "name": "Prepare Airtable records", "moduleId": "n_f08d636af4746f73", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_5ac6a97631af4777": { "inputs": {}, "name": "More records to save?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_22c9048f8334fd5a", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_86cdd5c5403f8e1f": { "inputs": { "baseId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ba9d1ba0e6020aab", "output": "baseId" } ] }, "tableIdOrName": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ba9d1ba0e6020aab", "output": "table" } ] }, "records": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_22c9048f8334fd5a", "output": "records" } ] } }, "name": "Save vocabulary to Airtable", "kind": "task", "task": { "inputs": [ { "description": "Base ID in the format appXXXXXXXXXXXXXX.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Base ID in the format appXXXXXXXXXXXXXX." }, "nullable": false, "handle": "baseId" }, { "description": "Table ID or table name accepted by the Airtable path parameter.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Table ID or table name accepted by the Airtable path parameter." }, "nullable": false, "handle": "tableIdOrName" }, { "description": "Records to create in the Airtable table.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Field values for the new Airtable record." } }, "required": [ "fields" ], "additionalProperties": false, "description": "Record payload used when creating Airtable records." }, "minItems": 1, "maxItems": 10, "description": "Records to create in the Airtable table." }, "nullable": false, "handle": "records" }, { "description": "Whether Airtable should coerce incoming values to compatible field types.", "jsonSchema": { "type": "boolean", "description": "Whether Airtable should coerce incoming values to compatible field types." }, "nullable": true, "handle": "typecast", "value": null }, { "description": "Whether Airtable should return field IDs instead of field names in record objects.", "jsonSchema": { "type": "boolean", "description": "Whether Airtable should return field IDs instead of field names in record objects." }, "nullable": true, "handle": "returnFieldsByFieldId", "value": null } ], "outputs": [ { "description": "Records created by Airtable.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Record ID." }, "createdTime": { "type": "string", "description": "Record creation timestamp returned by Airtable." }, "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Record fields keyed by field name or field ID." }, "commentCount": { "type": "integer", "description": "Comment count returned by Airtable when enabled on the endpoint." } }, "required": [ "id", "fields" ], "additionalProperties": true, "description": "Airtable record." }, "description": "Records created by Airtable." }, "nullable": false, "handle": "records" } ], "name": "Save vocabulary to Airtable", "executor": { "kind": "connector", "action": "airtable.create_records" } } }, "n_349ef10bb34c2d26": { "inputs": { "records": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_86cdd5c5403f8e1f", "output": "records" } ] }, "expected": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_22c9048f8334fd5a", "output": "expected" } ] } }, "name": "Confirm saved records", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Record ID." }, "createdTime": { "type": "string", "description": "Record creation timestamp returned by Airtable." }, "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Record fields keyed by field name or field ID." }, "commentCount": { "type": "integer", "description": "Comment count returned by Airtable when enabled on the endpoint." } }, "required": [ "id", "fields" ], "additionalProperties": true, "description": "Airtable record." }, "description": "Records created by Airtable." }, "nullable": false, "handle": "records" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "expected" } ], "outputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "confirmed" } ], "name": "Confirm saved records", "moduleId": "n_67a0896af392c136", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_738a62581b7cff87": { "inputs": { "state": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ac21ebf399873404", "output": "state" } ] }, "from": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ba9d1ba0e6020aab", "output": "from" } ] }, "to": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ba9d1ba0e6020aab", "output": "to" } ] } }, "name": "Prepare five words for SMS", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "from" }, { "jsonSchema": { "type": "string", "minLength": 7, "maxLength": 15, "pattern": "^[0-9]{7,15}$", "description": "The destination number in E.164 format without a leading plus sign." }, "nullable": false, "handle": "to" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1, "description": "The sender name or number. Number senders use E.164 format; sender ID rules vary by country." }, "nullable": false, "handle": "from" }, { "jsonSchema": { "type": "string", "minLength": 7, "maxLength": 15, "pattern": "^[0-9]{7,15}$", "description": "The destination number in E.164 format without a leading plus sign." }, "nullable": false, "handle": "to" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The text body of the outbound SMS." }, "nullable": false, "handle": "text" } ], "name": "Prepare five words for SMS", "moduleId": "n_6a50dc8aae4f9a7c", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_aa63eccf6d93a329": { "inputs": { "from": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_738a62581b7cff87", "output": "from" } ] }, "to": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_738a62581b7cff87", "output": "to" } ] }, "text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_738a62581b7cff87", "output": "text" } ] }, "type": { "kind": "value", "value": "unicode" } }, "name": "Send the vocabulary SMS", "kind": "task", "task": { "inputs": [ { "description": "The sender name or number. Number senders use E.164 format; sender ID rules vary by country.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The sender name or number. Number senders use E.164 format; sender ID rules vary by country." }, "nullable": false, "handle": "from" }, { "description": "The destination number in E.164 format without a leading plus sign.", "jsonSchema": { "type": "string", "minLength": 7, "maxLength": 15, "pattern": "^[0-9]{7,15}$", "description": "The destination number in E.164 format without a leading plus sign." }, "nullable": false, "handle": "to" }, { "description": "The text body of the outbound SMS.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The text body of the outbound SMS." }, "nullable": false, "handle": "text" }, { "description": "The SMS text encoding.", "jsonSchema": { "type": "string", "enum": [ "text", "unicode" ], "description": "The SMS text encoding." }, "nullable": true, "handle": "type", "value": null }, { "description": "How long Vonage should attempt delivery, in milliseconds.", "jsonSchema": { "type": "integer", "minimum": 20000, "maximum": 604800000, "description": "How long Vonage should attempt delivery, in milliseconds." }, "nullable": true, "handle": "ttl", "value": null }, { "description": "Whether Vonage should request a delivery receipt.", "jsonSchema": { "type": "boolean", "description": "Whether Vonage should request a delivery receipt." }, "nullable": true, "handle": "statusReportRequired", "value": null }, { "description": "The delivery receipt callback URL for this message.", "jsonSchema": { "type": "string", "maxLength": 100, "format": "uri", "description": "The delivery receipt callback URL for this message." }, "nullable": true, "handle": "callback", "value": null }, { "description": "A caller-defined reference included in the submission result.", "jsonSchema": { "type": "string", "maxLength": 100, "description": "A caller-defined reference included in the submission result." }, "nullable": true, "handle": "clientRef", "value": null } ], "outputs": [ { "description": "The number of SMS submission results returned by Vonage.", "jsonSchema": { "type": "integer", "description": "The number of SMS submission results returned by Vonage." }, "nullable": false, "handle": "messageCount" }, { "description": "The submitted SMS results.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "to": { "type": "string", "description": "The destination number in E.164 format." }, "messageId": { "type": "string", "description": "The Vonage message identifier." }, "status": { "type": "string", "description": "The Vonage SMS status code." }, "remainingBalance": { "type": [ "string", "null" ], "description": "The estimated remaining account balance." }, "messagePrice": { "type": [ "string", "null" ], "description": "The estimated price of the message." }, "network": { "type": [ "string", "null" ], "description": "The destination network identifier." }, "clientRef": { "type": [ "string", "null" ], "description": "The caller-supplied client reference." } }, "required": [ "to", "messageId", "status", "remainingBalance", "messagePrice", "network", "clientRef" ], "additionalProperties": false, "description": "One normalized Vonage SMS submission result." }, "description": "The submitted SMS results." }, "nullable": false, "handle": "messages" } ], "name": "Send the vocabulary SMS", "executor": { "kind": "connector", "action": "vonage.send_sms" } } }, "n_135fda4f732dd1a0": { "inputs": { "messageCount": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_aa63eccf6d93a329", "output": "messageCount" } ] }, "messages": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_aa63eccf6d93a329", "output": "messages" } ] } }, "name": "Confirm SMS submission", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "integer", "description": "The number of SMS submission results returned by Vonage." }, "nullable": false, "handle": "messageCount" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "to": { "type": "string", "description": "The destination number in E.164 format." }, "messageId": { "type": "string", "description": "The Vonage message identifier." }, "status": { "type": "string", "description": "The Vonage SMS status code." }, "remainingBalance": { "type": [ "string", "null" ], "description": "The estimated remaining account balance." }, "messagePrice": { "type": [ "string", "null" ], "description": "The estimated price of the message." }, "network": { "type": [ "string", "null" ], "description": "The destination network identifier." }, "clientRef": { "type": [ "string", "null" ], "description": "The caller-supplied client reference." } }, "required": [ "to", "messageId", "status", "remainingBalance", "messagePrice", "network", "clientRef" ], "additionalProperties": false, "description": "One normalized Vonage SMS submission result." }, "description": "The submitted SMS results." }, "nullable": false, "handle": "messages" } ], "outputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "accepted" } ], "name": "Confirm SMS submission", "moduleId": "n_2969264aae6588bc", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } } }, "edges": [ { "source": "n_1dceb4dfd8f6648a", "target": "n_ba9d1ba0e6020aab" }, { "source": "n_ba9d1ba0e6020aab", "target": "n_b474bfff4bd339bc" }, { "source": "n_b474bfff4bd339bc", "target": "n_5db5625f09ea6476" }, { "source": "n_5db5625f09ea6476", "target": "n_ac21ebf399873404" }, { "source": "n_ac21ebf399873404", "target": "n_9416758ad97c0f8c" }, { "source": "n_9416758ad97c0f8c", "target": "n_ac21ebf399873404", "sourceHandle": "more" }, { "source": "n_9416758ad97c0f8c", "target": "n_22c9048f8334fd5a", "sourceHandle": "otherwise" }, { "source": "n_22c9048f8334fd5a", "target": "n_5ac6a97631af4777" }, { "source": "n_5ac6a97631af4777", "target": "n_86cdd5c5403f8e1f", "sourceHandle": "more" }, { "source": "n_86cdd5c5403f8e1f", "target": "n_349ef10bb34c2d26" }, { "source": "n_349ef10bb34c2d26", "target": "n_22c9048f8334fd5a" }, { "source": "n_5ac6a97631af4777", "target": "n_738a62581b7cff87", "sourceHandle": "otherwise" }, { "source": "n_738a62581b7cff87", "target": "n_aa63eccf6d93a329" }, { "source": "n_aa63eccf6d93a329", "target": "n_135fda4f732dd1a0" } ] } }, "modules": { "n_3e717e0b6c623ba2": { "name": "Configure the destinations", "imports": [], "source": "export default function config({baseId,table,from,to}){for(const [k,v]of Object.entries({baseId,table,from,to}))if(typeof v!=='string'||!v.trim()||v.startsWith('YOUR_'))throw Error('Configure '+k);if(!/^[0-9]{7,15}$/.test(to))throw Error('Set destination as international digits without +');return{baseId,table,from,to}}\n" }, "n_12eea03a105fcbf7": { "name": "Extract unique title words", "imports": [], "source": "export default async function words({ids},context){if(!Array.isArray(ids)||ids.length<3)throw Error('Fewer than three top stories');let words=[];for(const id of ids.slice(0,3)){if(!Number.isSafeInteger(id)||id<=0)throw Error('Invalid story ID');const r=await context.actions.call('hackernews.get_item',{id});if(!r||r.id!==id||typeof r.title!=='string'||!r.title.trim())throw Error('Invalid story');for(const w of r.title.split(' '))if(w&&!/\\d/.test(w)&&!words.includes(w))words.push(w)}if(words.length<5)throw Error('Fewer than five words');return{words}}\n" }, "n_fb73890b694dcf7c": { "name": "Translate each word", "imports": [], "source": "export default async function translate({words,previous},context){if(!Array.isArray(words)||words.length<5)throw Error('Invalid words');const s=previous??{index:0,items:[]};if(s.index>=words.length)return{state:s,more:false};const word=words[s.index],r=await context.actions.call('lingvanex_translation_api.translate_text',{q:word,target:'de',format:'text'});if(!Array.isArray(r?.translations)||r.translations.length!==1||typeof r.translations[0].translatedText!=='string'||!r.translations[0].translatedText.trim())throw Error('Translation missing');const n={index:s.index+1,items:s.items.concat({english:word,german:r.translations[0].translatedText})};return{state:n,more:n.index0,records:batch.length?batch.map(x=>({fields:{'English word':x.english,'Translated word':x.german}})):[{fields:{'English word':'-','Translated word':'-'}}],expected:batch.length}}\n" }, "n_67a0896af392c136": { "name": "Confirm saved records", "imports": [], "source": "export default function confirm({records,expected}){if(!Array.isArray(records)||records.length!==expected||records.some(x=>typeof x.id!=='string'||!x.id))throw Error('Airtable create was not confirmed');return{confirmed:records.length}}\n" }, "n_6a50dc8aae4f9a7c": { "name": "Prepare five words for SMS", "imports": [], "source": "export default function message({state,from,to}){if(!Array.isArray(state.items)||state.items.length<5)throw Error('Not enough words');return{from,to,text:'Good morning, here are your words for today\\n'+state.items.slice(0,5).map(x=>x.english+' : '+x.german).join(', ')}}\n" }, "n_2969264aae6588bc": { "name": "Confirm SMS submission", "imports": [], "source": "export default function confirm({messageCount,messages}){if(messageCount<1||!Array.isArray(messages)||messages.length!==messageCount||messages.some(x=>x.status!=='0'||!x.messageId))throw Error('SMS submission not confirmed');return{accepted:messageCount}}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }