Skip to content

Instantly share code, notes, and snippets.

@My-Random-Thoughts
Last active December 13, 2022 20:53
Show Gist options
  • Save My-Random-Thoughts/5d24159af4001a9a34110d8f71f954af to your computer and use it in GitHub Desktop.
Save My-Random-Thoughts/5d24159af4001a9a34110d8f71f954af to your computer and use it in GitHub Desktop.
Upload Files To A Synology DiskStation

This subflow will allow you to upload files to your Synology NAS in a quick and simple way.

It will check to see if a sesison token already exists and if not will log in and create one for you.


Node Configuration

Address

The full URL and port of your Synology NAS. Example: https://nas.myds.com:5001

Username

The name of the user to login to the NAS with. It is suggested that you use a separate user account for backups

Password

The password of the above username


Inputs

msg.localFile

The full name and path of the file you want to upload to your NAS.

msg.remotePath

The full path on your NAS of where you want to put the file.

msg.login (optional)

If set to true then the curent session token is removed and a new one generated. This will also reset the 7 day timeout.

msg.logout (optional)

If set to true then the current session token is removed and the synology session closed.

The Synology default timeout for all session tokens is 7 days.


Outputs

Output

On login, logout or file upload all messages will be send to the first output of the node.

Debug

Shows all messages coming from the node.

Error

If an error occurs it will be sent to the second output of the node with all the data relating to the error.

An internal lookup table will also show you the error message as well as the returned error number.

[{"id":"e730aec3bcfbca6b","type":"subflow","name":"syno upload","info":"This subflow will allow you to upload files to your Synology NAS in a quick and simple way.\n\nIt will check to see if a sesison token already exists and if not will log in and create one for you.\n\n---\n\n# Node Configuration\n## Address\nThe full URL and port of your Synology NAS.\nExample: `https://nas.myds.com:5001` \n\n## Username\nThe name of the user to login to the NAS with. It is suggested that you use a separate user account for backups\n\n## Password\nThe password of the above username\n\n---\n\n# Inputs\n## `msg.localFile`\nThe full name and path of the file you want to upload to your NAS. \n\n## `msg.remotePath`\nThe full path on your NAS of where you want to put the file.\n\n## `msg.login` (optional)\nIf set to `true` then the curent session token is removed and a new one generated. This will also reset the 7 day timeout.\n\n## `msg.logout` (optional)\nIf set to `true` then the current session token is removed and the synology session closed.\n\nThe Synology default timeout for all session tokens is 7 days.\n\n---\n\n# Outputs\n## Output\nOn login, logout or file upload all messages will be send to the first output of the node.\n\n## Debug\nShows all output from the node.\n\n## Error\nIf an error occurs it will be sent to the second output of the node with all the data relating to the error.\n\nAn internal lookup table will also show you the error message as well as the returned error number.\n","category":"","in":[{"x":40,"y":40,"wires":[{"id":"7c2d7be5ee6f2f53"}]}],"out":[{"x":1090,"y":680,"wires":[{"id":"611f866eedc883e3","port":0}]},{"x":1090,"y":720,"wires":[{"id":"80933021554f6bf8","port":0}]},{"x":1090,"y":760,"wires":[{"id":"1dfbc01638d59c17","port":0}]}],"env":[{"name":"Address","type":"str","value":"","ui":{"icon":"font-awesome/fa-server","type":"input","opts":{"types":["str"]}}},{"name":"Username","type":"str","value":"","ui":{"icon":"font-awesome/fa-user-o","type":"input","opts":{"types":["str"]}}},{"name":"Password","type":"cred","ui":{"icon":"font-awesome/fa-hashtag"}},{"name":"","type":"str","value":"","ui":{"type":"none"}},{"name":"Overwrite","type":"str","value":"overwrite","ui":{"icon":"font-awesome/fa-edit","type":"select","opts":{"opts":[{"l":{"en-US":"Overwrite"},"v":"overwrite"},{"l":{"en-US":"Skip"},"v":"skip"}]}}},{"name":"Output","type":"bool","value":"true","ui":{"icon":"font-awesome/fa-align-justify","type":"input","opts":{"types":["bool"]}}},{"name":"Send to the Output every uploaded filename (Debug will always output)","type":"str","value":"","ui":{"type":"none"}}],"meta":{"module":"syno_upload","type":"subflow","version":"0.0.1","author":"support@myrandomthoughts.co.uk","desc":"Subflow to allow uploading of files to a Synology DiskStation","keywords":"Synology File Upload","license":"MIT"},"color":"#DEB887","outputLabels":["output","debug","error"],"icon":"font-awesome/fa-upload","status":{"x":1060,"y":640,"wires":[{"id":"6ac5f87655d99187","port":0}]}},{"id":"7c2d7be5ee6f2f53","type":"function","z":"e730aec3bcfbca6b","name":"Operation Check","func":"if (msg.logout && msg.logout === true) {\n return [msg, null, null];\n}\n\n// Check SID exists and is less than 7 days old\nvar sid = flow.get(\"sid\") || null;\nif (sid) {\n var created = new Date(flow.get(\"created\") || \"31/12/2199\");\n var isExpired = false;\n var day7Date = new Date();\n day7Date.setDate(day7Date.getDate() + 7)\n if (created > day7Date) { isExpired = true; }\n}\n\nif (!sid || isExpired || (msg.login && msg.login == true)) {\n return [ null, msg, null ];\n}\nelse {\n // Path clean up\n msg.localFile = msg.localFile.replace(/\\/\\//g, '/').replace(/\\/$/g, '');\n msg.remotePath = msg.remotePath.replace(/\\/\\//g, '/').replace(/\\/$/g, '');\n\n if (msg.localFile && msg.remotePath) {\n return [ null, null, msg ];\n }\n}\n","outputs":3,"noerr":0,"initialize":"","finalize":"","libs":[],"x":130,"y":180,"wires":[["b7c216255bdc024d"],["7bd30b5a95918387"],["85ab83d56bc2121f"]],"outputLabels":["Log Out","Log In","Upload"]},{"id":"b7c216255bdc024d","type":"function","z":"e730aec3bcfbca6b","name":"Set Logout","func":"flow.set(\"sid\", undefined);\nflow.set(\"created\", undefined);\n\nmsg.operation = \"logout\";\nmsg.method = \"GET\";\nmsg.url = `${env.get(\"Address\")}/webapi/auth.cgi`;\nmsg.payload = {\n \"api\": \"SYNO.API.Auth\",\n \"version\": \"1\",\n \"method\": \"logout\",\n \"session\": \"FileStation\"\n}\n\nvar query = \"?\" +\n Object.entries(msg.payload).map(arr => arr[0] + \"=\" +\n encodeURIComponent(arr[1])).join(\"&\");\nmsg.url += query;\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":140,"wires":[["6b2e334f1e57459b"]]},{"id":"7bd30b5a95918387","type":"function","z":"e730aec3bcfbca6b","name":"Set Login","func":"flow.set(\"sid\", undefined);\nflow.set(\"created\", undefined);\nflow.set(\"localFile\", msg.localFile);\nflow.set(\"remotePath\", msg.remotePath);\n\nmsg.operation = \"login\";\nmsg.method = \"GET\";\nmsg.url = `${env.get(\"Address\")}/webapi/auth.cgi`;\nmsg.payload = {\n \"api\": \"SYNO.API.Auth\",\n \"version\": \"3\",\n \"method\": \"login\",\n \"account\": env.get(\"Username\"),\n \"passwd\": env.get(\"Password\"),\n \"session\": \"FileStation\",\n \"format\": \"cookie\",\n}\n\nvar query = \"?\" +\n Object.entries(msg.payload).map(arr => arr[0] + \"=\" +\n encodeURIComponent(arr[1])).join(\"&\");\nmsg.url += query;\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":180,"wires":[["6b2e334f1e57459b"]]},{"id":"85ab83d56bc2121f","type":"file in","z":"e730aec3bcfbca6b","name":"Get File","filename":"localFile","filenameType":"msg","format":"","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":340,"y":220,"wires":[["e3fa28bae2232c17"]]},{"id":"e3fa28bae2232c17","type":"function","z":"e730aec3bcfbca6b","name":"Set Upload","func":"//var localFile = msg.localFile;\n//var split = localFile.split(\"/\");\n//var filename = split.pop();\n\nmsg.operation = \"upload\";\nmsg.method = \"POST\";\nmsg.url = `${env.get(\"Address\")}/webapi/entry.cgi`;\nmsg.headers = {\n \"content-type\": \"multipart/form-data\",\n \"cookie\": `id=${flow.get(\"sid\")}`\n}\nmsg.payload = {\n \"api\": \"SYNO.FileStation.Upload\",\n \"version\": \"3\",\n \"method\": \"upload\",\n \"overwrite\": env.get(\"Overwrite\"),\n \"path\": msg.remotePath,\n \"create_parents\": \"true\",\n \"file\": {\n \"value\": msg.payload,\n \"options\": {\n \"filename\": msg.filename\n }\n }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":220,"wires":[["6b2e334f1e57459b"]]},{"id":"6b2e334f1e57459b","type":"http request","z":"e730aec3bcfbca6b","name":"HTTP Request","method":"use","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"headers":[],"x":720,"y":140,"wires":[["291a829236e0d935"]]},{"id":"a012f4cec44fc893","type":"switch","z":"e730aec3bcfbca6b","name":"Success","property":"payload.success","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"false","repair":false,"outputs":2,"x":420,"y":420,"wires":[["c6f317449e38131a"],["d0022136afd7797c"]]},{"id":"4bb4d1d323ad3b45","type":"json","z":"e730aec3bcfbca6b","name":"JSON","property":"payload","action":"","pretty":false,"x":270,"y":420,"wires":[["a012f4cec44fc893"]]},{"id":"3c8a475de28bf2c9","type":"link out","z":"e730aec3bcfbca6b","name":"Login","mode":"link","links":["db15a98aa2355e77","f56ab9657cfd4633"],"x":790,"y":340,"wires":[],"icon":"font-awesome/fa-user-o","l":true},{"id":"f56ab9657cfd4633","type":"link in","z":"e730aec3bcfbca6b","name":"Login","links":["3c8a475de28bf2c9","913a79796f44b556"],"x":90,"y":240,"wires":[["7c2d7be5ee6f2f53"]],"icon":"font-awesome/fa-user-o","l":true},{"id":"e439c70cada5eb1b","type":"link in","z":"e730aec3bcfbca6b","name":"Output","links":["cb85499da4c1afd6","915db6e2c5942c0e"],"x":790,"y":680,"wires":[["611f866eedc883e3"]],"icon":"font-awesome/fa-sign-out","l":true},{"id":"db15a98aa2355e77","type":"link in","z":"e730aec3bcfbca6b","name":"Status","links":["3c8a475de28bf2c9","e922d69c9dd5fc94","913a79796f44b556","0b97fe86c6d1e70b","cb85499da4c1afd6"],"x":790,"y":640,"wires":[["6ac5f87655d99187"]],"icon":"node-red-contrib-home-assistant-websocket/ha-current-state.svg","l":true},{"id":"1e898a111d309171","type":"link out","z":"e730aec3bcfbca6b","name":"Debug","mode":"link","links":["7c250d55b39d7b84"],"x":790,"y":380,"wires":[],"icon":"font-awesome/fa-bug","l":true},{"id":"cbc2243ce0268cf3","type":"link in","z":"e730aec3bcfbca6b","name":"Status Check","links":["291a829236e0d935"],"x":110,"y":420,"wires":[["4bb4d1d323ad3b45"]],"icon":"font-awesome/fa-check-square-o","l":true},{"id":"291a829236e0d935","type":"link out","z":"e730aec3bcfbca6b","name":"Status Check","mode":"link","links":["cbc2243ce0268cf3"],"x":910,"y":140,"wires":[],"icon":"font-awesome/fa-check-square-o","l":true},{"id":"d0022136afd7797c","type":"function","z":"e730aec3bcfbca6b","name":"Error Lookup","func":"const errorCodes = new Map([\n // The codes listed below are common error codes of wrong parameters or failed login for all WebAPIs.\n [\"e100\", \"Unknown error\"],\n [\"e101\", \"No parameter of API, method or version\"],\n [\"e102\", \"The requested API does not exist\"],\n [\"e103\", \"The requested method does not exist\"],\n [\"e104\", \"The requested version does not support the functionality\"],\n [\"e105\", \"The logged in session does not have permission\"],\n [\"e106\", \"Session timeout\"],\n [\"e107\", \"Session interrupted by duplicate login\"],\n [\"e119\", \"SID not found\"],\n\n // The codes listed below are common error codes of file operations for all File Station APIs.\n [\"e400\", \"Invalid parameter of file operation\"],\n [\"e401\", \"Unknown error of file operation\"],\n [\"e402\", \"System is too busy\"],\n [\"e403\", \"Invalid user does this file operation\"],\n [\"e404\", \"Invalid group does this file operation\"],\n [\"e405\", \"Invalid user and group does this file operation\"],\n [\"e406\", \"Can't get user/group information from the account server\"],\n [\"e407\", \"Operation not permitted\"],\n [\"e408\", \"No such file or directory\"],\n [\"e409\", \"Non-supported file system\"],\n [\"e410\", \"Failed to connect internet-based file system (e.g., CIFS)\"],\n [\"e411\", \"Read-only file system\"],\n [\"e412\", \"Filename too long in the non-encrypted file system\"],\n [\"e413\", \"Filename too long in the encrypted file system\"],\n [\"e414\", \"File already exists\"],\n [\"e415\", \"Disk quota exceeded\"],\n [\"e416\", \"No space left on device\"],\n [\"e417\", \"Input/output error\"],\n [\"e418\", \"Illegal name or path\"],\n [\"e419\", \"Illegal file name\"],\n [\"e420\", \"Illegal file name on FAT file system\"],\n [\"e421\", \"Device or resource busy\"],\n [\"e599\", \"No such task of the file operation\"]\n]);\n\nmsg.payload.error.desc = errorCodes.get(`e${msg.payload.error.code}`);\nmsg.status = `Error: ${msg.payload.error.code}: ${msg.payload.error.desc}`;\n\nvar loginReq = null;\nif (msg.payload.error.code == 119) {\n flow.set(\"sid\", undefined)\n loginReq = {\n login: true,\n localFile: flow.get(\"localFile\"),\n remotePath: flow.get(\"remotePath\")\n };\n}\n\nreturn [\n { payload: `Error uploading ${msg.localFile} to ${msg.remotePath}: (${msg.payload.error.code}) ${msg.payload.error.desc}`},\n msg,\n loginReq\n];\n","outputs":3,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":460,"wires":[["cb85499da4c1afd6"],["d0b95f22e117106d"],["913a79796f44b556"]]},{"id":"c6f317449e38131a","type":"function","z":"e730aec3bcfbca6b","name":"Set Values","func":"// OUTPUT ORDER:\n// 1: LOGIN\n// 2: DEBUG\n// 3: OUPTUT / STATUS\n\nmsg.topic = \"Syno-Upload\";\n\nif (msg.operation == \"logout\") {\n msg.status = \"Logout Successful\";\n return [\n null,\n msg,\n {topic: msg.topic, payload: msg.status, status: msg.status}\n ];\n}\nelse if (msg.operation == \"upload\") {\n msg.status = `Uploaded: ${msg.payload.data.file}`;\n if (env.get(\"Output\")) {\n return [\n null,\n msg,\n {topic: msg.topic, payload: msg.status, status: msg.status}\n ];\n }\n else {\n return [\n null,\n msg,\n {topic: msg.topic, status: msg.status}\n ];\n }\n}\nelse if (msg.operation == \"login\") {\n msg.login = undefined;\n msg.operation = \"upload\"\n msg.status = \"Login Successful\";\n\n flow.set(\"sid\", msg.payload.data.sid)\n flow.set(\"created\", new Date())\n msg.localFile = flow.get(\"localFile\");\n msg.remotePath = flow.get(\"remotePath\");\n\n return [\n msg,\n msg,\n {topic: msg.topic, payload: msg.status, status: msg.status}\n ];\n}\nelse {\n // Eh?\n}\n","outputs":3,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":380,"wires":[["3c8a475de28bf2c9"],["1e898a111d309171"],["cb85499da4c1afd6"]]},{"id":"7c250d55b39d7b84","type":"link in","z":"e730aec3bcfbca6b","name":"Debug","links":["1e898a111d309171"],"x":790,"y":720,"wires":[["80933021554f6bf8"]],"icon":"font-awesome/fa-bug","l":true},{"id":"80933021554f6bf8","type":"change","z":"e730aec3bcfbca6b","name":"Clean Up","rules":[{"t":"delete","p":"headers","pt":"msg"},{"t":"delete","p":"method","pt":"msg"},{"t":"delete","p":"url","pt":"msg"},{"t":"delete","p":"responseCookies","pt":"msg"},{"t":"delete","p":"_event","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":940,"y":720,"wires":[[]]},{"id":"9a76f7f2123ece05","type":"catch","z":"e730aec3bcfbca6b","name":"Catch File","scope":["85ab83d56bc2121f"],"uncaught":false,"x":80,"y":760,"wires":[["535f6b1827d59bee","99218225d2165a21"]]},{"id":"535f6b1827d59bee","type":"change","z":"e730aec3bcfbca6b","name":"Set Status","rules":[{"t":"set","p":"status","pt":"msg","to":"error.stack","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":720,"wires":[["e922d69c9dd5fc94"]]},{"id":"1dfbc01638d59c17","type":"link in","z":"e730aec3bcfbca6b","name":"Error","links":["99218225d2165a21","d0b95f22e117106d"],"x":790,"y":760,"wires":[[]],"icon":"node-red/alert.svg","l":true},{"id":"99218225d2165a21","type":"link out","z":"e730aec3bcfbca6b","name":"Error","mode":"link","links":["1dfbc01638d59c17"],"x":430,"y":760,"wires":[],"icon":"node-red/alert.svg","l":true},{"id":"d0b95f22e117106d","type":"link out","z":"e730aec3bcfbca6b","name":"Error","mode":"link","links":["1dfbc01638d59c17"],"x":790,"y":460,"wires":[],"icon":"node-red/alert.svg","l":true},{"id":"cb85499da4c1afd6","type":"link out","z":"e730aec3bcfbca6b","name":"Output / Status","mode":"link","links":["db15a98aa2355e77","e439c70cada5eb1b"],"x":820,"y":420,"wires":[],"icon":"font-awesome/fa-sign-out","l":true},{"id":"e922d69c9dd5fc94","type":"link out","z":"e730aec3bcfbca6b","name":"Status","mode":"link","links":["db15a98aa2355e77"],"x":430,"y":720,"wires":[],"icon":"node-red-contrib-home-assistant-websocket/ha-current-state.svg","l":true},{"id":"913a79796f44b556","type":"link out","z":"e730aec3bcfbca6b","name":"Login","mode":"link","links":["db15a98aa2355e77","f56ab9657cfd4633"],"x":790,"y":500,"wires":[],"icon":"font-awesome/fa-user-o","l":true},{"id":"c67012657adf43eb","type":"comment","z":"e730aec3bcfbca6b","name":"Flow Description","info":"## Operation Check\nThis checks to see if the operation we are performing is `Login`, `Logout` or `Upload`.\nA `Login` is automatically performed if there is no `SID` value or the `Created` date is older than 7 days (Synology default)\n\n### Login\nxxx\n\n### Logout\nxxx\n\n### Upload\nxxx\n\n---\n\n## Status Check\nOnce the above operation has been performed, the result is checked to see if it was successful or not.\n\n### Success: true\nIf successful, then the following outputs are sent:\n\n#### Login\nIf the operation was a `login`, then the script loops back to the start to check if an `upload` is also required.\n\n#### Debug\nSend almost everything out to the debug node.\n\n#### Output / Status\nSend out basic operation action\n\n### Success: false\nxxx","x":100,"y":100,"wires":[]},{"id":"6ac5f87655d99187","type":"change","z":"e730aec3bcfbca6b","name":"Status","rules":[{"t":"set","p":"payload","pt":"msg","to":"status","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":930,"y":640,"wires":[[]]},{"id":"611f866eedc883e3","type":"switch","z":"e730aec3bcfbca6b","name":"Not Null","property":"payload","propertyType":"msg","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":940,"y":680,"wires":[[],[]]}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment