{ "kind": "open-flow-workflow", "version": 1, "name": "Save Starred Tiny Tiny RSS Articles to Wallabag", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_00a401f349ec2b4b": { "name": "Check now", "kind": "start", "inputDefs": [] }, "n_c3cfc47e7b179b55": { "name": "Every ten minutes UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "*/10 * * * *", "timezone": "UTC" } ] }, "n_259a77bd62cdab8e": { "inputs": { "rssBase": { "kind": "value", "value": "https://YOUR_TTRSS_HOST/tt-rss" }, "wallabagBase": { "kind": "value", "value": "https://YOUR_WALLABAG_HOST" }, "rssUser": { "kind": "unset" }, "rssPassword": { "kind": "unset" }, "clientId": { "kind": "unset" }, "clientSecret": { "kind": "unset" }, "wallabagUser": { "kind": "unset" }, "wallabagPassword": { "kind": "unset" } }, "name": "Configure services", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "rssBase" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "wallabagBase" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "rssUser" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "rssPassword" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "clientId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "clientSecret" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "wallabagUser" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "wallabagPassword" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "rssUrl" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "tokenUrl" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "saveUrl" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "rssLogin" }, { "jsonSchema": { "type": "object", "additionalProperties": { "anyOf": [ { "type": [ "string", "number", "boolean" ] }, { "type": "array", "items": { "type": [ "string", "number", "boolean" ] } } ] } }, "nullable": false, "handle": "tokenBody" } ], "name": "Configure services", "moduleId": "n_4f05d625b0606849", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_bb4d8f750db76ec0": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_259a77bd62cdab8e", "output": "rssUrl" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_259a77bd62cdab8e", "output": "rssLogin" } ] } }, "name": "Log in to Tiny Tiny RSS", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "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" } ], "name": "Log in to Tiny Tiny RSS", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "authentication": { "type": "none" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_ae6a95bee94849d6": { "inputs": { "response": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_bb4d8f750db76ec0", "output": "body" } ] } }, "name": "Prepare starred feed request", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "response" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" } ], "name": "Prepare starred feed request", "moduleId": "n_68c7f547144a012b", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_e7f726c2c169663f": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_259a77bd62cdab8e", "output": "rssUrl" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ae6a95bee94849d6", "output": "body" } ] } }, "name": "Read starred articles", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "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" } ], "name": "Read starred articles", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "authentication": { "type": "none" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_8256718436c36ec0": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_259a77bd62cdab8e", "output": "tokenUrl" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_259a77bd62cdab8e", "output": "tokenBody" } ] } }, "name": "Log in to Wallabag", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "object", "additionalProperties": { "anyOf": [ { "type": [ "string", "number", "boolean" ] }, { "type": "array", "items": { "type": [ "string", "number", "boolean" ] } } ] } }, "nullable": false, "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" } ], "name": "Log in to Wallabag", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "form", "authentication": { "type": "none" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_0d324afbe8cfd71b": { "inputs": { "response": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e7f726c2c169663f", "output": "body" } ] }, "token": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8256718436c36ec0", "output": "body" } ] } }, "name": "Select new articles", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "response" }, { "jsonSchema": {}, "nullable": true, "handle": "token" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "plans" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "token" }, { "jsonSchema": {}, "nullable": true, "handle": "newest" } ], "name": "Select new articles", "moduleId": "n_c202828b7a0ddd04", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_2b4731961b8fe399": { "inputs": { "plans": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0d324afbe8cfd71b", "output": "plans" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2b4731961b8fe399", "output": "state" } ] } }, "name": "Prepare next article", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "plans" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "object", "additionalProperties": { "anyOf": [ { "type": [ "string", "number", "boolean" ] }, { "type": "array", "items": { "type": [ "string", "number", "boolean" ] } } ] } }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "name": "Prepare next article", "moduleId": "n_f66e9121777f8063", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_123e7eac71802cd1": { "inputs": {}, "name": "More articles?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_2b4731961b8fe399", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_4f374f614d327742": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_259a77bd62cdab8e", "output": "saveUrl" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2b4731961b8fe399", "output": "body" } ] }, "auth.token": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0d324afbe8cfd71b", "output": "token" } ] } }, "name": "Save to Wallabag", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "object", "additionalProperties": { "anyOf": [ { "type": [ "string", "number", "boolean" ] }, { "type": "array", "items": { "type": [ "string", "number", "boolean" ] } } ] } }, "nullable": false, "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": "Save to Wallabag", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "form", "authentication": { "type": "bearer" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_a54941f1f07e9ae6": { "inputs": { "newest": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0d324afbe8cfd71b", "output": "newest" } ] }, "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2b4731961b8fe399", "output": "count" } ] } }, "name": "Remember newest article", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "newest" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "outputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "name": "Remember newest article", "moduleId": "n_2d947a5a55cc4f1e", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_c3459b7e2a3d4c62": { "name": "Saved count", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "outputMappings": { "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_a54941f1f07e9ae6", "output": "count" } ] } } } }, "edges": [ { "source": "n_00a401f349ec2b4b", "target": "n_259a77bd62cdab8e" }, { "source": "n_c3cfc47e7b179b55", "target": "n_259a77bd62cdab8e" }, { "source": "n_259a77bd62cdab8e", "target": "n_bb4d8f750db76ec0" }, { "source": "n_bb4d8f750db76ec0", "target": "n_ae6a95bee94849d6" }, { "source": "n_ae6a95bee94849d6", "target": "n_e7f726c2c169663f" }, { "source": "n_e7f726c2c169663f", "target": "n_8256718436c36ec0" }, { "source": "n_8256718436c36ec0", "target": "n_0d324afbe8cfd71b" }, { "source": "n_0d324afbe8cfd71b", "target": "n_2b4731961b8fe399" }, { "source": "n_2b4731961b8fe399", "target": "n_123e7eac71802cd1" }, { "source": "n_123e7eac71802cd1", "target": "n_4f374f614d327742", "sourceHandle": "more" }, { "source": "n_4f374f614d327742", "target": "n_2b4731961b8fe399" }, { "source": "n_123e7eac71802cd1", "target": "n_a54941f1f07e9ae6", "sourceHandle": "otherwise" }, { "source": "n_a54941f1f07e9ae6", "target": "n_c3459b7e2a3d4c62" } ] } }, "modules": { "n_4f05d625b0606849": { "name": "Configure services", "imports": [], "source": "export default function configure({rssBase,wallabagBase,rssUser,rssPassword,clientId,clientSecret,wallabagUser,wallabagPassword}){function base(s){let u;try{u=new URL(s)}catch{throw Error('Invalid service URL')}if(u.protocol!=='https:'||u.username||u.password||u.search||u.hash)throw Error('Service URL must use HTTPS without embedded credentials, query or fragment');return u.href.replace(/\\/+$/,'')}const rss=base(rssBase),wallabag=base(wallabagBase);for(const v of [rssUser,rssPassword,clientId,clientSecret,wallabagUser,wallabagPassword])if(typeof v!=='string'||!v)throw Error('Configure all credentials');return{rssUrl:rss+'/api/',tokenUrl:wallabag+'/oauth/v2/token',saveUrl:wallabag+'/api/entries.json',rssLogin:{op:'login',user:rssUser,password:rssPassword},tokenBody:{grant_type:'password',client_id:clientId,client_secret:clientSecret,username:wallabagUser,password:wallabagPassword}}}\n" }, "n_68c7f547144a012b": { "name": "Prepare starred feed request", "imports": [], "source": "export default function prepare({response}){if(!response||response.status!==0||typeof response.content?.session_id!=='string'||!response.content.session_id)throw Error('Tiny Tiny RSS login failed');return{body:{sid:response.content.session_id,op:'getHeadLines',feed_id:'-1'}}}\n" }, "n_c202828b7a0ddd04": { "name": "Select new articles", "imports": [], "source": "export default async function plan({response,token},context){if(!response||response.status!==0||!Array.isArray(response.content))throw Error('Tiny Tiny RSS headlines failed');if(!token||typeof token.access_token!=='string'||!token.access_token)throw Error('Wallabag login failed');const rows=response.content;for(const r of rows)if(!r||!(typeof r.id==='number'||typeof r.id==='string')||typeof r.link!=='string'||!/^https?:\\/\\//i.test(r.link))throw Error('Invalid article ID or URL');if(!rows.length)return{plans:[],token:token.access_token,newest:null};const old=await context.state.get('ttrss-wallabag-last-id');let plans=[];for(const r of rows){if(old!==null&&String(r.id)===String(old))break;plans.push({id:r.id,url:r.link})}return{plans,token:token.access_token,newest:rows[0].id}}\n" }, "n_f66e9121777f8063": { "name": "Prepare next article", "imports": [], "source": "export default function next({plans,previousState}){const state=previousState??{index:0};if(previousState)state.index++;if(!Array.isArray(plans)||state.index>plans.length)throw Error('Invalid plan');return{state,more:state.index