{ "kind": "open-flow-workflow", "version": 1, "name": "Set Slack Status and Light from Calendar Starts", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_f618094105d78b62": { "name": "Every five minutes", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "*/5 * * * *", "timezone": "UTC" } ] }, "n_8606b72605f0f147": { "inputs": { "calendarId": { "kind": "value", "value": "YOUR_CALENDAR_ID" }, "acknowledgeEventId": { "kind": "value", "value": "" } }, "name": "Open the calendar window", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "calendarId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "acknowledgeEventId" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "calendarId" }, { "jsonSchema": { "type": "number" }, "nullable": false, "handle": "from" }, { "jsonSchema": { "type": "number" }, "nullable": false, "handle": "to" } ], "name": "Open the calendar window", "moduleId": "n_97284ac6c1b6c8ae", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_5c3621907c381f85": { "inputs": { "calendarId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8606b72605f0f147", "output": "calendarId" } ] }, "from": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8606b72605f0f147", "output": "from" } ] }, "to": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8606b72605f0f147", "output": "to" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5c3621907c381f85", "output": "pageState" } ] } }, "name": "Read one calendar page", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "calendarId" }, { "jsonSchema": { "type": "number" }, "nullable": false, "handle": "from" }, { "jsonSchema": { "type": "number" }, "nullable": false, "handle": "to" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "page" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "pageState" } ], "name": "Read one calendar page", "moduleId": "n_4a72366ce70f28c1", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "googlecalendar.list_events" } ] } ] } }, "n_8e0b7f79fd405f40": { "inputs": { "page": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5c3621907c381f85", "output": "page" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8e0b7f79fd405f40", "output": "iteration" } ] } }, "name": "Select the next started event", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "page" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "morePages" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "eventId" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "iteration" } ], "name": "Select the next started event", "moduleId": "n_b150df0cfe701c58", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_0c33afce3431f7fe": { "inputs": {}, "name": "An event to process?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_8e0b7f79fd405f40", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_56441f052d598e7f": { "inputs": { "calendarId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8606b72605f0f147", "output": "calendarId" } ] }, "eventId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8e0b7f79fd405f40", "output": "eventId" } ] } }, "name": "Prepare the event status", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "calendarId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "eventId" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "route" } ], "name": "Prepare the event status", "moduleId": "n_7105e26365e45af1", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "googlecalendar.get_event" } ] } ] } }, "n_6a3ba15d024ab299": { "inputs": { "url": { "kind": "value", "value": "https://slack.com/api/users.profile.set" }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_56441f052d598e7f", "output": "body" } ] }, "auth.token": { "kind": "unset" } }, "name": "Update Slack status", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "auth.token" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Update Slack status", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "authentication": { "type": "bearer" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_6dbbaad7b16d1f02": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6a3ba15d024ab299", "output": "body" } ] }, "route": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_56441f052d598e7f", "output": "route" } ] } }, "name": "Confirm Slack status", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "route" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "route" } ], "name": "Confirm Slack status", "moduleId": "n_63af098739d3830b", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_7a4b6105975dcfd3": { "inputs": {}, "name": "Choose the light webhook", "kind": "condition", "matchMode": "first", "cases": [ { "output": "busy", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_6dbbaad7b16d1f02", "output": "route" } }, "operator": "==", "right": { "kind": "value", "value": "busy" } } ] } ] }, { "output": "personal", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_6dbbaad7b16d1f02", "output": "route" } }, "operator": "==", "right": { "kind": "value", "value": "personal" } } ] } ] } ] }, "n_9731e7e00a1c2aaa": { "inputs": { "url": { "kind": "value", "value": "https://example.com/configure-busy" } }, "name": "Set light busy", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Set light busy", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "authentication": { "type": "none" }, "responseFormat": "text", "statusPolicy": "success", "kind": "http-request" } } }, "n_5d0d7cc8089c023c": { "inputs": { "url": { "kind": "value", "value": "https://example.com/configure-personal" } }, "name": "Set light personal", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Set light personal", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "authentication": { "type": "none" }, "responseFormat": "text", "statusPolicy": "success", "kind": "http-request" } } }, "n_053aa0b253423a1e": { "inputs": { "url": { "kind": "value", "value": "https://example.com/configure-available" } }, "name": "Set light available", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Set light available", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "authentication": { "type": "none" }, "responseFormat": "text", "statusPolicy": "success", "kind": "http-request" } } }, "n_b095b49d82f92db5": { "inputs": { "eventId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8e0b7f79fd405f40", "output": "eventId" } ] } }, "name": "Confirm the event is handled", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "eventId" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "done" } ], "name": "Confirm the event is handled", "moduleId": "n_dc426a647488c823", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_8475d8f26c627c9c": { "inputs": {}, "name": "More calendar pages?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_8e0b7f79fd405f40", "output": "morePages" } }, "operator": "isTrue" } ] } ] } ] }, "n_fe081e5441dc2420": { "inputs": {}, "name": "Save the completed window", "kind": "task", "task": { "inputs": [], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "done" } ], "name": "Save the completed window", "moduleId": "n_11982a52d373a3ea", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } } }, "edges": [ { "source": "n_f618094105d78b62", "target": "n_8606b72605f0f147" }, { "source": "n_8606b72605f0f147", "target": "n_5c3621907c381f85" }, { "source": "n_5c3621907c381f85", "target": "n_8e0b7f79fd405f40" }, { "source": "n_8e0b7f79fd405f40", "target": "n_0c33afce3431f7fe" }, { "source": "n_0c33afce3431f7fe", "target": "n_56441f052d598e7f", "sourceHandle": "yes" }, { "source": "n_56441f052d598e7f", "target": "n_6a3ba15d024ab299" }, { "source": "n_6a3ba15d024ab299", "target": "n_6dbbaad7b16d1f02" }, { "source": "n_6dbbaad7b16d1f02", "target": "n_7a4b6105975dcfd3" }, { "source": "n_7a4b6105975dcfd3", "target": "n_9731e7e00a1c2aaa", "sourceHandle": "busy" }, { "source": "n_7a4b6105975dcfd3", "target": "n_5d0d7cc8089c023c", "sourceHandle": "personal" }, { "source": "n_7a4b6105975dcfd3", "target": "n_053aa0b253423a1e", "sourceHandle": "otherwise" }, { "source": "n_9731e7e00a1c2aaa", "target": "n_b095b49d82f92db5" }, { "source": "n_5d0d7cc8089c023c", "target": "n_b095b49d82f92db5" }, { "source": "n_053aa0b253423a1e", "target": "n_b095b49d82f92db5" }, { "source": "n_b095b49d82f92db5", "target": "n_8e0b7f79fd405f40" }, { "source": "n_0c33afce3431f7fe", "target": "n_8475d8f26c627c9c", "sourceHandle": "otherwise" }, { "source": "n_8475d8f26c627c9c", "target": "n_5c3621907c381f85", "sourceHandle": "yes" }, { "source": "n_8475d8f26c627c9c", "target": "n_fe081e5441dc2420", "sourceHandle": "otherwise" } ] } }, "modules": { "n_97284ac6c1b6c8ae": { "name": "Open the calendar window", "imports": [], "source": "export default async function window({calendarId,acknowledgeEventId},context){if(typeof calendarId!=='string'||!calendarId.trim()||calendarId==='YOUR_CALENDAR_ID')throw Error('Configure a calendar ID');let s=await context.state.get('calendar-start-progress'),now=Date.now();if(s===null){s={last:now,seen:[],pending:null};await context.state.set('calendar-start-progress',s)}if(!s||!Number.isFinite(s.last)||!Array.isArray(s.seen))throw Error('Invalid calendar checkpoint');if(s.pending){if(typeof acknowledgeEventId!=='string'||acknowledgeEventId!==s.pending)throw Error('Pending event '+s.pending+': check Slack and the light, then acknowledge this event ID');s.seen=[...new Set([...s.seen,s.pending])];s.pending=null;await context.state.set('calendar-start-progress',s)}let from=s.window?.from??s.last,to=s.window?.to??now;if(!Number.isFinite(from)||!Number.isFinite(to)||from>to)throw Error('Invalid calendar window');if(!s.window){s.window={from,to};await context.state.set('calendar-start-progress',s)}return{calendarId,from,to}}\n" }, "n_4a72366ce70f28c1": { "name": "Read one calendar page", "imports": [], "source": "export default async function page({calendarId,from,to,previous},context){const token=previous?.nextToken??null;if(previous&&(!token||typeof token!=='string'))throw Error('Invalid page token');const r=await context.actions.call('googlecalendar.list_events',{calendarId,timeMin:new Date(from-1000).toISOString(),timeMax:new Date(to+1000).toISOString(),singleEvents:true,showDeleted:false,orderBy:'startTime',maxResults:100,...(token?{pageToken:token}:{})});if(!r||!Array.isArray(r.items))throw Error('Invalid calendar page');const next=r.nextPageToken??null;if(next!==null&&(typeof next!=='string'||!next||next===token||(previous?.seen??[]).includes(next)))throw Error('Invalid next page');function start(e){let s=e?.start;if(s?.dateTime){let t=Date.parse(s.dateTime);if(!Number.isFinite(t))throw Error('Invalid event start');return t}if(s?.date){let zone=s.timeZone||r.timeZone;if(typeof zone!=='string'||!zone)throw Error('Missing calendar timezone');let [y,m,d]=s.date.split('-').map(Number);if(!y||!m||!d)throw Error('Invalid all-day start');let target=Date.UTC(y,m-1,d),t=target;const f=new Intl.DateTimeFormat('en-CA-u-ca-iso8601',{timeZone:zone,year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit',hourCycle:'h23'});for(let i=0;i<3;i++){let q=Object.fromEntries(f.formatToParts(new Date(t)).map(x=>[x.type,x.value]));t+=target-Date.UTC(+q.year,+q.month-1,+q.day,+q.hour,+q.minute,+q.second)}return t}throw Error('Missing event start')};const ids=[];for(const e of r.items){const t=start(e);if(t>from&&t<=to){if(typeof e.id!=='string'||!e.id)throw Error('Missing event ID');ids.push(e.id)}}return{page:{ids,key:token??'',nextToken:next},pageState:{nextToken:next,seen:token?[...(previous?.seen??[]),token]:(previous?.seen??[])}}}\n" }, "n_b150df0cfe701c58": { "name": "Select the next started event", "imports": [], "source": "export default async function select({page,previous},context){if(!page||!Array.isArray(page.ids))throw Error('Invalid page');let s=await context.state.get('calendar-start-progress');if(!s||s.pending)throw Error('Pending event requires attention');let i=previous?.key===page.key?previous.index:0;for(;i