├── LICENSE ├── README.md ├── example ├── main.js └── yourScript.js └── main.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Linus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scriptable Auto Update 2 | This simple wrapper for your [Scriptable](https://scriptable.app) code automatically updates it locally every day. 3 | 4 | ![Loop GIF](https://i.imgur.com/iEFhyVq.gif) 5 | 6 | ## Motivation 7 | I'm a big fan of the [Scriptable](https://scriptable.app) app. It's an excellent tool for creating custom widgets, for example. When sharing my code with other people, I didn't find an easy way to fix bugs or add features afterward. This wrapper aims to solve this challenge. 8 | 9 | ## Features 10 | * Automatically downloads your latest code every day from a URL 11 | * If the update fails, it uses the latest locally available version 12 | * Cleans up old locally saved versions automatically 13 | * Executes your code every time it gets run 14 | 15 | ## Usage 16 | The wrapper will run the main() function of your code every time it gets executed. Therefore it's necessary to export the function at the end of your file like so: 17 | ```javascript 18 | module.exports = { 19 | main 20 | }; 21 | ``` 22 | Copy the content from *main.js* and add your custom values to the `scriptName` & `scriptUrl` variables at the top of the file. Then simply paste it into Scriptable. The wrapper will automatically load and update your code from the URL. 23 | 24 | ## Example 25 | Take a look inside the example folder to see an implementation example. 26 | Want to try it out? Then simply copy the content of *example/main.js* and paste it into Scriptable! 27 | 28 | ## License 29 | This repository is available under the [MIT license](https://opensource.org/licenses/MIT). 30 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | // jshint -W119 2 | 3 | let scriptName = 'AutoUpdateExample'; 4 | let scriptUrl = 'https://raw.githubusercontent.com/linusmimietz/scriptable-auto-update/main/example/yourScript.js'; 5 | 6 | let modulePath = await downloadModule(scriptName, scriptUrl); // jshint ignore:line 7 | if (modulePath != null) { 8 | let importedModule = importModule(modulePath); 9 | await importedModule.main(); // jshint ignore:line 10 | } else { 11 | console.log('Failed to download new module and could not find any local version.'); 12 | } 13 | 14 | async function downloadModule(scriptName, scriptUrl) { 15 | // returns path of latest module version which is accessible 16 | let fm = FileManager.local(); 17 | let scriptPath = module.filename; 18 | let moduleDir = scriptPath.replace(fm.fileName(scriptPath, true), scriptName); 19 | if (fm.fileExists(moduleDir) && !fm.isDirectory(moduleDir)) fm.remove(moduleDir); 20 | if (!fm.fileExists(moduleDir)) fm.createDirectory(moduleDir); 21 | let dayNumber = Math.floor(Date.now() / 1000 / 60 / 60 / 24); 22 | let moduleFilename = dayNumber.toString() + '.js'; 23 | let modulePath = fm.joinPath(moduleDir, moduleFilename); 24 | if (fm.fileExists(modulePath)) { 25 | console.log('Module already downlaoded ' + moduleFilename); 26 | return modulePath; 27 | } else { 28 | let [moduleFiles, moduleLatestFile] = getModuleVersions(scriptName); 29 | console.log('Downloading ' + moduleFilename + ' from URL: ' + scriptUrl); 30 | let req = new Request(scriptUrl); 31 | let moduleJs = await req.load().catch(() => { 32 | return null; 33 | }); 34 | if (moduleJs) { 35 | fm.write(modulePath, moduleJs); 36 | if (moduleFiles != null) { 37 | moduleFiles.map(x => { 38 | fm.remove(fm.joinPath(moduleDir, x)); 39 | }); 40 | } 41 | return modulePath; 42 | } else { 43 | console.log('Failed to download new module. Using latest local version: ' + moduleLatestFile); 44 | return (moduleLatestFile != null) ? fm.joinPath(moduleDir, moduleLatestFile) : null; 45 | } 46 | } 47 | } 48 | 49 | function getModuleVersions(scriptName) { 50 | // returns all saved module versions and latest version of them 51 | let fm = FileManager.local(); 52 | let scriptPath = module.filename; 53 | let moduleDir = scriptPath.replace(fm.fileName(scriptPath, true), scriptName); 54 | let dirContents = fm.listContents(moduleDir); 55 | if (dirContents.length > 0) { 56 | let versions = dirContents.map(x => { 57 | if (x.endsWith('.js')) return parseInt(x.replace('.js', '')); 58 | }); 59 | versions.sort(function(a, b) { 60 | return b - a; 61 | }); 62 | versions = versions.filter(Boolean); 63 | if (versions.length > 0) { 64 | let moduleFiles = versions.map(x => { 65 | return x + '.js'; 66 | }); 67 | moduleLatestFile = versions[0] + '.js'; 68 | return [moduleFiles, moduleLatestFile]; 69 | } 70 | } 71 | return [null, null]; 72 | } 73 | -------------------------------------------------------------------------------- /example/yourScript.js: -------------------------------------------------------------------------------- 1 | // jshint -W119 2 | 3 | async function main() { 4 | let widget = new ListWidget(); 5 | widget.addText('Widget Text'); 6 | let value = (config.runsInWidget) ? Script.setWidget(widget) : await widget.presentSmall(); 7 | Script.complete(); 8 | } 9 | 10 | module.exports = { 11 | main 12 | }; 13 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | // jshint -W119 2 | 3 | let scriptName = 'your_script_name'; 4 | let scriptUrl = 'https://your_script_url'; 5 | 6 | let modulePath = await downloadModule(scriptName, scriptUrl); // jshint ignore:line 7 | if (modulePath != null) { 8 | let importedModule = importModule(modulePath); 9 | await importedModule.main(); // jshint ignore:line 10 | } else { 11 | console.log('Failed to download new module and could not find any local version.'); 12 | } 13 | 14 | async function downloadModule(scriptName, scriptUrl) { 15 | // returns path of latest module version which is accessible 16 | let fm = FileManager.local(); 17 | let scriptPath = module.filename; 18 | let moduleDir = scriptPath.replace(fm.fileName(scriptPath, true), scriptName); 19 | if (fm.fileExists(moduleDir) && !fm.isDirectory(moduleDir)) fm.remove(moduleDir); 20 | if (!fm.fileExists(moduleDir)) fm.createDirectory(moduleDir); 21 | let dayNumber = Math.floor(Date.now() / 1000 / 60 / 60 / 24); 22 | let moduleFilename = dayNumber.toString() + '.js'; 23 | let modulePath = fm.joinPath(moduleDir, moduleFilename); 24 | if (fm.fileExists(modulePath)) { 25 | console.log('Module already downlaoded ' + moduleFilename); 26 | return modulePath; 27 | } else { 28 | let [moduleFiles, moduleLatestFile] = getModuleVersions(scriptName); 29 | console.log('Downloading ' + moduleFilename + ' from URL: ' + scriptUrl); 30 | let req = new Request(scriptUrl); 31 | let moduleJs = await req.load().catch(() => { 32 | return null; 33 | }); 34 | if (moduleJs) { 35 | fm.write(modulePath, moduleJs); 36 | if (moduleFiles != null) { 37 | moduleFiles.map(x => { 38 | fm.remove(fm.joinPath(moduleDir, x)); 39 | }); 40 | } 41 | return modulePath; 42 | } else { 43 | console.log('Failed to download new module. Using latest local version: ' + moduleLatestFile); 44 | return (moduleLatestFile != null) ? fm.joinPath(moduleDir, moduleLatestFile) : null; 45 | } 46 | } 47 | } 48 | 49 | function getModuleVersions(scriptName) { 50 | // returns all saved module versions and latest version of them 51 | let fm = FileManager.local(); 52 | let scriptPath = module.filename; 53 | let moduleDir = scriptPath.replace(fm.fileName(scriptPath, true), scriptName); 54 | let dirContents = fm.listContents(moduleDir); 55 | if (dirContents.length > 0) { 56 | let versions = dirContents.map(x => { 57 | if (x.endsWith('.js')) return parseInt(x.replace('.js', '')); 58 | }); 59 | versions.sort(function(a, b) { 60 | return b - a; 61 | }); 62 | versions = versions.filter(Boolean); 63 | if (versions.length > 0) { 64 | let moduleFiles = versions.map(x => { 65 | return x + '.js'; 66 | }); 67 | moduleLatestFile = versions[0] + '.js'; 68 | return [moduleFiles, moduleLatestFile]; 69 | } 70 | } 71 | return [null, null]; 72 | } 73 | --------------------------------------------------------------------------------