Workflow: develop a modular n8n workflow that supports Functionitem Executecommand Update Webhook
Integrations: cron, emailSend, executeCommand, functionItem, htmlExtract, httpRequest, if, moveBinaryData, readBinaryFile, writeBinaryFile

{"nodes": [{"name": "HTML Extract", "type": "n8n-nodes-base.htmlExtract", "position": [-220, -390], "parameters": {"options": {}, "extractionValues": {"values": [{"key": "price", "cssSelector": "={{$node[\"initItem\"].json[\"selector\"]}}"}]}}, "typeVersion": 1}, {"name": "Cron", "type": "n8n-nodes-base.cron", "position": [-1290, -390], "parameters": {"triggerTimes": {"item": [{"mode": "everyX", "unit": "minutes", "value": 15}]}}, "typeVersion": 1}, {"name": "getActualPrice", "type": "n8n-nodes-base.functionItem", "position": [-20, -390], "parameters": {"functionCode": "const globalData = getWorkflowStaticData('global');\n\nvar price = String(item.price).replace(\",\", \".\");\nprice = parseFloat(price);\n//price = price.replace(/\\D/g, '');\n//item.price = String(item.price).replace(\",\", \".\");\n//item.price = parseFloat(item.price);\n\nitem.priceExists = (price > 0 ? true : false)\nitem.price = price;\n\n// Update its data\nglobalData.actualPrice = item;\n\nreturn item;"}, "typeVersion": 1}, {"name": "fetchWeb", "type": "n8n-nodes-base.httpRequest", "position": [-410, -390], "parameters": {"url": "={{$node[\"initItem\"].json[\"link\"]}}", "options": {}, "responseFormat": "string"}, "typeVersion": 1}, {"name": "FunctionItem", "type": "n8n-nodes-base.functionItem", "position": [1020, -390], "parameters": {"functionCode": "const globalData = getWorkflowStaticData('global');\n\nglobalData.iteration = 0;\n//var thiselem = $node[\"initItem\"].json;\n\n//const s1 = {'slug': thiselem.slug, \"link\": thiselem.link, \"selector\": thiselem.selector, \"price\":$node[\"getActualPrice\"].json.price, \"currency\": thiselem.currency};\n//const s2 = {'slug': thiselem.slug+'2', \"link\": thiselem.link, \"selector\": thiselem.selector, \"price\":$node[\"getActualPrice\"].json.price, \"currency\": thiselem.currency};\n//const s3 = {'slug': thiselem.slug+'3', \"link\": thiselem.link, \"selector\": thiselem.selector, \"price\":$node[\"getActualPrice\"].json.price, \"currency\": thiselem.currency};\n\nreturn $node[\"changeME\"].json.myWatchers;\n"}, "typeVersion": 1}, {"name": "Write Binary File1", "type": "n8n-nodes-base.writeBinaryFile", "position": [1850, -390], "parameters": {"fileName": "/data/kopacky.json", "dataPropertyName": "=price"}, "typeVersion": 1}, {"name": "Move Binary Data1", "type": "n8n-nodes-base.moveBinaryData", "position": [1420, -390], "parameters": {"mode": "jsonToBinary", "options": {}, "destinationKey": "price"}, "typeVersion": 1}, {"name": "IF1", "type": "n8n-nodes-base.if", "position": [550, -370], "parameters": {"conditions": {"string": [{"value1": "={{$node[\"checkifexists\"].json[\"stdout\"]}}", "value2": "Exists", "operation": "notEqual"}]}}, "typeVersion": 1}, {"name": "checkifexists", "type": "n8n-nodes-base.executeCommand", "position": [410, -370], "parameters": {"command": "if [ -r /data/kopacky.json ]; then echo Exists; fi"}, "typeVersion": 1}, {"name": "IF3", "type": "n8n-nodes-base.if", "position": [680, 110], "parameters": {"conditions": {"string": [{"value1": "={{$node[\"checkifexists\"].json[\"stdout\"]}}", "value2": "Exists"}]}}, "typeVersion": 1}, {"name": "SaveToFile", "type": "n8n-nodes-base.writeBinaryFile", "position": [1650, 110], "parameters": {"fileName": "/data/kopacky.json", "dataPropertyName": "=price"}, "typeVersion": 1}, {"name": "JsonToBinary", "type": "n8n-nodes-base.moveBinaryData", "position": [1500, 110], "parameters": {"mode": "jsonToBinary", "options": {}, "destinationKey": "price"}, "typeVersion": 1}, {"name": "changeME", "type": "n8n-nodes-base.functionItem", "color": "#3BDD33", "position": [-830, -390], "parameters": {"functionCode": "const globalData = getWorkflowStaticData('global');\n\n//{'slug': 'kopacky', 'link': 'https://www.adsport.sk/kopacky-lisovky-adidas-x-19-3-ll-fg-ef0598/#1131861', 'currency': 'EUR'}[]\nvar myWatchers = [\n{'slug': 'kopacky', 'link': 'https://www.adsport.sk/kopacky-lisovky-adidas-x-19-3-ll-fg-ef0598/#1131861', 'selector':'.prices > strong:nth-child(1) > span:nth-child(1)', 'currency': 'EUR'},\n{'slug': 'kopacky2', 'link': 'https://www.adsport.sk/turfy-adidas-ace-tango-17-3-tf-by2203/', 'selector':'.col-xs-4 > strong:nth-child(1) > span:nth-child(1)', 'currency': 'EUR'},\n{'slug': 'mobilcek', 'link': 'https://mobil.bazos.sk/inzerat/112253662/predam-odolny-doogee-s60-52-4g-lte-nfc.php', 'selector':'.listadvlevo > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(5) > td:nth-child(2) > b:nth-child(2)', 'currency': 'EUR'},\n{'slug': 'ADIZERO RC 2', 'link': 'https://www.adsport.sk/panske-bezecke-topanky-adidas-adizero-rc-2-eg1187/', 'selector':'.col-xs-4 > strong:nth-child(1) > span:nth-child(1)', 'currency': 'EUR'}\n];\n\nitem.myWatchers = myWatchers;\nitem.watchersCount = myWatchers.length;\nglobalData.myWatchers = myWatchers;\n\nreturn item;"}, "typeVersion": 1}, {"name": "initItem", "type": "n8n-nodes-base.functionItem", "position": [-620, -390], "parameters": {"functionCode": "const globalData = getWorkflowStaticData('global');\n\nvar counter = globalData.iteration;\n\nitem.myWatchers[counter].watchersCount = item.watchersCount;\nitem.myWatchers[counter].canContinue = (globalData.iteration < item.watchersCount-1 ? true : false);\n//item.myWatchers[counter].canContinue = false;\n\nglobalData.iteration = counter + 1;\n\nreturn item.myWatchers[counter];"}, "typeVersion": 1}, {"name": "savedItems", "type": "n8n-nodes-base.readBinaryFile", "position": [850, -20], "parameters": {"filePath": "/data/kopacky.json", "dataPropertyName": "savedItems"}, "typeVersion": 1, "continueOnFail": true, "alwaysOutputData": true}, {"name": "itemsToJSON", "type": "n8n-nodes-base.moveBinaryData", "position": [1020, -20], "parameters": {"options": {}, "sourceKey": "savedItems"}, "typeVersion": 1}, {"name": "IF", "type": "n8n-nodes-base.if", "position": [2190, -90], "parameters": {"conditions": {"string": [], "boolean": [{"value1": "={{$node[\"initItem\"].json[\"canContinue\"]}}", "value2": true}]}}, "typeVersion": 1}, {"name": "initItem1", "type": "n8n-nodes-base.functionItem", "position": [-1060, -390], "parameters": {"functionCode": "const globalData = getWorkflowStaticData('global');\n\nglobalData.iteration = 0;\n\nreturn item;"}, "typeVersion": 1}, {"name": "IF2", "type": "n8n-nodes-base.if", "position": [1850, 110], "parameters": {"conditions": {"number": [{"value1": "={{$node[\"getActualPrice\"].json[\"price\"]}}", "value2": "={{$node[\"updateSavedItems1\"].json[\"oldPrice\"]}}"}], "string": []}}, "typeVersion": 1}, {"name": "updateSavedItems", "type": "n8n-nodes-base.functionItem", "position": [1350, 110], "parameters": {"functionCode": "const globalData = getWorkflowStaticData('global');\n\nvar myitems = [];\nvar i;\nfor (i = 0; i < item.items.length; i++) { \n  if($node[\"initItem\"].json.slug == item.items[i].slug && $node[\"getActualPrice\"].json.price < item.items[i].price) {\n    item.items[i].price = $node[\"getActualPrice\"].json.price;\n  }\n  myitems.push(item.items[i]);\n} \n\nreturn myitems;\n"}, "typeVersion": 1}, {"name": "updateSavedItems1", "type": "n8n-nodes-base.functionItem", "position": [1200, -20], "parameters": {"functionCode": "const globalData = getWorkflowStaticData('global');\nvar oldPrice = null;\nvar myitems = [];\nvar i;\nfor (i = 0; i < item.length; i++) {\n  if($node[\"initItem\"].json.slug == item[i].slug) {\n\n    item[i].link = $node[\"initItem\"].json.link;\n    item[i].selector = $node[\"initItem\"].json.selector;\n    item[i].currency = $node[\"initItem\"].json.currency;\n    \n    if(!item[i].price){\n      item[i].price = $node[\"getActualPrice\"].json.price;\n    }\n    \n    if($node[\"getActualPrice\"].json.price < item[i].price){\n      oldPrice = item[i].price;\n      item[i].price = $node[\"getActualPrice\"].json.price;\n    }\n    \n    \n  }\n  \n  myitems.push(item[i]);\n} \n\n//item.somar = $node[\"initItem\"].json;\n//return globalData.actualPrice;\n\nvar itemm = {};\nitemm.items = myitems;\nitemm.oldPrice = oldPrice;\nreturn itemm;\n"}, "typeVersion": 1}, {"name": "cleanData", "type": "n8n-nodes-base.executeCommand", "notes": "This will remove all storaged data.", "position": [-1290, -560], "parameters": {"command": "file=\"/data/kopacky.json\"\n[ -f $file ] && rm $file"}, "typeVersion": 1}, {"name": "IF4", "type": "n8n-nodes-base.if", "position": [150, -390], "parameters": {"conditions": {"string": [], "boolean": [{"value1": "={{$node[\"getActualPrice\"].json[\"priceExists\"]}}"}]}}, "typeVersion": 1}, {"name": "NotifyBetterPrice", "type": "n8n-nodes-base.emailSend", "position": [1850, -90], "parameters": {"html": "=<h2>Nov\u00e1 cena je: {{$node[\"getActualPrice\"].json[\"price\"]}} {{$node[\"initItem\"].json[\"currency\"]}}</h2><br>\nP\u00f4vodn\u00e1 cena bola: {{$node[\"updateSavedItems1\"].json[\"oldPrice\"]}} {{$node[\"initItem\"].json[\"currency\"]}}<br>\nURL: {{$node[\"initItem\"].json[\"link\"]}}", "text": "=", "options": {}, "subject": "=Nov\u00e1 cena - {{$node[\"initItem\"].json[\"slug\"]}} - {{$node[\"getActualPrice\"].json[\"price\"]}} {{$node[\"initItem\"].json[\"currency\"]}}", "toEmail": "sthosstudio@gmail.com", "fromEmail": "hostovecky@weyou.sk"}, "credentials": {"smtp": "hostovecky@weyou.sk"}, "typeVersion": 1}, {"name": "NotifyIncorrectPrice", "type": "n8n-nodes-base.emailSend", "position": [270, -690], "parameters": {"html": "=Please check the link or selector for the item with slug <strong>{{$node[\"initItem\"].json[\"slug\"]}}</strong><br>\nURL: {{$node[\"initItem\"].json[\"link\"]}}", "text": "=", "options": {}, "subject": "={{$node[\"initItem\"].json[\"slug\"]}} - Getting price issue.", "toEmail": "sthosstudio@gmail.com", "fromEmail": "hostovecky@weyou.sk"}, "credentials": {"smtp": "hostovecky@weyou.sk"}, "typeVersion": 1}], "connections": {"IF": {"main": [[{"node": "changeME", "type": "main", "index": 0}]]}, "IF1": {"main": [[{"node": "FunctionItem", "type": "main", "index": 0}], [{"node": "IF3", "type": "main", "index": 0}]]}, "IF2": {"main": [[{"node": "NotifyBetterPrice", "type": "main", "index": 0}], [{"node": "IF", "type": "main", "index": 0}]]}, "IF3": {"main": [[{"node": "savedItems", "type": "main", "index": 0}]]}, "IF4": {"main": [[{"node": "NotifyIncorrectPrice", "type": "main", "index": 0}], [{"node": "checkifexists", "type": "main", "index": 0}]]}, "Cron": {"main": [[{"node": "initItem1", "type": "main", "index": 0}]]}, "changeME": {"main": [[{"node": "initItem", "type": "main", "index": 0}]]}, "fetchWeb": {"main": [[{"node": "HTML Extract", "type": "main", "index": 0}]]}, "initItem": {"main": [[{"node": "fetchWeb", "type": "main", "index": 0}]]}, "initItem1": {"main": [[{"node": "changeME", "type": "main", "index": 0}]]}, "SaveToFile": {"main": [[{"node": "IF2", "type": "main", "index": 0}]]}, "savedItems": {"main": [[{"node": "itemsToJSON", "type": "main", "index": 0}]]}, "itemsToJSON": {"main": [[{"node": "updateSavedItems1", "type": "main", "index": 0}]]}, "FunctionItem": {"main": [[{"node": "Move Binary Data1", "type": "main", "index": 0}]]}, "HTML Extract": {"main": [[{"node": "getActualPrice", "type": "main", "index": 0}]]}, "JsonToBinary": {"main": [[{"node": "SaveToFile", "type": "main", "index": 0}]]}, "checkifexists": {"main": [[{"node": "IF1", "type": "main", "index": 0}]]}, "getActualPrice": {"main": [[{"node": "IF4", "type": "main", "index": 0}]]}, "updateSavedItems": {"main": [[{"node": "JsonToBinary", "type": "main", "index": 0}]]}, "Move Binary Data1": {"main": [[{"node": "Write Binary File1", "type": "main", "index": 0}]]}, "NotifyBetterPrice": {"main": [[{"node": "IF", "type": "main", "index": 0}]]}, "updateSavedItems1": {"main": [[{"node": "updateSavedItems", "type": "main", "index": 0}]]}, "Write Binary File1": {"main": [[{"node": "IF", "type": "main", "index": 0}]]}, "NotifyIncorrectPrice": {"main": [[{"node": "checkifexists", "type": "main", "index": 0}]]}}}