{ "kind": "open-flow-workflow", "version": 1, "name": "Weekly Mattermost Coffee Groups", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_c15a9689cc2b5de1": { "name": "Every Monday at 10 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 10 * * 1", "timezone": "UTC" } ] }, "n_acdfcf20ac919572": { "inputs": { "scheduledAt": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_c15a9689cc2b5de1", "output": "scheduledAt" } ] }, "channelId": { "kind": "value", "value": "YOUR_MATTERMOST_CHANNEL_ID" }, "calendarId": { "kind": "value", "value": "YOUR_GOOGLE_CALENDAR_ID" } }, "name": "Configure the coffee session", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "scheduledAt" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "channelId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "calendarId" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "channelId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "calendarId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "start" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "end" } ], "name": "Configure the coffee session", "moduleId": "n_a390e5b336536fff", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_95e97238b9cfafe5": { "inputs": { "channelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_acdfcf20ac919572", "output": "channelId" } ] }, "message": { "kind": "value", "value": "👋 Happy Monday\n\nGroups for this week's virtual coffee are:" } }, "name": "Greet the channel", "kind": "task", "task": { "inputs": [ { "description": "A Mattermost object ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "A Mattermost object ID." }, "nullable": false, "handle": "channelId" }, { "description": "The Markdown message body to post.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The Markdown message body to post." }, "nullable": false, "handle": "message" }, { "description": "Optional root post ID for replying in a thread.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Optional root post ID for replying in a thread." }, "nullable": true, "handle": "rootId", "value": null }, { "description": "Optional Mattermost post props object.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "Optional Mattermost post props object." }, "nullable": true, "handle": "props", "value": null } ], "outputs": [ { "description": "A Mattermost post object.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Mattermost post object." }, "nullable": false, "handle": "post" }, { "description": "The raw Mattermost API JSON response.", "jsonSchema": { "description": "The raw Mattermost API JSON response." }, "nullable": false, "handle": "raw" } ], "name": "Greet the channel", "executor": { "kind": "connector", "action": "mattermost.create_post" } } }, "n_5221c63add8db707": { "inputs": { "channelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_acdfcf20ac919572", "output": "channelId" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5221c63add8db707", "output": "state" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_776dd14a29e5f92a", "output": "statusCode" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_776dd14a29e5f92a", "output": "body" } ] }, "bodyEncoding": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_776dd14a29e5f92a", "output": "bodyEncoding" } ] } }, "name": "Collect channel members", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "channelId" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "integer" }, "nullable": true, "handle": "statusCode" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": false, "handle": "query" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "users" } ], "name": "Collect channel members", "moduleId": "n_41cac5e3f36fd88c", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_aee5189baf3563ba": { "inputs": {}, "name": "More member pages", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_5221c63add8db707", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_776dd14a29e5f92a": { "inputs": { "endpoint": { "kind": "value", "value": "/users" }, "query": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5221c63add8db707", "output": "query" } ] } }, "name": "Read channel members", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": true, "handle": "query" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "name": "Read channel members", "executor": { "providerId": "mattermost", "method": "GET", "query": { "mode": "object", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "kind": "connector-proxy" } } }, "n_8d6817ee5a34b5bf": { "inputs": { "users": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5221c63add8db707", "output": "users" } ] } }, "name": "Shuffle and form groups", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "users" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "type": "object", "additionalProperties": true } } }, "nullable": false, "handle": "groups" } ], "name": "Shuffle and form groups", "moduleId": "n_16455fbb3a2e4c5b", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_022e23946611f999": { "inputs": { "groups": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8d6817ee5a34b5bf", "output": "groups" } ] }, "channelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_acdfcf20ac919572", "output": "channelId" } ] }, "calendarId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_acdfcf20ac919572", "output": "calendarId" } ] }, "start": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_acdfcf20ac919572", "output": "start" } ] }, "end": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_acdfcf20ac919572", "output": "end" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_022e23946611f999", "output": "state" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6819abc6c2424f16", "output": "statusCode" } ] }, "bodyEncoding": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6819abc6c2424f16", "output": "bodyEncoding" } ] } }, "name": "Prepare each group", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "type": "object", "additionalProperties": true } } }, "nullable": false, "handle": "groups" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "channelId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "calendarId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "start" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "end" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "integer" }, "nullable": true, "handle": "statusCode" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "announce" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "A Mattermost object ID." }, "nullable": false, "handle": "channelId" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The Markdown message body to post." }, "nullable": false, "handle": "message" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": false, "handle": "query" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "name": "Prepare each group", "moduleId": "n_dde9f94789667d28", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_8e7797735ea366de": { "inputs": {}, "name": "Any groups remaining", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_022e23946611f999", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_ce1e75da0ddb4da3": { "inputs": {}, "name": "Announce or invite", "kind": "condition", "matchMode": "first", "cases": [ { "output": "announce", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_022e23946611f999", "output": "announce" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_491a1afe52658788": { "inputs": { "channelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_022e23946611f999", "output": "channelId" } ] }, "message": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_022e23946611f999", "output": "message" } ] } }, "name": "Announce the group", "kind": "task", "task": { "inputs": [ { "description": "A Mattermost object ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "A Mattermost object ID." }, "nullable": false, "handle": "channelId" }, { "description": "The Markdown message body to post.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The Markdown message body to post." }, "nullable": false, "handle": "message" }, { "description": "Optional root post ID for replying in a thread.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Optional root post ID for replying in a thread." }, "nullable": true, "handle": "rootId", "value": null }, { "description": "Optional Mattermost post props object.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "Optional Mattermost post props object." }, "nullable": true, "handle": "props", "value": null } ], "outputs": [ { "description": "A Mattermost post object.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Mattermost post object." }, "nullable": false, "handle": "post" }, { "description": "The raw Mattermost API JSON response.", "jsonSchema": { "description": "The raw Mattermost API JSON response." }, "nullable": false, "handle": "raw" } ], "name": "Announce the group", "executor": { "kind": "connector", "action": "mattermost.create_post" } } }, "n_6819abc6c2424f16": { "inputs": { "endpoint": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_022e23946611f999", "output": "endpoint" } ] }, "query": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_022e23946611f999", "output": "query" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_022e23946611f999", "output": "body" } ] } }, "name": "Create the group invitation", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": true, "handle": "query" }, { "jsonSchema": {}, "nullable": true, "handle": "body" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "name": "Create the group invitation", "executor": { "providerId": "googlecalendar", "method": "POST", "query": { "mode": "object", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "kind": "connector-proxy" } } }, "n_8727fa3be3fa864c": { "name": "Groups processed", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "outputMappings": { "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_022e23946611f999", "output": "count" } ] } } } }, "edges": [ { "source": "n_c15a9689cc2b5de1", "target": "n_acdfcf20ac919572" }, { "source": "n_acdfcf20ac919572", "target": "n_95e97238b9cfafe5" }, { "source": "n_95e97238b9cfafe5", "target": "n_5221c63add8db707" }, { "source": "n_5221c63add8db707", "target": "n_aee5189baf3563ba" }, { "source": "n_aee5189baf3563ba", "target": "n_776dd14a29e5f92a", "sourceHandle": "more" }, { "source": "n_776dd14a29e5f92a", "target": "n_5221c63add8db707" }, { "source": "n_aee5189baf3563ba", "target": "n_8d6817ee5a34b5bf", "sourceHandle": "otherwise" }, { "source": "n_8d6817ee5a34b5bf", "target": "n_022e23946611f999" }, { "source": "n_022e23946611f999", "target": "n_8e7797735ea366de" }, { "source": "n_8e7797735ea366de", "target": "n_ce1e75da0ddb4da3", "sourceHandle": "more" }, { "source": "n_8e7797735ea366de", "target": "n_8727fa3be3fa864c", "sourceHandle": "otherwise" }, { "source": "n_ce1e75da0ddb4da3", "target": "n_491a1afe52658788", "sourceHandle": "announce" }, { "source": "n_491a1afe52658788", "target": "n_022e23946611f999" }, { "source": "n_ce1e75da0ddb4da3", "target": "n_6819abc6c2424f16", "sourceHandle": "otherwise" }, { "source": "n_6819abc6c2424f16", "target": "n_022e23946611f999" } ] } }, "modules": { "n_a390e5b336536fff": { "name": "Configure the coffee session", "imports": [], "source": "export default function coffeeConfig({scheduledAt,channelId,calendarId}){\n if(!channelId||channelId.startsWith('YOUR_')||!calendarId||calendarId.startsWith('YOUR_'))throw Error('Configure the Mattermost channel and Calendar ID');\n const time=Date.parse(scheduledAt);if(!Number.isFinite(time))throw Error('Schedule time is invalid');\n return{channelId,calendarId,start:new Date(time+3600000).toISOString(),end:new Date(time+5400000).toISOString()};\n}\n" }, "n_41cac5e3f36fd88c": { "name": "Collect channel members", "imports": [], "source": "export default function coffeePages({channelId,previousState,statusCode,body,bodyEncoding}){\n let state;if(previousState===null)state={page:0,users:[],done:false};else{\n if(!Number.isInteger(statusCode)||statusCode<200||statusCode>=300||bodyEncoding!=null||!Array.isArray(body))throw Error('Mattermost user page failed');\n if(previousState.page>=100)throw Error('Channel membership exceeds pagination limit');\n state={page:previousState.page+1,users:previousState.users.concat(body),done:body.length===0};\n }\n return{state,more:!state.done,query:{in_channel:channelId,page:state.page,per_page:100},users:state.users};\n}\n" }, "n_16455fbb3a2e4c5b": { "name": "Shuffle and form groups", "imports": [], "source": "export default function coffeeGroups({users}){\n if(users.length<2)return{groups:[]};\n const entries=users.map(u=>{if(!u||typeof u.username!=='string'||!u.username||typeof u.email!=='string'||!u.email)throw Error('Every member needs username and email');return{username:u.username,email:u.email}});\n for(let n=entries.length;n>0;n--){let j=Math.floor(Math.random()*n);[entries[n-1],entries[j]]=[entries[j],entries[n-1]];}\n const groups=[];for(let i=0;i=300||bodyEncoding!=null))throw Error('Calendar invitation failed');state={phase:state.phase,index:state.index+1};}\n if(state.index>=groups.length){if(state.phase==='announce')state={phase:'invite',index:0};else state={phase:'done',index:0};}\n if(!groups.length)state={phase:'done',index:0};\n const more=state.phase!=='done',g=more?groups[state.index]:[],id=Date.now().toString(36)+'-'+Math.random().toString(36).slice(2)+'-'+state.index;\n return{state,more,announce:state.phase==='announce',channelId,message:more?'☀️ '+g.map(x=>x.username).join(', '):'-',endpoint:'/calendars/'+encodeURIComponent(calendarId)+'/events',query:{conferenceDataVersion:1,sendUpdates:'all'},body:{summary:'n8n coffee catchup',start:{dateTime:start},end:{dateTime:end},attendees:g.map(x=>({email:x.email})),guestsCanModify:true,conferenceData:{createRequest:{requestId:id,conferenceSolutionKey:{type:'hangoutsMeet'}}}},count:groups.length};\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }