{ "kind": "open-flow-workflow", "version": 1, "name": "Bookmark New YouTube Playlist Videos", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_353f1db1f3b8f1e9": { "name": "Check playlist now", "kind": "start", "inputDefs": [] }, "n_6ae8c6f762024a90": { "name": "Every 30 minutes UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "*/30 * * * *", "timezone": "UTC" } ] }, "n_9cdb9f06abe9deee": { "inputs": { "playlistId": { "kind": "value", "value": "YOUR_YOUTUBE_PLAYLIST_ID" }, "part": { "kind": "value", "value": [ "snippet" ] }, "maxResults": { "kind": "value", "value": 25 } }, "name": "Read the first 25 playlist items", "kind": "task", "task": { "inputs": [ { "description": "The YouTube playlist ID to inspect.", "jsonSchema": { "type": "string", "pattern": "\\S", "minLength": 1, "description": "The YouTube playlist ID to inspect." }, "nullable": false, "handle": "playlistId" }, { "description": "The resource parts to include.", "jsonSchema": { "type": "array", "items": { "type": "string", "pattern": "\\S", "minLength": 1, "description": "One YouTube API part such as snippet or statistics." }, "minItems": 1, "description": "The resource parts to include." }, "nullable": true, "handle": "part", "value": null }, { "description": "Maximum number of items to return per page.", "jsonSchema": { "type": "integer", "minimum": 1, "maximum": 50, "description": "Maximum number of items to return per page." }, "nullable": true, "handle": "maxResults", "value": null }, { "description": "Opaque pagination token returned by a previous YouTube API response.", "jsonSchema": { "type": "string", "pattern": "\\S", "minLength": 1, "description": "Opaque pagination token returned by a previous YouTube API response." }, "nullable": true, "handle": "pageToken", "value": null } ], "outputs": [ { "description": "The playlist items returned by YouTube.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "description": "The YouTube playlist item ID." }, "kind": { "type": [ "string", "null" ], "description": "The YouTube resource kind." }, "etag": { "type": [ "string", "null" ], "description": "The entity tag returned by YouTube." }, "snippet": { "type": [ "object", "null" ], "properties": {}, "additionalProperties": true, "description": "The snippet object returned by YouTube." }, "contentDetails": { "type": [ "object", "null" ], "properties": {}, "additionalProperties": true, "description": "The contentDetails object returned by YouTube." }, "status": { "type": [ "object", "null" ], "properties": {}, "additionalProperties": true, "description": "The status object returned by YouTube." }, "raw": { "type": "object", "properties": {}, "additionalProperties": true, "description": "The raw YouTube playlist item resource." } }, "required": [ "id", "kind", "etag", "snippet", "contentDetails", "status", "raw" ], "additionalProperties": false, "description": "A YouTube playlist item resource." }, "description": "The playlist items returned by YouTube." }, "nullable": false, "handle": "playlistItems" }, { "description": "The token for the next result page.", "jsonSchema": { "type": [ "string", "null" ], "description": "The token for the next result page." }, "nullable": true, "handle": "nextPageToken" }, { "description": "The token for the previous result page.", "jsonSchema": { "type": [ "string", "null" ], "description": "The token for the previous result page." }, "nullable": true, "handle": "prevPageToken" }, { "description": "Pagination summary returned by YouTube.", "jsonSchema": { "type": "object", "properties": { "totalResults": { "type": "integer", "description": "The total result count reported by YouTube." }, "resultsPerPage": { "type": "integer", "description": "The number of results included in this page." } }, "required": [ "totalResults", "resultsPerPage" ], "additionalProperties": false, "description": "Pagination summary returned by YouTube." }, "nullable": false, "handle": "pageInfo" } ], "name": "Read the first 25 playlist items", "executor": { "kind": "connector", "action": "youtube.list_playlist_items" } } }, "n_852020f46a49ede3": { "inputs": { "playlistItems": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_9cdb9f06abe9deee", "output": "playlistItems" } ] } }, "name": "Select and remember new videos", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": [ "string", "null" ], "description": "The YouTube playlist item ID." }, "kind": { "type": [ "string", "null" ], "description": "The YouTube resource kind." }, "etag": { "type": [ "string", "null" ], "description": "The entity tag returned by YouTube." }, "snippet": { "type": [ "object", "null" ], "properties": {}, "additionalProperties": true, "description": "The snippet object returned by YouTube." }, "contentDetails": { "type": [ "object", "null" ], "properties": {}, "additionalProperties": true, "description": "The contentDetails object returned by YouTube." }, "status": { "type": [ "object", "null" ], "properties": {}, "additionalProperties": true, "description": "The status object returned by YouTube." }, "raw": { "type": "object", "properties": {}, "additionalProperties": true, "description": "The raw YouTube playlist item resource." } }, "required": [ "id", "kind", "etag", "snippet", "contentDetails", "status", "raw" ], "additionalProperties": false, "description": "A YouTube playlist item resource." }, "description": "The playlist items returned by YouTube." }, "nullable": false, "handle": "playlistItems" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "plans" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "name": "Select and remember new videos", "moduleId": "n_a4f2c8f527bd0235", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_3e570c2545b5804f": { "inputs": { "plans": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_852020f46a49ede3", "output": "plans" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3e570c2545b5804f", "output": "state" } ] }, "result": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6eeca18d403b3363", "output": "result" } ] }, "item": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6eeca18d403b3363", "output": "item" } ] } }, "name": "Prepare the next bookmark", "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" }, { "jsonSchema": { "type": "boolean", "description": "Whether Raindrop.io completed the request successfully." }, "nullable": true, "handle": "result" }, { "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "The item returned by Raindrop.io." }, "nullable": true, "handle": "item" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string", "format": "uri", "description": "The bookmark URL." }, "nullable": false, "handle": "link" }, { "jsonSchema": { "type": "string", "maxLength": 1000, "description": "The bookmark title, up to 1000 characters." }, "nullable": false, "handle": "title" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "name": "Prepare the next bookmark", "moduleId": "n_8c6956f2c3a01419", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_87db609b82a05d7f": { "inputs": {}, "name": "More videos to bookmark?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_3e570c2545b5804f", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_6eeca18d403b3363": { "inputs": { "link": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3e570c2545b5804f", "output": "link" } ] }, "title": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3e570c2545b5804f", "output": "title" } ] }, "tags": { "kind": "value", "value": [ "youtube" ] }, "collectionId": { "kind": "value", "value": -1 } }, "name": "Create the Raindrop bookmark", "kind": "task", "task": { "inputs": [ { "description": "The bookmark URL.", "jsonSchema": { "type": "string", "format": "uri", "description": "The bookmark URL." }, "nullable": false, "handle": "link" }, { "description": "The bookmark title, up to 1000 characters.", "jsonSchema": { "type": "string", "maxLength": 1000, "description": "The bookmark title, up to 1000 characters." }, "nullable": true, "handle": "title", "value": null }, { "description": "The bookmark description, up to 10000 characters.", "jsonSchema": { "type": "string", "maxLength": 10000, "description": "The bookmark description, up to 10000 characters." }, "nullable": true, "handle": "excerpt", "value": null }, { "description": "The bookmark note, up to 10000 characters.", "jsonSchema": { "type": "string", "maxLength": 10000, "description": "The bookmark note, up to 10000 characters." }, "nullable": true, "handle": "note", "value": null }, { "description": "The collection ID that contains the bookmark.", "jsonSchema": { "type": "integer", "description": "The collection ID that contains the bookmark." }, "nullable": true, "handle": "collectionId", "value": null }, { "description": "The bookmark tags.", "jsonSchema": { "type": "array", "items": { "type": "string", "description": "A tag name." }, "description": "The bookmark tags." }, "nullable": true, "handle": "tags", "value": null }, { "description": "Whether the bookmark is marked as a favorite.", "jsonSchema": { "type": "boolean", "description": "Whether the bookmark is marked as a favorite." }, "nullable": true, "handle": "important", "value": null }, { "description": "The bookmark cover URL.", "jsonSchema": { "type": "string", "format": "uri", "description": "The bookmark cover URL." }, "nullable": true, "handle": "cover", "value": null }, { "description": "The bookmark content type.", "jsonSchema": { "type": "string", "enum": [ "link", "article", "image", "video", "document", "audio" ], "description": "The bookmark content type." }, "nullable": true, "handle": "type", "value": null } ], "outputs": [ { "description": "Whether Raindrop.io completed the request successfully.", "jsonSchema": { "type": "boolean", "description": "Whether Raindrop.io completed the request successfully." }, "nullable": false, "handle": "result" }, { "description": "The item returned by Raindrop.io.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "The item returned by Raindrop.io." }, "nullable": false, "handle": "item" } ], "name": "Create the Raindrop bookmark", "executor": { "kind": "connector", "action": "raindrop.create_raindrop" } } }, "n_057d1455a1b780b0": { "name": "Bookmark results", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "bookmarks" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "outputMappings": { "bookmarks": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3e570c2545b5804f", "output": "state" } ] }, "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3e570c2545b5804f", "output": "count" } ] } } } }, "edges": [ { "source": "n_353f1db1f3b8f1e9", "target": "n_9cdb9f06abe9deee" }, { "source": "n_6ae8c6f762024a90", "target": "n_9cdb9f06abe9deee" }, { "source": "n_9cdb9f06abe9deee", "target": "n_852020f46a49ede3" }, { "source": "n_852020f46a49ede3", "target": "n_3e570c2545b5804f" }, { "source": "n_3e570c2545b5804f", "target": "n_87db609b82a05d7f" }, { "source": "n_87db609b82a05d7f", "target": "n_6eeca18d403b3363", "sourceHandle": "more" }, { "source": "n_6eeca18d403b3363", "target": "n_3e570c2545b5804f" }, { "source": "n_87db609b82a05d7f", "target": "n_057d1455a1b780b0", "sourceHandle": "otherwise" } ] } }, "modules": { "n_a4f2c8f527bd0235": { "name": "Select and remember new videos", "imports": [], "source": "export default async function select({playlistItems},context){if(!Array.isArray(playlistItems))throw Error('Invalid playlist response');const rows=playlistItems.map(item=>{const s=item?.snippet,id=s?.resourceId?.videoId;if(typeof id!=='string'||!/^[A-Za-z0-9_-]+$/.test(id)||typeof s?.title!=='string'||!s.title||typeof s?.videoOwnerChannelTitle!=='string'||!s.videoOwnerChannelTitle)throw Error('Each playlist item requires a video ID, title and owner');const title=s.videoOwnerChannelTitle+' | '+s.title;if(title.length>1000)throw Error('Bookmark title exceeds 1000 characters');return{id,link:'https://www.youtube.com/watch?v='+encodeURIComponent(id),title}});if(!rows.length)return{plans:[],count:0};const saved=await context.state.get('youtube-raindrop-ids');if(saved!==null&&(!Array.isArray(saved)||saved.some(x=>typeof x!=='string')))throw Error('Invalid saved video IDs');const ids=rows.map(x=>x.id),fresh=saved===null?rows:rows.filter(x=>!saved.includes(x.id));await context.state.set('youtube-raindrop-ids',saved===null?ids:[...fresh.map(x=>x.id),...saved]);return{plans:fresh,count:fresh.length}}\n" }, "n_8c6956f2c3a01419": { "name": "Prepare the next bookmark", "imports": [], "source": "export default function next({plans,previousState,result,item}){const state=previousState??{index:0,created:[]};if(previousState){if(result!==true||!item||typeof item!=='object'||Array.isArray(item))throw Error('Raindrop did not return a created bookmark');state.created.push(item);state.index++}if(state.index===plans.length)return{state,more:false,link:'https://example.com',title:'unused',count:state.created.length};if(!Array.isArray(plans)||state.index>plans.length)throw Error('Invalid bookmark state');const p=plans[state.index];return{state,more:true,link:p.link,title:p.title,count:state.created.length}}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }