├── .gitignore ├── .travis.yml ├── CONTRIBUTING.markdown ├── Html ├── images.htm └── options.html ├── Images ├── 128x128.png ├── 1400x560.jpg ├── 16x16.png ├── 1x1.png ├── 440x280.jpg ├── 48x48.png ├── 640x400.png └── 920x680.jpg ├── LICENCE ├── PRIVACY ├── README.markdown ├── Scripts ├── app │ ├── app.js │ ├── images.js │ └── shared │ │ ├── filters │ │ └── translateFilter.js │ │ └── services │ │ ├── anchorScroll.service.js │ │ ├── chrome.service.js │ │ └── fuskr.service.js ├── background.js ├── fuskr.js └── options.js ├── Styles └── styles.scss ├── TODO.md ├── Tests ├── fuskr │ ├── Fuskr.GetLinks.spec.js │ ├── Fuskr.IsFuskable.spec.js │ └── Fuskr.spec.js └── lib │ └── angular-mocks.js ├── _config.yml ├── _locales └── en_GB │ └── messages.json ├── gruntfile.js ├── index.html ├── manifest.json ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .sass-cache 4 | fuskr-*.zip 5 | 6 | # OS generated files # 7 | ###################### 8 | .DS_Store 9 | .DS_Store? 10 | ._* 11 | .Spotlight-V100 12 | .Trashes 13 | ehthumbs.db 14 | Thumbs.db 15 | 16 | #Editors 17 | .vs/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: node_js 4 | 5 | node_js: 6 | - "node" 7 | 8 | before_install: 9 | - npm install -g grunt-cli 10 | 11 | install: npm install 12 | before_script: grunt travis:build --force 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.markdown: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Running Fuskr locally 4 | We now use grunt to manage the project assets. Firstly, clone Fuskr and make sure you checkout the master branch. 5 | 6 | Run `npm install` to download all of the tooling followed by `grunt build` to generate a release folder. 7 | 8 | ### Loading unpacked extension in chrome 9 | When loading the extension into Chrome, be sure to choose the `/dist` folder as the extension directory. 10 | 11 | You shouldn't have to reload the extension when you make file changes (unless you're updating the manifest). 12 | 13 | ## Grunt tasks 14 | There are a lot of tasks but the main one would be just running `grunt`. 15 | 16 | The `default` task will initially build the project and then watch the files for any changes. If you update any files then it will rebuild and run any necessary tasks (i.e. sass will be run for any sass changes, a rebuild will be done for JS files with tests executed). 17 | 18 | `grunt lint` - Only run the linters 19 | `grunt compile` - Only run what's needed to build the dist (no tests/lints) 20 | `grunt build` - Build a full dist folder with any debugging information (i.e. sourcemaps) 21 | `grunt release` - Do everything. Run all lints, tests, compile tasks, etc. Clean the dist folder of unnecessary files and build a zip file ready for release 22 | 23 | ## Linting 24 | For JavaScript, JSCS and JSHint are both used to check the non-vendor code. Sass gets checked by sass-lint. 25 | 26 | All 3 linters have their configuration files in the root of the repository. 27 | 28 | ## Testing 29 | Tests are run automatically on build. If you aren't using the grunt watcher and just want a quick test on the non-release version of the code, then run `grunt test`. This will execute the tests and can be useful to debug tests run on the release files. 30 | 31 | ### Writing tests 32 | The tests that will be run will use the jasmine framework. Any file in the `Tests` folder ending in `*.spec.js` will be run. 33 | -------------------------------------------------------------------------------- /Html/images.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fuskr 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 | 18 | 24 | 38 | 39 | 40 |
41 | 42 |
43 |
44 | 45 | {{ image.filename }} 46 | 47 | 48 |
49 | {{ image.url }} 50 | 51 | 57 |
58 |
59 |
60 | 61 |
62 |

{{ vm.model.originalUrl }}

63 | 69 | 83 |
84 |
85 | 86 |
87 | 88 |
89 | 90 |
91 | 92 |
93 |
94 | 95 |
96 |
97 | 98 |
99 |
100 | 101 |
102 |
103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /Html/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fuskr Options 6 | 7 | 8 |
9 | 13 |

14 |
15 |
16 | 20 |

21 |
22 |
23 | 27 |

28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Images/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanAtkinson/Fuskr/d9e11f6c404708003d1c71c876dacf7e9a3b7bc0/Images/128x128.png -------------------------------------------------------------------------------- /Images/1400x560.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanAtkinson/Fuskr/d9e11f6c404708003d1c71c876dacf7e9a3b7bc0/Images/1400x560.jpg -------------------------------------------------------------------------------- /Images/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanAtkinson/Fuskr/d9e11f6c404708003d1c71c876dacf7e9a3b7bc0/Images/16x16.png -------------------------------------------------------------------------------- /Images/1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanAtkinson/Fuskr/d9e11f6c404708003d1c71c876dacf7e9a3b7bc0/Images/1x1.png -------------------------------------------------------------------------------- /Images/440x280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanAtkinson/Fuskr/d9e11f6c404708003d1c71c876dacf7e9a3b7bc0/Images/440x280.jpg -------------------------------------------------------------------------------- /Images/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanAtkinson/Fuskr/d9e11f6c404708003d1c71c876dacf7e9a3b7bc0/Images/48x48.png -------------------------------------------------------------------------------- /Images/640x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanAtkinson/Fuskr/d9e11f6c404708003d1c71c876dacf7e9a3b7bc0/Images/640x400.png -------------------------------------------------------------------------------- /Images/920x680.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanAtkinson/Fuskr/d9e11f6c404708003d1c71c876dacf7e9a3b7bc0/Images/920x680.jpg -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /PRIVACY: -------------------------------------------------------------------------------- 1 | WE DO NOT TRACK YOUR USE OF THE FUSKR EXTENSION. 2 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | [![Join the chat at https://gitter.im/DanAtkinson/Fuskr](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/DanAtkinson/Fuskr?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | [![Build Status](https://travis-ci.org/DanAtkinson/Fuskr.svg?branch=master)](https://travis-ci.org/DanAtkinson/Fuskr) 3 | 4 | > **IMPORTANT! PLEASE READ!** 5 | > ========================== 6 | > 7 | > This extension was unpublished from the Google Chrome extensions webstore in February 2020 as Google decided that some of the permissions used in this extension (such as opening new tabs, and being able to download images as a zip) were not necessary for an image gallery extension that supported downloads. 8 | > 9 | > I fundamentally disagreed with their decision and therefore preemptively unpublished the extension. At this time I will not be re-publishing it without having to make major changes to the underlying source code. For one, it was re-written for Angular.js which I no longer actively use (I have moved to Angular professionally). 10 | > 11 | > Please see a more detailed explanation here:
12 | > https://github.com/DanAtkinson/Fuskr/issues/44#issuecomment-644381586 13 | > 14 | > If you wish to continue using the extension (**not supported!**) and feel that you are technically literate, you can follow the below steps, somewhat covered [here](https://developer.chrome.com/docs/extensions/mv2/getstarted/): 15 | > 1. Clone/download this repository to your local machine. 16 | > 1. Open the Extension Management page by navigating to chrome://extensions.
17 | > _The Extension Management page can also be opened by clicking on the Chrome menu, hovering over More Tools then selecting Extensions._ 18 | > 1. Enable Developer Mode by clicking the toggle switch next to Developer mode. 19 | > 1. Click the LOAD UNPACKED button and select the extension directory. 20 | 21 | How to use 22 | ========== 23 | 24 | 1. Go ahead and install Fuskr from the [Google Chrome extensions gallery](https://chrome.google.com/webstore/detail/fuskr/glieaboaghdnlglpkekghloldikefofo). 25 | 1. On your desired image, right click and choose 'Fusk'. 26 | 1. Choose which direction: 27 | * '+/-' will return a gallery with images that come before and after it. 28 | * '+' will return a gallery with images that only come after it. 29 | * '-' will return a gallery with images that only come before it. 30 | 1. Now choose how large you want your gallery to be - 10/20/50/100/200/500 or 'Other' (you choose!). 31 | 1. Your gallery will appear in the tab next to your current one. If an image isn't returned (404 or some other error), it will be hidden from view, but you can toggle that too! 32 | 33 | 34 | Version History 35 | =============== 36 | 37 | * 4.0.75 38 | * Omnibox and history should now be working correctly again. 39 | * 4.0.61 40 | * Fix issues with the context menus not working correctly in Chrome. Firefox was unaffected but the solution is cross-browser compatible. 41 | * 4.0.20 42 | * Create dark mode option. 43 | * Tidy up code and improve some speed issues, specifically around context menu creation. 44 | * New version numbering. Minor version is incremented and we also include a longer version name indicating the build date/time. 45 | * 3.2 46 | * Download images as a zip file and retain the structure if it's a nested fusk! 47 | * Options page change to support Chrome's preferred options layout. 48 | * Improvements to the way many urls are handled. 49 | * 3.1 50 | * Fixed an issue with the fusk option not showing on links. 51 | * 3.0 52 | * Application templating rewritten using [AngularJS](https://angularjs.org/). 53 | * 2.7 54 | * Reduced the permissions level greatly. Previously, Fuskr required that you give us access to all websites and all browsing activity, but we only care about your current tab. Google Chrome now has that ability, so we’ve reduced the permissions needed accordingly! 55 | * Fixed an annoying bug where Fuskr wouldn’t work on some links that didn’t have numbers, and wouldn’t revert to the image url. 56 | * 2.6 57 | * You can now download images directly from the gallery page! 58 | * You can now view the page in a slideshow! 59 | * Beginning to internationalize (l18n). If you want to help, let us know! 60 | * Updated to jQuery and jQuery UI. 61 | * 2.5 62 | * Updated to jQuery v2.1.3 and jQuery UI 1.11.2. 63 | * 2.4 64 | * Updated to jQuery v2 and jQuery UI 1.10.3. 65 | * Minor styling changes to the image gallery page. 66 | * 2.3 67 | * Fixed some template issues and added more information and credits to the options page. 68 | * 2.2 69 | * Added the ability to create a fusk by typing 'fuskr' in the omnibox, followed by your fusk url! 70 | * Updated libraries used by Fuskr. 71 | * 2.1 72 | * Updated Fuskr with new icon/image goodness kindly provided by Richard Stelmach of Creative Binge! 73 | * 2.0 74 | * Alphabetical fusking is now possible! You can now do fusks such as https://example.com/path/file/[a-z].jpg or even https://domain.com/path/file/[a-z]and[c-g]and[j-m].jpg 75 | * Changes to the options page to make it much cleaner. 76 | * 1.9.1 77 | * Some changes to the application in order to take into account recent security update to Google Chrome which will be enforced shortly. 78 | * 1.9 79 | * Fuskr wasn't working on Macs. 80 | * 1.8 81 | * Change of name to Fuskr. 82 | * Jonathon Bolster has put a lot of work into making Fuskr modular, and there are now a few unit tests around to make sure everything's hunky dory! 83 | * Fixed some issues with previous/next functionality not working when there are images missing. It also scrolls smoothly as well. 84 | * 1.7 85 | * Added linkage below images. 86 | * 1.6 87 | * A few bug fixes. Incognito works properly now, but only in v9 as there are some Chrome bugs outstanding. 88 | * 1.5 89 | * Fixing a bug with the '+' icon showing up unnecessarily. 90 | * Galleries created in incognito mode are no longer stored. 91 | * Added the ability to scale images to the current window size. 92 | * Added some information about the current gallery (number of images and broken images). 93 | * 1.4 94 | * Clicking a created image will jump the user to the next one. Manual fusks are a little more difficult so maybe that's one for later... 95 | * Added an options page! Currently the only two options are below. 96 | * Added recent fusks history. This allows you to keep track of and, if desired, go back to a previous one that you may have closed. This feature will only store 10 at maximum, can be disabled completely, or the history can be wiped. 97 | * Added an option for opening a gallery in the foreground. 98 | * 1.3 99 | * Fixed a bug where having a nested gallery meant that the images were returned in the wrong order. - Kudos Jonathon! 100 | * Fixed an issue where the ChromeFusk option appears, even when the image is not 'fusk-able' - Kudos Jonathon! 101 | * Fixed an issue where choosing a manual gallery and not entering a url would still try to create a new tab. Kudos Jonathon! 102 | * Provided an 'option' for having the new gallery show on creation, or open in the background (default is open in the foreground). 103 | * 1.2 104 | * Fixed a bug where the horizontal scrollbar was not visible for galleries where the images were bigger than the page. 105 | * Added the ability to use create a gallery based on a thumbnail, which will go to the linked image instead. 106 | * 1.1 107 | * About 5 minutes after I released 1.0, I realised that Chrome had finally allowed extensions to create context menus, so I ripped my application apart and rebuilt it in thirty minutes to use the context menus. 108 | * 1.0 109 | * Initial release. 110 | -------------------------------------------------------------------------------- /Scripts/app/app.js: -------------------------------------------------------------------------------- 1 | /* globals angular */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | angular 7 | .module('fuskrApp', ['ngSanitize']) 8 | .run(['$rootScope', '$filter', function ($rootScope, $filter) { 9 | $rootScope.manifestName = $filter('translate')('ManifestName', ''); 10 | $rootScope.manifestLanguage = $filter('translate')('ManifestLanguage', ''); 11 | }]); 12 | 13 | }()); 14 | -------------------------------------------------------------------------------- /Scripts/app/images.js: -------------------------------------------------------------------------------- 1 | /* globals angular, JSZip, saveAs, prompt */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | angular 7 | .module('fuskrApp') 8 | .controller('ImageListController', ['$document', '$scope', '$location', '$timeout', '$filter', 'anchorScrollService', 'fuskrService', 'chromeService', imageListController]); 9 | 10 | function imageListController($document, $scope, $location, $timeout, $filter, anchorScrollService, fuskrService, chromeService) { 11 | /* jshint validthis: true */ 12 | 13 | var vm = this; 14 | 15 | //Functions 16 | vm.totalSuccess = totalSuccess; 17 | vm.totalFailed = totalFailed; 18 | vm.isFinishedLoading = isFinishedLoading; 19 | vm.scrollToAnchor = scrollToAnchor; 20 | vm.shouldDisplayImage = shouldDisplayImage; 21 | vm.pluraliseForImages = pluraliseForImages; 22 | vm.downloadZip = downloadZip; 23 | vm.fuskUrlChanged = fuskUrlChanged; 24 | vm.changeFusk = changeFusk; 25 | vm.buildFusk = buildFusk; 26 | 27 | //Initialise 28 | $timeout(function () { 29 | var url = $location.hash(); 30 | vm.model = { 31 | images: [], 32 | imageUrls: '', 33 | filteredImages: [], 34 | originalUrl: url, 35 | fuskUrl: url, 36 | showViewer: false, 37 | showImageUrls: false, 38 | showBrokenImages: false, 39 | fuskUrlDifferent: false, 40 | selectedImageId: 0, 41 | imageDisplay: 'images-fit-on-page', 42 | lightOrDark: 'lightMode' 43 | }; 44 | 45 | $document[0].title += ' - ' + vm.model.originalUrl; 46 | 47 | chromeService.getDarkMode().then(function(response) { 48 | if (response) { 49 | vm.model.lightOrDark = 'darkMode'; 50 | } 51 | }); 52 | 53 | buildFusk(); 54 | 55 | $document.bind('keydown', keyboardBinding); 56 | }()); 57 | 58 | // Lambda functions 59 | function totalSuccess() { 60 | return vm.model.images.map(function (x) { 61 | return x.loaded && x.success ? 1 : 0; 62 | }).reduce(function (a, b) { 63 | return a + b; 64 | }, 0); 65 | } 66 | 67 | function totalFailed() { 68 | return vm.model.images.map(function (x) { 69 | return x.loaded && !x.success ? 1 : 0; 70 | }).reduce(function (a, b) { 71 | return a + b; 72 | }, 0); 73 | } 74 | 75 | function isFinishedLoading() { 76 | return vm.model.images.every(function (x) { return x.loaded; }); 77 | } 78 | 79 | function scrollToAnchor($event, htmlElementId, itemId) { 80 | if (typeof $event.preventDefault !== 'undefined') { 81 | $event.preventDefault(); 82 | } 83 | 84 | if (itemId < 0 || !itemId) { 85 | itemId = 0; 86 | } 87 | 88 | if (itemId > vm.model.filteredImages.length - 1) { 89 | itemId = vm.model.filteredImages.length - 1; 90 | } 91 | 92 | vm.model.selectedImageId = itemId; 93 | 94 | if (htmlElementId) { 95 | anchorScrollService.scrollTo(htmlElementId); 96 | } 97 | } 98 | 99 | function shouldDisplayImage() { 100 | return function (img) { 101 | return !img.loaded || img.success || vm.model.showBrokenImages; 102 | }; 103 | } 104 | 105 | function pluraliseForImages(key) { 106 | return { 107 | 0: $filter('translate')(key), 108 | one: $filter('translate')(key), 109 | other: $filter('translate')(key) 110 | }; 111 | } 112 | 113 | function keyboardBinding(e) { 114 | //If the focus is the textbox, do not do anything here. 115 | if (document.activeElement.className.includes('fusk-url-textbox')) { 116 | return true; 117 | } 118 | 119 | switch (e.which) { 120 | case 37: 121 | /* Left */ 122 | e.preventDefault(); 123 | $scope.$apply(function () { 124 | scrollToAnchor(e, 'image' + (vm.model.selectedImageId - 1), vm.model.selectedImageId - 1); 125 | }); 126 | break; 127 | case 39: 128 | /* Right */ 129 | e.preventDefault(); 130 | $scope.$apply(function () { 131 | scrollToAnchor(e, 'image' + (vm.model.selectedImageId + 1), vm.model.selectedImageId + 1); 132 | }); 133 | break; 134 | case 27: 135 | /* Escape */ 136 | e.preventDefault(); 137 | $scope.$apply(function () { 138 | vm.model.showViewer = !vm.model.showViewer; 139 | }); 140 | break; 141 | } 142 | } 143 | 144 | function downloadZip() { 145 | var zip, validImages, explodedPaths, shortenedPathImages, imageUrlsForTxtFile = '', index = 0; 146 | 147 | validImages = vm.model.images.filter(function (x) { 148 | return x.loaded && x.success; 149 | }); 150 | 151 | // Split each URL into path components 152 | explodedPaths = validImages.map(function (x) { 153 | return { 154 | url: x.url, 155 | data: x.data, 156 | urlArray: x.url.split('/').map(safeFileName) 157 | }; 158 | }); 159 | 160 | // Check that all URL components at an index are the same, to determine the root folder 161 | function checkIfAllItemsAtIndexEqual(x) { 162 | var pass, allSame; 163 | pass = explodedPaths.map(function (r) { 164 | return r.urlArray.length > x ? r.urlArray[index] : null; 165 | }); 166 | 167 | allSame = pass.every(function (r) { 168 | return r === pass[0]; 169 | }); 170 | 171 | return allSame; 172 | } 173 | 174 | for (index = 0; index < validImages.length; index++) { 175 | if (checkIfAllItemsAtIndexEqual(index) === false) { 176 | break; 177 | } 178 | } 179 | 180 | // Trim the URL up until the common folder and add it to zip text file. 181 | shortenedPathImages = explodedPaths.map(function (r) { 182 | imageUrlsForTxtFile += r.url + '\r\n'; 183 | return { 184 | data: r.data, 185 | url: r.urlArray.slice(index).join('/') 186 | }; 187 | }); 188 | 189 | function addExtensionIfNeeded(filename, blobData) { 190 | var expected, types; 191 | 192 | types = { 193 | 'image/gif': ['gif'], 194 | 'image/jpeg': ['jpeg', 'jpg'], 195 | 'image/png': ['png'], 196 | 'image/tiff': ['tif', 'tiff'], 197 | 'image/vnd.wap.wbmp': ['wbmp'], 198 | 'image/x-icon': ['ico'], 199 | 'image/x-jng': ['jng'], 200 | 'image/x-ms-bmp': ['bmp'], 201 | 'image/svg+xml': ['svg'], 202 | 'image/webp': ['webp'] 203 | }; 204 | 205 | // Get expected extension 206 | expected = types[blobData.type]; 207 | if (expected) { 208 | // Iterate through expected types 209 | // to check if it matches any on the list 210 | var hasMatch = expected.some(function (x) { 211 | return filename.match(new RegExp('\\.' + x + '$')); 212 | }); 213 | 214 | if (!hasMatch) { 215 | // If no extension matches, add one 216 | return filename + '.' + expected[0]; 217 | } 218 | } 219 | 220 | // If an acceptable extension or no known 221 | // mimetype, just return 222 | return filename; 223 | } 224 | 225 | function safeFileName(str) { 226 | return str.replace(/[^a-zA-Z0-9_\-.]/g, '_'); 227 | } 228 | 229 | zip = new JSZip(); 230 | zip.file('Fuskr.txt', 'These images were downloaded using Fuskr.\r\n\r\nFusk Url: ' + vm.model.originalUrl + '\r\n\r\n\r\nUrls:\r\n' + imageUrlsForTxtFile); 231 | 232 | // Add an extension for known file types and remove any trailing slash 233 | shortenedPathImages.forEach(function (img) { 234 | var fileName = addExtensionIfNeeded(img.url.replace(/\/$/, ''), img.data); 235 | zip.file(fileName, img.data, { blob: true }); 236 | }); 237 | 238 | zip 239 | .generateAsync({ type: 'blob' }) 240 | .then(function (content) { 241 | var zipFilename = prompt('Please enter the name of the generated zip file to download.\nChoosing cancel will abort the zip file download.', 'fuskr.zip'); 242 | 243 | if (zipFilename === '') { 244 | //User has cancelled out. 245 | return; 246 | } 247 | 248 | //Ensure correct extension. 249 | if (!zipFilename.toLowerCase().endsWith('.zip')) { 250 | zipFilename += '.zip'; 251 | } 252 | 253 | saveAs(content, zipFilename); 254 | }); 255 | } 256 | 257 | function fuskUrlChanged() { 258 | var manualCheck, alphabetCheck; 259 | 260 | vm.model.fuskUrlDifferent = false; 261 | manualCheck = /\[\d+(-\d+)?\]/; 262 | alphabetCheck = /\[\w(-\w)?\]/; 263 | 264 | //Check whether it's different. Don't try and be smart by doing case insensitivity. 265 | if (vm.model.fuskUrl === vm.model.originalUrl) { 266 | return false; 267 | } 268 | 269 | //We should validate the url. 270 | if (manualCheck.exec(vm.model.fuskUrl) === null && alphabetCheck.exec(vm.model.fuskUrl) === null) { 271 | return false; 272 | } 273 | 274 | //Only if the fusk is valid and is different to the original do we allow the user to resubmit the fusk. 275 | vm.model.fuskUrlDifferent = true; 276 | } 277 | 278 | function changeFusk() { 279 | //Update the url hash. 280 | $location.hash(vm.model.fuskUrl); 281 | 282 | //Execute the new fusk. 283 | buildFusk(); 284 | } 285 | 286 | function buildFusk() { 287 | var urls = '', images = fuskrService.getLinks(vm.model.fuskUrl); 288 | vm.model.images = images; 289 | vm.model.filteredImages = images; 290 | 291 | if (images.length > 200) { 292 | //We should warn the user about a fusk which could potentially kick their computer's arse. 293 | //The user should be offered the chance to disable this warning. 294 | //The user can either: 295 | // A: Cancel the fusk, which would then close the tab. 296 | // B: Continue running the fusk as normal. 297 | // C: Ask us to process the fusk in batches of 100 images at a time. 298 | } 299 | 300 | //Reset vars. 301 | vm.model.selectedImageId = 0; 302 | vm.model.fuskUrlDifferent = false; 303 | vm.model.originalUrl = vm.model.fuskUrl; 304 | 305 | vm.model.images.map(function(x) { 306 | urls += x.url + '\n'; 307 | }); 308 | vm.model.imageUrls = urls; 309 | } 310 | } 311 | 312 | }()); 313 | -------------------------------------------------------------------------------- /Scripts/app/shared/filters/translateFilter.js: -------------------------------------------------------------------------------- 1 | /* globals angular */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | angular 7 | .module('fuskrApp') 8 | .filter('translate', translateFilter); 9 | 10 | translateFilter.$inject = ['chromeService']; 11 | 12 | function translateFilter(chromeService) { 13 | return function (name, base) { 14 | base = typeof (base) === 'undefined' ? 'Images_' : (base === '' ? '' : base + '_'); 15 | return chromeService.getMessage(base + name); 16 | }; 17 | } 18 | 19 | }()); 20 | -------------------------------------------------------------------------------- /Scripts/app/shared/services/anchorScroll.service.js: -------------------------------------------------------------------------------- 1 | /* globals self, document, setTimeout, angular */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | angular 7 | .module('fuskrApp') 8 | .factory('anchorScrollService', AnchorScrollService); 9 | 10 | function AnchorScrollService() { 11 | return { 12 | scrollTo: scrollTo 13 | }; 14 | 15 | function scrollTo(elementId) { 16 | // This scrolling function is from http://www.itnewb.com/tutorial/Creating-the-Smooth-Scroll-Effect-with-JavaScript 17 | var i, elm, startY, stopY, distance, speed, step, leapY, timer; 18 | 19 | //Check if the required element actually exists. 20 | elm = document.getElementById(elementId); 21 | if (elm === null) { 22 | return; 23 | } 24 | 25 | i = 0; 26 | startY = currentYPosition(); 27 | stopY = elmYPosition(elm); 28 | distance = stopY > startY ? stopY - startY : startY - stopY; 29 | if (distance < 100) { 30 | scrollTo(0, stopY); 31 | return; 32 | } 33 | speed = Math.round(distance / 100); 34 | 35 | if (speed >= 20) { 36 | speed = 20; 37 | } 38 | 39 | step = Math.round(distance / 25); 40 | leapY = stopY > startY ? startY + step : startY - step; 41 | timer = 0; 42 | 43 | if (stopY > startY) { 44 | for (i = startY; i < stopY; i += step) { 45 | setTimeout('window.scrollTo(0, ' + leapY + ')', timer * speed); 46 | leapY += step; 47 | if (leapY > stopY) { 48 | leapY = stopY; 49 | } 50 | timer++; 51 | } 52 | return; 53 | } 54 | 55 | for (i = startY; i > stopY; i -= step) { 56 | setTimeout('window.scrollTo(0, ' + leapY + ')', timer * speed); 57 | leapY -= step; 58 | if (leapY < stopY) { 59 | leapY = stopY; 60 | } 61 | timer++; 62 | } 63 | 64 | function currentYPosition() { 65 | return self.pageYOffset; 66 | } 67 | 68 | function elmYPosition(elm) { 69 | var node, y = -1; 70 | y = elm.offsetTop; 71 | node = elm; 72 | while (node.offsetParent && node.offsetParent !== document.body) { 73 | node = node.offsetParent; 74 | y += node.offsetTop; 75 | } 76 | return y; 77 | } 78 | 79 | } 80 | } 81 | }()); 82 | -------------------------------------------------------------------------------- /Scripts/app/shared/services/chrome.service.js: -------------------------------------------------------------------------------- 1 | /* globals chrome, angular */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | angular 7 | .module('fuskrApp') 8 | .factory('chromeService', ChromeService); 9 | 10 | function ChromeService() { 11 | return { 12 | getMessage: getMessage, 13 | getDarkMode: getDarkMode 14 | }; 15 | 16 | function getMessage(name) { 17 | return chrome.i18n.getMessage(name); 18 | } 19 | 20 | function getDarkMode () { 21 | var darkModePromise = new Promise(function (resolve, reject) { 22 | var result = false; 23 | try { 24 | chrome.storage.sync.get(['darkMode'], function (response) { 25 | if (typeof response !== 'undefined' && response.hasOwnProperty('darkMode')) { 26 | result = JSON.parse(response.darkMode.toString().toLowerCase()); 27 | resolve(result); 28 | } 29 | }); 30 | } catch (err) { 31 | reject(err); 32 | } 33 | }); 34 | 35 | return darkModePromise; 36 | } 37 | } 38 | }()); 39 | -------------------------------------------------------------------------------- /Scripts/app/shared/services/fuskr.service.js: -------------------------------------------------------------------------------- 1 | /* globals Fuskr, URL, angular */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | angular 7 | .module('fuskrApp') 8 | .factory('fuskrService', FuskrService); 9 | 10 | FuskrService.$inject = ['$http']; 11 | 12 | function FuskrService($http) { 13 | 14 | var disallowedTypes = { 15 | 'text/html': ['html'], 16 | 'text/plain': ['plain'] 17 | }; 18 | 19 | return { 20 | getLinks: getLinks 21 | }; 22 | 23 | function getLinks(url) { 24 | var mappedLinks = [], 25 | fuskLinks = Fuskr.GetLinks(url); 26 | 27 | mappedLinks.originalUrl = url; 28 | mappedLinks.totalLoaded = 0; 29 | mappedLinks.totalSuccess = 0; 30 | mappedLinks.totalFailed = 0; 31 | mappedLinks.finishedLoading = false; 32 | 33 | fuskLinks.forEach(function (url, i) { 34 | var imageItem = { 35 | url: url, 36 | loaded: false, 37 | success: false, 38 | active: (i === 0), 39 | src: null, 40 | contentType: '', 41 | filename: Fuskr.GetImageFilename(url) 42 | }; 43 | 44 | $http({ 45 | url: imageItem.url, 46 | responseType: 'blob', 47 | method: 'GET', 48 | }).then(function (response) { 49 | imageItem.success = (response.status >= 200 && response.status < 400); 50 | imageItem.loaded = true; 51 | imageItem.src = (response.data) ? URL.createObjectURL(response.data) : null; 52 | imageItem.data = response.data; 53 | imageItem.contentType = (response.data) ? response.data.type : ''; 54 | 55 | mappedLinks.totalLoaded = mappedLinks.totalLoaded + 1; 56 | mappedLinks.finishedLoading = mappedLinks.totalLoaded === fuskLinks.length; 57 | 58 | //Perform checks on successful images, such as its content type. 59 | if (disallowedTypes[imageItem.contentType]) { 60 | imageItem.success = false; 61 | mappedLinks.totalFailed = mappedLinks.totalFailed + 1; 62 | } else { 63 | mappedLinks.totalSuccess = mappedLinks.totalSuccess + 1; 64 | } 65 | }, function (response) { 66 | imageItem.success = false; 67 | imageItem.loaded = true; 68 | imageItem.contentType = (response.data) ? response.data.type : ''; 69 | 70 | mappedLinks.totalLoaded = mappedLinks.totalLoaded + 1; 71 | mappedLinks.totalFailed = mappedLinks.totalFailed + 1; 72 | mappedLinks.finishedLoading = mappedLinks.totalLoaded === fuskLinks.length; 73 | }); 74 | 75 | mappedLinks.push(imageItem); 76 | }); 77 | 78 | return mappedLinks; 79 | } 80 | } 81 | }()); 82 | -------------------------------------------------------------------------------- /Scripts/background.js: -------------------------------------------------------------------------------- 1 | /* globals chrome, alert, prompt, Fuskr */ 2 | (function () { 3 | 4 | var i = 0, 5 | recentId = 0, 6 | parentId = -1, 7 | historyIds = [], 8 | targetUrls = (function () { 9 | var targetUrls = []; 10 | //Target urls tell Chrome what urls are acceptable. 11 | //Create regex patterns to match only urls that contain numbers 12 | for (var i = 0; i <= 9; i++) { 13 | targetUrls.push('*://*/*' + i + '*'); 14 | } 15 | return targetUrls; 16 | }()), 17 | options = { 18 | darkMode: false, 19 | keepFusks: true, 20 | openInForeground: true, 21 | history: [], 22 | }; 23 | 24 | function l18nify(name) { 25 | return chrome.i18n.getMessage('Application_' + name); 26 | } 27 | 28 | function createRecentMenu(historyArray) { 29 | var historyId; 30 | 31 | if (recentId !== 0) { 32 | chrome.contextMenus.remove(recentId); 33 | recentId = 0; 34 | } 35 | 36 | historyIds = []; 37 | 38 | if (historyArray === null || historyArray.length === 0) { 39 | return false; 40 | } 41 | 42 | recentId = createContextMenu({ Id: 'FuskrRecent', ParentId: 'FuskrContextMenu', Title: l18nify('ContextMenu_Recent') }); 43 | 44 | for (i = 0; i < historyArray.length; i++) { 45 | if (historyArray[i] !== '') { 46 | //Add the menu 47 | historyId = createContextMenu({ Id: ('FuskrHistory_' + i), ParentId: recentId, Title: historyArray[i] }); 48 | historyIds.push([historyId, historyArray[i]]); 49 | } 50 | } 51 | 52 | if (historyArray.length > 0) { 53 | createContextMenu({ Id: 'FuskrSeparator3', ParentId: recentId, ItemType: 'separator' }); 54 | createContextMenu({ Id: 'FuskrClearHistory', ParentId: recentId, Title: l18nify('ContextMenu_ClearRecentActivity') }); 55 | } 56 | } 57 | 58 | function clearRecentOnClick() { 59 | createRecentMenu([]); 60 | } 61 | 62 | function optionsOnClick() { 63 | chrome.runtime.openOptionsPage(); 64 | } 65 | 66 | function manualOnClick(info, tab) { 67 | var imageUrl, manualCheck, alphabetCheck, url; 68 | imageUrl = info.linkUrl !== null ? info.linkUrl : info.srcUrl; 69 | manualCheck = /\[\d+-\d+\]/; 70 | alphabetCheck = /\[\w-\w\]/; 71 | url = prompt(l18nify('Prompt_PleaseEnterTheUrl'), imageUrl); 72 | 73 | if (url) { 74 | if (manualCheck.exec(url) === null && alphabetCheck.exec(url) === null) { 75 | alert(l18nify('Prompt_NotAValidFusk')); 76 | return false; 77 | } 78 | 79 | createTab(url, tab); 80 | } 81 | } 82 | 83 | /* 84 | function infiniteOnClick(info, tab) { 85 | var url = '', 86 | digitsCheck, 87 | findDigitsRegexp = /^(.*?)(\d+)([^\d]*)$/; 88 | 89 | if (info.linkUrl !== null) { 90 | digitsCheck = findDigitsRegexp.exec(info.linkUrl); 91 | if (digitsCheck !== null) { 92 | url = info.linkUrl; 93 | } 94 | } 95 | 96 | if (url === '' && info.srcUrl !== null) { 97 | digitsCheck = findDigitsRegexp.exec(info.srcUrl); 98 | if (digitsCheck !== null) { 99 | url = info.srcUrl; 100 | } 101 | } 102 | 103 | if (url === null || typeof url === 'undefined' || url === '') { 104 | alert(l18nify('Prompt_NotAValidFusk')); 105 | return; 106 | } 107 | 108 | if (digitsCheck && digitsCheck.length === 4) { 109 | //Should turn something like https://example.com/images/01/01.jpg into https://example.com/images/01/[01-01].jpg 110 | //Then we need to perform a check to see whether the fusk url creates a single element array. 111 | url = digitsCheck[1] + '[' + digitsCheck[2] + '-' + digitsCheck[2] + ']' + digitsCheck[3]; 112 | createTab(url, tab); 113 | } 114 | } 115 | */ 116 | 117 | function createFromSelectionOnClick(info, tab) { 118 | var url, manualCheck; 119 | url = info.selectionText; 120 | manualCheck = /\[\d+-\d+\]/; 121 | 122 | if (manualCheck.exec(url) === null) { 123 | alert(l18nify('Prompt_NotAValidFusk')); 124 | return false; 125 | } 126 | 127 | createTab(url, tab); 128 | } 129 | 130 | function recentOnClick(info, tab) { 131 | var historyIndex = info.menuItemId.match(/^FuskrHistory_(\d+)$/); 132 | if (!historyIds.length || historyIndex === null || historyIndex.length !== 2) { 133 | alert(l18nify('Prompt_NotAValidFusk')); 134 | return; 135 | } 136 | 137 | historyIndex = parseInt(historyIndex[1], 10); 138 | if (isNaN(historyIndex) || historyIndex < 0) { 139 | alert(l18nify('Prompt_NotAValidFusk')); 140 | return; 141 | } 142 | 143 | if (historyIds.length <= historyIndex) { 144 | alert(l18nify('Prompt_NotAValidFusk')); 145 | return; 146 | } 147 | 148 | createTab(historyIds[historyIndex][1], tab); 149 | } 150 | 151 | function createTab(url, tab) { 152 | if (typeof url === 'undefined' || url === null || url.length === 0) { 153 | return; 154 | } 155 | 156 | addUrlToHistory(url, tab); 157 | 158 | chrome.tabs.create({ 159 | windowId: tab.windowId, 160 | url: '/Html/images.htm#' + url, 161 | index: (tab.index + 1), 162 | active: options.openInForeground 163 | }); 164 | } 165 | 166 | function addUrlToHistory(url, tab) { 167 | var newHistory = []; 168 | 169 | if (tab.incognito || options.keepRecentFusks === false) { 170 | //As a rule, do not store incognito data in history. 171 | return false; 172 | } 173 | 174 | // Add the url to the history 175 | newHistory = options.history.slice(); 176 | newHistory.push(url); 177 | 178 | // Trim to the recent 10 179 | newHistory.slice(-10); 180 | 181 | chrome.storage.sync.set({ 182 | history: newHistory 183 | }, null); 184 | 185 | //now need to reset the 'Recent' context menus and add them again. 186 | if (options.keepRecentFusks) { 187 | createRecentMenu(newHistory); 188 | } 189 | } 190 | 191 | function choiceOnClick(info, tab) { 192 | var count = 0, 193 | direction = 0, 194 | response = '', 195 | url = '', 196 | digitsCheck, 197 | menuItemInfo, 198 | findDigitsRegexp = /^(.*?)(\d+)([^\d]*)$/, 199 | menuItemRegexp = /^Fuskr_IncDec_(10|20|50|100|200|500|Other)_(NegOne|Zero|One)$/; 200 | 201 | if (info.linkUrl !== null) { 202 | digitsCheck = findDigitsRegexp.exec(info.linkUrl); 203 | if (digitsCheck !== null) { 204 | url = info.linkUrl; 205 | } 206 | } 207 | 208 | if (url === '' && info.srcUrl !== null) { 209 | digitsCheck = findDigitsRegexp.exec(info.srcUrl); 210 | if (digitsCheck !== null) { 211 | url = info.srcUrl; 212 | } 213 | } 214 | 215 | if (url === null || typeof url === 'undefined' || url === '') { 216 | alert(l18nify('Prompt_NotAValidFusk')); 217 | return; 218 | } 219 | 220 | menuItemInfo = info.menuItemId.match(menuItemRegexp); 221 | 222 | if (menuItemInfo === null || menuItemInfo.length !== 3) { 223 | alert(l18nify('Prompt_NotAValidFusk')); 224 | return; 225 | } 226 | 227 | if (menuItemInfo[1] === l18nify('ContextMenu_Other')) { 228 | response = prompt(l18nify('Prompt_HowMany')); 229 | 230 | if (isNaN(response) === true) { 231 | alert(l18nify('Prompt_NotAValidNumber')); 232 | return; 233 | } 234 | count = parseInt(response, 10); 235 | } else { 236 | count = parseInt(menuItemInfo[1], 10); 237 | } 238 | 239 | switch (menuItemInfo[2]) { 240 | case 'One': 241 | direction = 1; 242 | break; 243 | case 'Zero': 244 | direction = 0; 245 | break; 246 | case 'NegOne': 247 | direction = -1; 248 | break; 249 | } 250 | 251 | var fuskUrl = Fuskr.CreateFuskUrl(url, count, direction); 252 | createTab(fuskUrl, tab); 253 | } 254 | 255 | function createContextMenu(obj) { 256 | //Generate a new context menu item with a dynamically generated guid. 257 | var contextMenuId = chrome.contextMenus.create({ 258 | parentId: obj.ParentId, 259 | title: obj.Title, 260 | contexts: obj.Context || ['all'], 261 | type: obj.ItemType || 'normal', 262 | targetUrlPatterns: obj.TargetUrlPatterns || null, 263 | id: obj.Id 264 | }); 265 | 266 | return contextMenuId; 267 | } 268 | 269 | function createContextMenus() { 270 | var incDecMenuId, incMenuId, decMenuId, numbers, i; 271 | 272 | //First, empty all the context menus for this extension. 273 | chrome.contextMenus.removeAll(); 274 | 275 | parentId = createContextMenu({ Id: 'FuskrContextMenu', Title: l18nify('ContextMenu_Fusk'), Context: ['all'] }); 276 | incDecMenuId = createContextMenu({ Id: 'FuskrIncrementDecrement', ParentId: parentId, Title: '+/-', Context: ['image', 'video', 'audio', 'link'], TargetUrlPatterns: targetUrls }); 277 | incMenuId = createContextMenu({ Id: 'FuskrIncrement', ParentId: parentId, Title: '+', Context: ['image', 'video', 'audio', 'link'], TargetUrlPatterns: targetUrls }); 278 | decMenuId = createContextMenu({ Id: 'FuskrDecrement', ParentId: parentId, Title: '-', Context: ['image', 'video', 'audio', 'link'], TargetUrlPatterns: targetUrls }); 279 | 280 | numbers = [l18nify('ContextMenu_10'), l18nify('ContextMenu_20'), l18nify('ContextMenu_50'), l18nify('ContextMenu_100'), l18nify('ContextMenu_200'), l18nify('ContextMenu_500'), l18nify('ContextMenu_Other')]; 281 | for (i = 0; i < numbers.length; i++) { 282 | createContextMenu({ Id: 'Fuskr_IncDec_' + numbers[i] + '_Zero', ParentId: incDecMenuId, Title: numbers[i], Context: ['image', 'video', 'audio', 'link'] }); 283 | createContextMenu({ Id: 'Fuskr_IncDec_' + numbers[i] + '_One', ParentId: incMenuId, Title: numbers[i], Context: ['image', 'video', 'audio', 'link'] }); 284 | createContextMenu({ Id: 'Fuskr_IncDec_' + numbers[i] + '_NegOne', ParentId: decMenuId, Title: numbers[i], Context: ['image', 'video', 'audio', 'link'] }); 285 | } 286 | 287 | createContextMenu({ Id: 'FuskrSeparator1', ParentId: parentId, Context: ['image', 'video', 'audio', 'link'], ItemType: 'separator' }); 288 | createContextMenu({ Id: 'FuskrCreateFromSelection', ParentId: parentId, Title: l18nify('ContextMenu_CreateFromSelection'), Context: ['selection'] }); 289 | createContextMenu({ Id: 'FuskrManual', ParentId: parentId, Title: l18nify('ContextMenu_Manual') }); 290 | //createContextMenu({ Id: 'FuskrInfinite', ParentId: parentId, Title: l18nify('ContextMenu_Infinite') }); 291 | createContextMenu({ Id: 'FuskrSeparator2', ParentId: parentId, ItemType: 'separator' }); 292 | 293 | createContextMenu({ Id: 'FuskrOptions', ParentId: parentId, Title: l18nify('ContextMenu_Options') }); 294 | } 295 | 296 | chrome.runtime.onInstalled.addListener(function (details) { 297 | if (details.reason === 'install') { 298 | // First install - set defaults 299 | chrome.storage.sync.set({ 300 | history: [], 301 | darkMode: false, 302 | keepRecentFusks: true, 303 | openInForeground: true 304 | }); 305 | } else if (details.reason === 'update') { 306 | var previousDarkMode = localStorage.getItem('darkMode') || '0'; 307 | var previousKeepFusks = localStorage.getItem('keepRecentFusks') || '1'; 308 | var previousOpenInForeground = localStorage.getItem('openInForeground') || '1'; 309 | var previousHistory = localStorage.getItem('history') || ''; 310 | 311 | // Was previously stored as 0/1 312 | var darkModeBool = parseInt(previousDarkMode, 10) === 1; 313 | var keepFusksBool = parseInt(previousKeepFusks, 10) === 1; 314 | var openForegroundBool = parseInt(previousOpenInForeground, 10) === 1; 315 | 316 | // Was previously stored delimited by || 317 | var historyArray = previousHistory.split('||').filter(function (x) { 318 | return x !== null && typeof x !== 'undefined' && x.length > 0; 319 | }); 320 | 321 | localStorage.clear(); 322 | 323 | chrome.storage.sync.set({ 324 | history: historyArray, 325 | darkMode: darkModeBool, 326 | keepRecentFusks: keepFusksBool, 327 | openInForeground: openForegroundBool 328 | }); 329 | } 330 | 331 | createContextMenus(); 332 | }); 333 | 334 | chrome.storage.onChanged.addListener(function (changes) { 335 | if (changes === null || typeof changes === 'undefined') { 336 | return; 337 | } 338 | 339 | Object.keys(changes).map(function (key) { 340 | options[key] = changes[key].newValue; 341 | }); 342 | 343 | // Hide or show the 'Recent Items' menu 344 | if (changes.keepRecentFusks && changes.keepRecentFusks.newValue !== changes.keepRecentFusks.oldValue) { 345 | if (changes.keepRecentFusks === true) { 346 | createRecentMenu(options.history); 347 | } else { 348 | createRecentMenu(null); 349 | } 350 | } 351 | }); 352 | 353 | // Fill the options 354 | chrome.storage.sync.get(null, function (items) { 355 | if (typeof items === 'undefined') { 356 | //We cannot get or set options. 357 | return; 358 | } 359 | 360 | Object.keys(items).map(function (key) { 361 | options[key] = items[key]; 362 | }); 363 | }); 364 | 365 | chrome.contextMenus.onClicked.addListener(function (info, tab) { 366 | switch(info.menuItemId) { 367 | case 'FuskrContextMenu', 'FuskrIncrementDecrement', 'FuskrIncrement', 'FuskrDecrement', 'FuskrSeparator1', 'FuskrSeparator2', 'FuskrSeparator3', 'FuskrRecent', 'FuskrInfinite': 368 | return; 369 | case 'FuskrCreateFromSelection': 370 | createFromSelectionOnClick(info, tab); 371 | return; 372 | case 'FuskrManual': 373 | manualOnClick(info, tab); 374 | return; 375 | case 'FuskrClearHistory': 376 | clearRecentOnClick(info, tab); 377 | return; 378 | case 'FuskrOptions': 379 | optionsOnClick(info, tab); 380 | return; 381 | } 382 | 383 | if (info.menuItemId.includes('Fuskr_IncDec_')) { 384 | choiceOnClick(info, tab); 385 | return; 386 | } 387 | 388 | if (info.menuItemId.includes('FuskrHistory_')) { 389 | recentOnClick(info, tab); 390 | return; 391 | } 392 | }); 393 | 394 | // This event is fired with the user accepts the input in the omnibox. 395 | chrome.omnibox.onInputEntered.addListener(function (text) { 396 | chrome.tabs.query({currentWindow: true, active: true}, function (tab) { 397 | createTab(text, tab[0]); 398 | }); 399 | }); 400 | }()); 401 | -------------------------------------------------------------------------------- /Scripts/fuskr.js: -------------------------------------------------------------------------------- 1 | var Fuskr = (function (ret) { 2 | 'use strict'; 3 | 4 | var groupRegex = /\{\d+\}/, 5 | numericRegex = /^(.*?)\[(\d+)-(\d+)\](.*)$/, 6 | alphabeticRegex = /^(.*?)\[(\w)-(\w)\](.*)$/; 7 | 8 | function padString(number, stringLength, padding) { 9 | var numStr = number.toString(); 10 | if (!padding) { 11 | return numStr; 12 | } 13 | 14 | while (numStr.length < stringLength) { 15 | numStr = padding + numStr; 16 | } 17 | 18 | return numStr; 19 | } 20 | 21 | function getAlphabeticalUrls(prefix, suffix, startString, endString, groupNumber) { 22 | var i, 23 | link, 24 | links, 25 | retUrls = [], 26 | thisNumString, 27 | startNumber = ret.ConvertCharToInt(startString), 28 | endNumber = ret.ConvertCharToInt(endString); 29 | 30 | while (startNumber <= endNumber) { 31 | thisNumString = ret.ConvertIntToChar(startNumber); 32 | link = prefix + thisNumString + suffix; 33 | 34 | if (groupRegex.test(link)) { 35 | link = link.replace(new RegExp('\\{' + groupNumber + '\\}', 'g'), thisNumString); 36 | } 37 | 38 | if (ret.IsFuskable(link)) { 39 | links = ret.GetLinks(link, groupNumber + 1); 40 | for (i = 0; i < links.length; i += 1) { 41 | retUrls.push(links[i]); 42 | } 43 | } else { 44 | retUrls.push(link); 45 | } 46 | 47 | startNumber += 1; 48 | } 49 | 50 | return retUrls; 51 | } 52 | 53 | function getNumericUrls(prefix, suffix, startNumString, endNumString, groupNumber) { 54 | var i, 55 | link, 56 | links, 57 | retUrls = [], 58 | thisNumString, 59 | startNumber = parseInt(startNumString, 10), 60 | endNumber = parseInt(endNumString, 10), 61 | paddedLength = startNumString.length; 62 | 63 | while (startNumber <= endNumber) { 64 | thisNumString = padString(startNumber, paddedLength, '0'); 65 | link = prefix + thisNumString + suffix; 66 | 67 | if (groupRegex.test(link)) { 68 | link = link.replace(new RegExp('\\{' + groupNumber + '\\}', 'g'), thisNumString); 69 | } 70 | 71 | if (ret.IsFuskable(link)) { 72 | links = ret.GetLinks(link, groupNumber + 1); 73 | for (i = 0; i < links.length; i += 1) { 74 | retUrls.push(links[i]); 75 | } 76 | } else { 77 | retUrls.push(link); 78 | } 79 | 80 | startNumber += 1; 81 | } 82 | 83 | return retUrls; 84 | } 85 | 86 | ret.ConvertIntToChar = function (i) { 87 | return String.fromCharCode(i); 88 | }; 89 | 90 | ret.ConvertCharToInt = function (a) { 91 | return a.charCodeAt(); 92 | }; 93 | 94 | ret.IsAlphabetical = function (url) { 95 | return alphabeticRegex.test(url); 96 | }; 97 | 98 | ret.IsNumeric = function (url) { 99 | return numericRegex.test(url); 100 | }; 101 | 102 | ret.IsFuskable = function (url) { 103 | return numericRegex.test(url) || alphabeticRegex.test(url); 104 | }; 105 | 106 | ret.GetLinks = function (url, groupNumber) { 107 | var matches, links = []; 108 | 109 | groupNumber = groupNumber || 0; 110 | 111 | if (!ret.IsFuskable(url)) { 112 | return links; 113 | } 114 | 115 | if (ret.IsNumeric(url)) { 116 | matches = numericRegex.exec(url); 117 | links = getNumericUrls(matches[1], matches[4], matches[2], matches[3], groupNumber); 118 | } else if (ret.IsAlphabetical(url)) { 119 | matches = alphabeticRegex.exec(url); 120 | links = getAlphabeticalUrls(matches[1], matches[4], matches[2], matches[3], groupNumber); 121 | } 122 | 123 | return links; 124 | }; 125 | 126 | ret.CreateFuskUrl = function (url, count, direction) { 127 | var begin, number, end, firstNum, lastNum, findDigitsRegexp, digitsCheck; 128 | 129 | findDigitsRegexp = /^(.*?)(\d+)([^\d]*)$/; 130 | digitsCheck = findDigitsRegexp.exec(url); 131 | 132 | begin = digitsCheck[1]; 133 | number = digitsCheck[2]; 134 | end = digitsCheck[3]; 135 | 136 | firstNum = parseInt(number, 10); 137 | lastNum = firstNum; 138 | 139 | if (direction === 0) { 140 | firstNum -= count; 141 | lastNum += count; 142 | } else if (direction === -1) { 143 | firstNum -= count; 144 | } else if (direction === 1) { 145 | lastNum += count; 146 | } 147 | 148 | firstNum = (firstNum < 0 ? 0 : firstNum).toString(); 149 | lastNum = (lastNum < 0 ? 0 : lastNum).toString(); 150 | 151 | while (firstNum.length < number.length) { 152 | firstNum = '0' + firstNum; 153 | } 154 | 155 | while (lastNum.length < firstNum.length) { 156 | lastNum = '0' + lastNum; 157 | } 158 | 159 | return begin + '[' + firstNum + '-' + lastNum + ']' + end; 160 | }; 161 | 162 | ret.GetImageFilename = function (url) { 163 | if (typeof url === 'undefined' || url === '') { 164 | return ''; 165 | } 166 | 167 | return url.substring(url.lastIndexOf('/') + 1); 168 | }; 169 | 170 | return ret; 171 | }(Fuskr || {})); 172 | -------------------------------------------------------------------------------- /Scripts/options.js: -------------------------------------------------------------------------------- 1 | /* globals document, chrome, clearTimeout, setTimeout */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | var timeoutId = null, 7 | options = { 8 | history: [], 9 | darkMode: false, 10 | keepRecentFusks: true, 11 | openInForeground: true 12 | }; 13 | 14 | function setCheckboxes() { 15 | var darkMode = document.getElementById('darkMode'); 16 | var keepRecentFusks = document.getElementById('keepRecentFusks'); 17 | var openInForeground = document.getElementById('openInForeground'); 18 | 19 | if (darkMode && keepRecentFusks && openInForeground) { 20 | darkMode.checked = options.darkMode; 21 | keepRecentFusks.checked = options.keepRecentFusks; 22 | openInForeground.checked = options.openInForeground; 23 | } 24 | } 25 | 26 | function saveOptions() { 27 | var darkMode = document.getElementById('darkMode'); 28 | var keepRecentFusks = document.getElementById('keepRecentFusks'); 29 | var openInForeground = document.getElementById('openInForeground'); 30 | var status = document.getElementById('status'); 31 | 32 | var optionsToSet = { 33 | darkMode: darkMode.checked, 34 | keepRecentFusks: keepRecentFusks.checked, 35 | openInForeground: openInForeground.checked 36 | }; 37 | 38 | // If disabling recent fusks, clear history 39 | if (!keepRecentFusks.checked) { 40 | optionsToSet.history = []; 41 | } 42 | 43 | chrome.storage.sync.set(optionsToSet, function () { 44 | if (chrome.runtime.lastError) { 45 | status.innerHTML = 'Could not save settings. Try again.'; 46 | } else { 47 | status.innerHTML = 'Options saved!'; 48 | clearTimeout(timeoutId); 49 | timeoutId = setTimeout(function () { 50 | status.innerHTML = ''; 51 | }, 2000); 52 | } 53 | }); 54 | } 55 | 56 | (function () { 57 | document.addEventListener('DOMContentLoaded', setCheckboxes); 58 | document.getElementById('save').addEventListener('click', saveOptions); 59 | 60 | var storagePromise = new Promise(function (resolve) { 61 | chrome.storage.sync.get(null, function (items) { 62 | if (typeof items === 'undefined') { 63 | //We cannot get or set options. 64 | return; 65 | } 66 | resolve(items); 67 | }); 68 | }); 69 | 70 | storagePromise.then(function(items) { 71 | Object.keys(items).map(function (key) { 72 | options[key] = items[key]; 73 | }); 74 | 75 | setCheckboxes(); 76 | }); 77 | }()); 78 | 79 | chrome.storage.onChanged.addListener(function (changes) { 80 | if (changes === null || typeof changes === 'undefined') { 81 | return; 82 | } 83 | 84 | Object.keys(changes).map(function (key) { 85 | options[key] = changes[key].newValue; 86 | }); 87 | 88 | setCheckboxes(); 89 | }); 90 | }()); 91 | -------------------------------------------------------------------------------- /Styles/styles.scss: -------------------------------------------------------------------------------- 1 | $black: #000; 2 | $white: #fff; 3 | $off-white: #eee; 4 | $dark: #222; 5 | $red: #b33c04; 6 | 7 | html { 8 | border: 0; 9 | cursor: default; 10 | font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif; 11 | font-size: 14px; 12 | margin: 0; 13 | padding: 0; 14 | text-align: center; 15 | 16 | &.ng-cloak { 17 | display: none; 18 | } 19 | 20 | &.lightMode { 21 | background-color: $white; 22 | color: $dark; 23 | 24 | select { 25 | border: 1px solid $black; 26 | } 27 | 28 | .info { 29 | background: $white url('/Images/128x128.png') no-repeat left top; 30 | .fusk-url-container { 31 | .fusk-url-textbox { 32 | background-color: $white; 33 | color: $black; 34 | } 35 | } 36 | } 37 | 38 | .viewer { 39 | .close { 40 | color: $white; 41 | } 42 | } 43 | } 44 | 45 | &.darkMode { 46 | background-color: $dark; 47 | color: $off-white; 48 | 49 | select { 50 | background-color: $dark; 51 | border: 1px solid $off-white; 52 | color: $off-white; 53 | } 54 | .info { 55 | background: $dark url('/Images/128x128.png') no-repeat left top; 56 | 57 | .fusk-url-container { 58 | .fusk-url-textbox { 59 | background-color: $dark; 60 | color: $off-white; 61 | } 62 | } 63 | } 64 | 65 | .viewer { 66 | .close { 67 | color: $dark; 68 | } 69 | } 70 | 71 | } 72 | } 73 | 74 | img { 75 | border: 0; 76 | margin: auto; 77 | margin-bottom: 4px; 78 | } 79 | 80 | a { 81 | &, 82 | &:hover, 83 | &:visited { 84 | color: $red; 85 | cursor: pointer; 86 | text-decoration: none; 87 | } 88 | } 89 | 90 | button { 91 | &.download { 92 | float: right; 93 | margin: .5em .4em .5em 0; 94 | padding: 5px; 95 | } 96 | } 97 | 98 | .images-are-thumbnails { 99 | .wrap { 100 | width: min-content; 101 | } 102 | 103 | img { 104 | width: auto; 105 | 106 | &.fusk-image { 107 | width: 100%; 108 | } 109 | } 110 | } 111 | 112 | .images-are-full-page { 113 | .wrap { 114 | width: -webkit-fill-available; 115 | } 116 | 117 | img { 118 | width: auto; 119 | 120 | &.fusk-image { 121 | width: 100%; 122 | } 123 | } 124 | } 125 | 126 | .images-are-full-width { 127 | img { 128 | width: auto; 129 | 130 | &.fusk-image { 131 | width: 100%; 132 | } 133 | } 134 | } 135 | 136 | .code { 137 | background-color: $off-white; 138 | font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif; 139 | padding: 1px 5px; 140 | white-space: pre-wrap; 141 | } 142 | 143 | .info { 144 | clear: both; 145 | min-height: 128px; 146 | padding-left: 130px; 147 | padding-top: 10px; 148 | text-align: left; 149 | 150 | .fusk-url-container { 151 | .fusk-url-textbox { 152 | border: 0; 153 | font-size: 20px; 154 | width: 99%; 155 | } 156 | } 157 | 158 | .image-urls { 159 | height: 300px; 160 | width: 50%; 161 | } 162 | } 163 | 164 | .bottom { 165 | clear: both; 166 | padding-top: 20px; 167 | } 168 | 169 | .rating { 170 | border: 1px solid $off-white; 171 | clear: both; 172 | margin: 0 auto; 173 | padding: 20px 0; 174 | width: 500px; 175 | } 176 | 177 | .wrap { 178 | border: 1px solid $off-white; 179 | float: left; 180 | margin: 7px; 181 | padding: 3px; 182 | text-align: center; 183 | } 184 | 185 | .viewer { 186 | .close { 187 | color: $dark; 188 | cursor: pointer; 189 | display: block; 190 | font-size: 30px; 191 | position: fixed; 192 | right: 20px; 193 | top: 20px; 194 | z-index: 101; 195 | 196 | &:hover { 197 | color: $off-white; 198 | } 199 | } 200 | 201 | .viewer-item { 202 | bottom: 0; 203 | left: 0; 204 | position: fixed; 205 | right: 0; 206 | top: 0; 207 | z-index: 100; 208 | 209 | a { 210 | background-position: 50% 50%; 211 | background-repeat: no-repeat; 212 | background-size: contain; 213 | display: block; 214 | height: 100%; 215 | width: 100%; 216 | 217 | &.previous-image { 218 | background-position: 35% 50%; 219 | } 220 | 221 | &.next-image { 222 | background-position: 65% 50%; 223 | } 224 | } 225 | 226 | &.small { 227 | bottom: 10%; 228 | opacity: .2; 229 | top: 10%; 230 | z-index: 99; 231 | } 232 | 233 | &.modal { 234 | background-color: $dark; 235 | z-index: 98; 236 | } 237 | } 238 | } 239 | 240 | ul { 241 | &.totals { 242 | font-weight: bold; 243 | } 244 | 245 | &.linklist { 246 | padding: 0; 247 | 248 | li { 249 | display: inline; 250 | padding-right: 5px; 251 | } 252 | 253 | li~li { 254 | border-left: 1px solid $dark; 255 | padding-left: 5px; 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | Below is a list of desirable functionality changes **in no particular order**: 4 | 5 | * Allow images to be saved individually. Currently the blob hash value is used as the filename which isn't nice. This should be as simple as adding the [`download`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#Attributes) attribute to the ``. 6 | * Make use of Chrome Sync ([chrome.storage](https://developer.chrome.com/extensions/storage)) to store settings and allow synchronisation across devices. 7 | * Move fusk url to inside the page. Use a guid in the url instead which is tied to the url in storage. This way, the fusk can still persist across browser restarts, and potentially browsers when used in conjunction with Chrome Sync (see #3 above). 8 | * Split out Chrome-specific functionality so that we can create a Firefox/Opera? version of Fuskr. 9 | * Allow fusks to be created from a list of urls. A crappy example would be an Imgur album (which you can download by appending /zip to the url), where the filenames are not sequential. 10 | * Create 'infinite' fusk. Provide a single starting point and continue loading in blocks of 100 until the user requests it stops. 11 | * For example, fusk url is https://www.example.com/images/5.jpg. If they chose infinite fusk, we would start by creating a range from 0 to 100 and continue going in blocks of 100 until we reach a failure. 12 | * Implement overload protection. If over 500 images are found, inform the user of the total generated image count. 13 | * Output a list of the generated urls for the user in a textarea. The list can change depending on whether the 'show broken links' option is true. 14 | -------------------------------------------------------------------------------- /Tests/fuskr/Fuskr.GetLinks.spec.js: -------------------------------------------------------------------------------- 1 | /*globals describe:false, it:false, expect:false, Fuskr, jasmine */ 2 | 3 | describe('Fuskr - GetLinks', function () { 4 | 5 | it('Function exists', function () { 6 | expect(Fuskr.GetLinks).toEqual(jasmine.any(Function)); 7 | }); 8 | 9 | it('Null url', function () { 10 | var url, 11 | links = Fuskr.GetLinks(url); 12 | 13 | expect(links).toEqual(jasmine.any(Array)); 14 | expect(links.length).toEqual(0); 15 | }); 16 | 17 | it('Empty string Url', function () { 18 | var url = '', 19 | links = Fuskr.GetLinks(url); 20 | 21 | expect(links).toEqual(jasmine.any(Array)); 22 | expect(links.length).toEqual(0); 23 | }); 24 | 25 | it('Object / Invalid parameter Url', function () { 26 | var url = { hey: 'ho' }, 27 | links = Fuskr.GetLinks(url); 28 | 29 | expect(links).toEqual(jasmine.any(Array)); 30 | expect(links.length).toEqual(0); 31 | }); 32 | 33 | it('Array / Invalid parameter Url', function () { 34 | var url = ['string', 1234, { obj: 'ject' }], 35 | links = Fuskr.GetLinks(url); 36 | 37 | expect(links).toEqual(jasmine.any(Array)); 38 | expect(links.length).toEqual(0); 39 | }); 40 | 41 | it('URL - Unfuskable', function () { 42 | var url = 'http://domain.com/path/file/', 43 | links = Fuskr.GetLinks(url); 44 | 45 | expect(links).toEqual(jasmine.any(Array)); 46 | expect(links.length).toEqual(0); 47 | }); 48 | 49 | it('URL - Fuskable file [0-9]', function () { 50 | var url = 'http://domain.com/path/file/[0-9].jpg', 51 | links = Fuskr.GetLinks(url); 52 | 53 | expect(links).toEqual(jasmine.any(Array)); 54 | expect(links.length).toEqual(10); 55 | 56 | expect(links[0]).toEqual('http://domain.com/path/file/0.jpg'); 57 | expect(links[4]).toEqual('http://domain.com/path/file/4.jpg'); 58 | expect(links[7]).toEqual('http://domain.com/path/file/7.jpg'); 59 | expect(links[9]).toEqual('http://domain.com/path/file/9.jpg'); 60 | }); 61 | 62 | it('URL - Fuskable file [a-z]', function () { 63 | var url = 'http://domain.com/path/file/[a-z].jpg', 64 | links = Fuskr.GetLinks(url); 65 | 66 | expect(links).toEqual(jasmine.any(Array)); 67 | expect(links.length).toEqual(26); 68 | expect(links[0]).toEqual('http://domain.com/path/file/a.jpg'); 69 | expect(links[4]).toEqual('http://domain.com/path/file/e.jpg'); 70 | expect(links[12]).toEqual('http://domain.com/path/file/m.jpg'); 71 | expect(links[25]).toEqual('http://domain.com/path/file/z.jpg'); 72 | }); 73 | 74 | it('URL - Fuskable file [8-16]', function () { 75 | var url = 'http://domain.com/path/file/[8-16].jpg', 76 | links = Fuskr.GetLinks(url); 77 | 78 | expect(links).toEqual(jasmine.any(Array)); 79 | expect(links.length).toEqual(9); 80 | expect(links[0]).toEqual('http://domain.com/path/file/8.jpg'); 81 | expect(links[1]).toEqual('http://domain.com/path/file/9.jpg'); 82 | expect(links[2]).toEqual('http://domain.com/path/file/10.jpg'); 83 | expect(links[3]).toEqual('http://domain.com/path/file/11.jpg'); 84 | expect(links[8]).toEqual('http://domain.com/path/file/16.jpg'); 85 | }); 86 | 87 | it('URL - Fuskable file [h-p]', function () { 88 | var url = 'http://domain.com/path/file/[h-p].jpg', 89 | links = Fuskr.GetLinks(url); 90 | 91 | expect(links).toEqual(jasmine.any(Array)); 92 | expect(links.length).toEqual(9); 93 | expect(links[0]).toEqual('http://domain.com/path/file/h.jpg'); 94 | expect(links[1]).toEqual('http://domain.com/path/file/i.jpg'); 95 | expect(links[2]).toEqual('http://domain.com/path/file/j.jpg'); 96 | expect(links[3]).toEqual('http://domain.com/path/file/k.jpg'); 97 | expect(links[8]).toEqual('http://domain.com/path/file/p.jpg'); 98 | }); 99 | 100 | it('URL - Fuskable file [08-16]', function () { 101 | var url = 'http://domain.com/path/file/[08-16].jpg', 102 | links = Fuskr.GetLinks(url); 103 | 104 | expect(links).toEqual(jasmine.any(Array)); 105 | expect(links.length).toEqual(9); 106 | expect(links[0]).toEqual('http://domain.com/path/file/08.jpg'); 107 | expect(links[1]).toEqual('http://domain.com/path/file/09.jpg'); 108 | expect(links[2]).toEqual('http://domain.com/path/file/10.jpg'); 109 | expect(links[3]).toEqual('http://domain.com/path/file/11.jpg'); 110 | expect(links[8]).toEqual('http://domain.com/path/file/16.jpg'); 111 | }); 112 | 113 | it('URL - Fuskable file [0-9] [3-7]', function () { 114 | var url = 'http://domain.com/path/file/[0-9]and[3-7].jpg', 115 | links = Fuskr.GetLinks(url); 116 | 117 | expect(links).toEqual(jasmine.any(Array)); 118 | expect(links.length).toEqual(50); 119 | expect(links[0]).toEqual('http://domain.com/path/file/0and3.jpg'); 120 | expect(links[1]).toEqual('http://domain.com/path/file/0and4.jpg'); 121 | expect(links[2]).toEqual('http://domain.com/path/file/0and5.jpg'); 122 | expect(links[3]).toEqual('http://domain.com/path/file/0and6.jpg'); 123 | expect(links[4]).toEqual('http://domain.com/path/file/0and7.jpg'); 124 | expect(links[5]).toEqual('http://domain.com/path/file/1and3.jpg'); 125 | expect(links[6]).toEqual('http://domain.com/path/file/1and4.jpg'); 126 | expect(links[7]).toEqual('http://domain.com/path/file/1and5.jpg'); 127 | expect(links[8]).toEqual('http://domain.com/path/file/1and6.jpg'); 128 | expect(links[9]).toEqual('http://domain.com/path/file/1and7.jpg'); 129 | expect(links[49]).toEqual('http://domain.com/path/file/9and7.jpg'); 130 | }); 131 | 132 | it('URL - Fuskable file [a-z] [c-g]', function () { 133 | var url = 'http://domain.com/path/file/[a-z]and[c-g].jpg', 134 | links = Fuskr.GetLinks(url); 135 | 136 | expect(links).toEqual(jasmine.any(Array)); 137 | expect(links.length).toEqual(130); 138 | expect(links[0]).toEqual('http://domain.com/path/file/aandc.jpg'); 139 | expect(links[1]).toEqual('http://domain.com/path/file/aandd.jpg'); 140 | expect(links[2]).toEqual('http://domain.com/path/file/aande.jpg'); 141 | expect(links[3]).toEqual('http://domain.com/path/file/aandf.jpg'); 142 | expect(links[4]).toEqual('http://domain.com/path/file/aandg.jpg'); 143 | expect(links[5]).toEqual('http://domain.com/path/file/bandc.jpg'); 144 | expect(links[6]).toEqual('http://domain.com/path/file/bandd.jpg'); 145 | expect(links[7]).toEqual('http://domain.com/path/file/bande.jpg'); 146 | expect(links[8]).toEqual('http://domain.com/path/file/bandf.jpg'); 147 | expect(links[9]).toEqual('http://domain.com/path/file/bandg.jpg'); 148 | expect(links[49]).toEqual('http://domain.com/path/file/jandg.jpg'); 149 | expect(links[129]).toEqual('http://domain.com/path/file/zandg.jpg'); 150 | }); 151 | 152 | it('URL - Fuskable file [0-9] [c-g]', function () { 153 | var url = 'http://domain.com/path/file/[0-9]and[c-g].jpg', 154 | links = Fuskr.GetLinks(url); 155 | 156 | expect(links).toEqual(jasmine.any(Array)); 157 | expect(links.length).toEqual(50); 158 | expect(links[0]).toEqual('http://domain.com/path/file/0andc.jpg'); 159 | expect(links[1]).toEqual('http://domain.com/path/file/0andd.jpg'); 160 | expect(links[2]).toEqual('http://domain.com/path/file/0ande.jpg'); 161 | expect(links[3]).toEqual('http://domain.com/path/file/0andf.jpg'); 162 | expect(links[4]).toEqual('http://domain.com/path/file/0andg.jpg'); 163 | expect(links[5]).toEqual('http://domain.com/path/file/1andc.jpg'); 164 | expect(links[6]).toEqual('http://domain.com/path/file/1andd.jpg'); 165 | expect(links[7]).toEqual('http://domain.com/path/file/1ande.jpg'); 166 | expect(links[8]).toEqual('http://domain.com/path/file/1andf.jpg'); 167 | expect(links[9]).toEqual('http://domain.com/path/file/1andg.jpg'); 168 | expect(links[49]).toEqual('http://domain.com/path/file/9andg.jpg'); 169 | expect(links[49]).toEqual('http://domain.com/path/file/9andg.jpg'); 170 | }); 171 | 172 | it('URL - Fuskable file [0-9] [3-7] [10-13]', function () { 173 | var url = 'http://domain.com/path/file/[0-9]and[3-7]and[10-13].jpg', 174 | links = Fuskr.GetLinks(url); 175 | 176 | expect(links).toEqual(jasmine.any(Array)); 177 | expect(links.length).toEqual(200); 178 | expect(links[0]).toEqual('http://domain.com/path/file/0and3and10.jpg'); 179 | expect(links[1]).toEqual('http://domain.com/path/file/0and3and11.jpg'); 180 | expect(links[2]).toEqual('http://domain.com/path/file/0and3and12.jpg'); 181 | expect(links[3]).toEqual('http://domain.com/path/file/0and3and13.jpg'); 182 | expect(links[4]).toEqual('http://domain.com/path/file/0and4and10.jpg'); 183 | expect(links[5]).toEqual('http://domain.com/path/file/0and4and11.jpg'); 184 | expect(links[6]).toEqual('http://domain.com/path/file/0and4and12.jpg'); 185 | expect(links[7]).toEqual('http://domain.com/path/file/0and4and13.jpg'); 186 | expect(links[8]).toEqual('http://domain.com/path/file/0and5and10.jpg'); 187 | expect(links[9]).toEqual('http://domain.com/path/file/0and5and11.jpg'); 188 | expect(links[199]).toEqual('http://domain.com/path/file/9and7and13.jpg'); 189 | }); 190 | 191 | it('URL - Fuskable file [a-z] [c-g] [j-m]', function () { 192 | var url = 'http://domain.com/path/file/[a-z]and[c-g]and[j-m].jpg', 193 | links = Fuskr.GetLinks(url); 194 | 195 | expect(links).toEqual(jasmine.any(Array)); 196 | expect(links.length).toEqual(520); 197 | expect(links[0]).toEqual('http://domain.com/path/file/aandcandj.jpg'); 198 | expect(links[1]).toEqual('http://domain.com/path/file/aandcandk.jpg'); 199 | expect(links[2]).toEqual('http://domain.com/path/file/aandcandl.jpg'); 200 | expect(links[3]).toEqual('http://domain.com/path/file/aandcandm.jpg'); 201 | expect(links[4]).toEqual('http://domain.com/path/file/aanddandj.jpg'); 202 | expect(links[5]).toEqual('http://domain.com/path/file/aanddandk.jpg'); 203 | expect(links[6]).toEqual('http://domain.com/path/file/aanddandl.jpg'); 204 | expect(links[7]).toEqual('http://domain.com/path/file/aanddandm.jpg'); 205 | expect(links[8]).toEqual('http://domain.com/path/file/aandeandj.jpg'); 206 | expect(links[9]).toEqual('http://domain.com/path/file/aandeandk.jpg'); 207 | expect(links[519]).toEqual('http://domain.com/path/file/zandgandm.jpg'); 208 | }); 209 | 210 | it('URL - Fuskable file [0-9] [c-g] [j-m]', function () { 211 | var url = 'http://domain.com/path/file/[0-9]and[c-g]and[j-m].jpg', 212 | links = Fuskr.GetLinks(url); 213 | 214 | expect(links).toEqual(jasmine.any(Array)); 215 | expect(links.length).toEqual(200); 216 | expect(links[0]).toEqual('http://domain.com/path/file/0andcandj.jpg'); 217 | expect(links[1]).toEqual('http://domain.com/path/file/0andcandk.jpg'); 218 | expect(links[2]).toEqual('http://domain.com/path/file/0andcandl.jpg'); 219 | expect(links[3]).toEqual('http://domain.com/path/file/0andcandm.jpg'); 220 | expect(links[4]).toEqual('http://domain.com/path/file/0anddandj.jpg'); 221 | expect(links[5]).toEqual('http://domain.com/path/file/0anddandk.jpg'); 222 | expect(links[6]).toEqual('http://domain.com/path/file/0anddandl.jpg'); 223 | expect(links[7]).toEqual('http://domain.com/path/file/0anddandm.jpg'); 224 | expect(links[8]).toEqual('http://domain.com/path/file/0andeandj.jpg'); 225 | expect(links[9]).toEqual('http://domain.com/path/file/0andeandk.jpg'); 226 | expect(links[199]).toEqual('http://domain.com/path/file/9andgandm.jpg'); 227 | }); 228 | 229 | it('URL - Fuskable file [0-9] [3-7] [0010-0013]', function () { 230 | var url = 'http://domain.com/path/file/[0-9]and[3-7]and[0010-0013].jpg', 231 | links = Fuskr.GetLinks(url); 232 | 233 | expect(links).toEqual(jasmine.any(Array)); 234 | expect(links.length).toEqual(200); 235 | expect(links[0]).toEqual('http://domain.com/path/file/0and3and0010.jpg'); 236 | expect(links[1]).toEqual('http://domain.com/path/file/0and3and0011.jpg'); 237 | expect(links[2]).toEqual('http://domain.com/path/file/0and3and0012.jpg'); 238 | expect(links[3]).toEqual('http://domain.com/path/file/0and3and0013.jpg'); 239 | expect(links[4]).toEqual('http://domain.com/path/file/0and4and0010.jpg'); 240 | expect(links[5]).toEqual('http://domain.com/path/file/0and4and0011.jpg'); 241 | expect(links[6]).toEqual('http://domain.com/path/file/0and4and0012.jpg'); 242 | expect(links[7]).toEqual('http://domain.com/path/file/0and4and0013.jpg'); 243 | expect(links[8]).toEqual('http://domain.com/path/file/0and5and0010.jpg'); 244 | expect(links[9]).toEqual('http://domain.com/path/file/0and5and0011.jpg'); 245 | expect(links[199]).toEqual('http://domain.com/path/file/9and7and0013.jpg'); 246 | }); 247 | 248 | it('URL - Fuskable file [0-9] {0}', function () { 249 | var url = 'http://domain.com/path/file/[0-9]and{0}.jpg', 250 | links = Fuskr.GetLinks(url); 251 | 252 | expect(links).toEqual(jasmine.any(Array)); 253 | expect(links.length).toEqual(10); 254 | expect(links[0]).toEqual('http://domain.com/path/file/0and0.jpg'); 255 | expect(links[4]).toEqual('http://domain.com/path/file/4and4.jpg'); 256 | expect(links[7]).toEqual('http://domain.com/path/file/7and7.jpg'); 257 | expect(links[9]).toEqual('http://domain.com/path/file/9and9.jpg'); 258 | }); 259 | 260 | it('URL - Fuskable file [a-z] {0}', function () { 261 | var url = 'http://domain.com/path/file/[a-z]and{0}.jpg', 262 | links = Fuskr.GetLinks(url); 263 | 264 | expect(links).toEqual(jasmine.any(Array)); 265 | expect(links.length).toEqual(26); 266 | expect(links[0]).toEqual('http://domain.com/path/file/aanda.jpg'); 267 | expect(links[4]).toEqual('http://domain.com/path/file/eande.jpg'); 268 | expect(links[7]).toEqual('http://domain.com/path/file/handh.jpg'); 269 | expect(links[25]).toEqual('http://domain.com/path/file/zandz.jpg'); 270 | }); 271 | 272 | it('URL - Fuskable file [0-9] [3-7] {1}', function () { 273 | var url = 'http://domain.com/path/file/[0-9]and[3-7]and{1}.jpg', 274 | links = Fuskr.GetLinks(url); 275 | 276 | expect(links).toEqual(jasmine.any(Array)); 277 | expect(links.length).toEqual(50); 278 | expect(links[0]).toEqual('http://domain.com/path/file/0and3and3.jpg'); 279 | expect(links[1]).toEqual('http://domain.com/path/file/0and4and4.jpg'); 280 | expect(links[2]).toEqual('http://domain.com/path/file/0and5and5.jpg'); 281 | expect(links[3]).toEqual('http://domain.com/path/file/0and6and6.jpg'); 282 | expect(links[4]).toEqual('http://domain.com/path/file/0and7and7.jpg'); 283 | expect(links[5]).toEqual('http://domain.com/path/file/1and3and3.jpg'); 284 | expect(links[6]).toEqual('http://domain.com/path/file/1and4and4.jpg'); 285 | expect(links[7]).toEqual('http://domain.com/path/file/1and5and5.jpg'); 286 | expect(links[8]).toEqual('http://domain.com/path/file/1and6and6.jpg'); 287 | expect(links[9]).toEqual('http://domain.com/path/file/1and7and7.jpg'); 288 | expect(links[49]).toEqual('http://domain.com/path/file/9and7and7.jpg'); 289 | }); 290 | 291 | it('URL - Fuskable file [a-z] [c-g] {1}', function () { 292 | var url = 'http://domain.com/path/file/[a-z]and[c-g]and{1}.jpg', 293 | links = Fuskr.GetLinks(url); 294 | 295 | expect(links).toEqual(jasmine.any(Array)); 296 | expect(links.length).toEqual(130); 297 | expect(links[0]).toEqual('http://domain.com/path/file/aandcandc.jpg'); 298 | expect(links[1]).toEqual('http://domain.com/path/file/aanddandd.jpg'); 299 | expect(links[2]).toEqual('http://domain.com/path/file/aandeande.jpg'); 300 | expect(links[3]).toEqual('http://domain.com/path/file/aandfandf.jpg'); 301 | expect(links[4]).toEqual('http://domain.com/path/file/aandgandg.jpg'); 302 | expect(links[5]).toEqual('http://domain.com/path/file/bandcandc.jpg'); 303 | expect(links[6]).toEqual('http://domain.com/path/file/banddandd.jpg'); 304 | expect(links[7]).toEqual('http://domain.com/path/file/bandeande.jpg'); 305 | expect(links[8]).toEqual('http://domain.com/path/file/bandfandf.jpg'); 306 | expect(links[9]).toEqual('http://domain.com/path/file/bandgandg.jpg'); 307 | expect(links[49]).toEqual('http://domain.com/path/file/jandgandg.jpg'); 308 | expect(links[129]).toEqual('http://domain.com/path/file/zandgandg.jpg'); 309 | }); 310 | 311 | it('URL - Fuskable file {1} [0-9] [3-7] {0}', function () { 312 | var url = 'http://domain.com/path/file/{1}and[0-9]then[3-7]and{0}.jpg', 313 | links = Fuskr.GetLinks(url); 314 | 315 | expect(links).toEqual(jasmine.any(Array)); 316 | expect(links.length).toEqual(50); 317 | expect(links[0]).toEqual('http://domain.com/path/file/3and0then3and0.jpg'); 318 | expect(links[1]).toEqual('http://domain.com/path/file/4and0then4and0.jpg'); 319 | expect(links[2]).toEqual('http://domain.com/path/file/5and0then5and0.jpg'); 320 | expect(links[3]).toEqual('http://domain.com/path/file/6and0then6and0.jpg'); 321 | expect(links[4]).toEqual('http://domain.com/path/file/7and0then7and0.jpg'); 322 | expect(links[5]).toEqual('http://domain.com/path/file/3and1then3and1.jpg'); 323 | expect(links[6]).toEqual('http://domain.com/path/file/4and1then4and1.jpg'); 324 | expect(links[7]).toEqual('http://domain.com/path/file/5and1then5and1.jpg'); 325 | expect(links[8]).toEqual('http://domain.com/path/file/6and1then6and1.jpg'); 326 | expect(links[9]).toEqual('http://domain.com/path/file/7and1then7and1.jpg'); 327 | expect(links[49]).toEqual('http://domain.com/path/file/7and9then7and9.jpg'); 328 | }); 329 | 330 | it('URL - Fuskable file [0-9] {0} {0} {0} {0}', function () { 331 | var url = 'http://domain.com/path/file/[0-9]and{0}and{0}and{0}and{0}.jpg', 332 | links = Fuskr.GetLinks(url); 333 | 334 | expect(links).toEqual(jasmine.any(Array)); 335 | expect(links.length).toEqual(10); 336 | expect(links[0]).toEqual('http://domain.com/path/file/0and0and0and0and0.jpg'); 337 | expect(links[1]).toEqual('http://domain.com/path/file/1and1and1and1and1.jpg'); 338 | expect(links[2]).toEqual('http://domain.com/path/file/2and2and2and2and2.jpg'); 339 | expect(links[3]).toEqual('http://domain.com/path/file/3and3and3and3and3.jpg'); 340 | expect(links[4]).toEqual('http://domain.com/path/file/4and4and4and4and4.jpg'); 341 | expect(links[5]).toEqual('http://domain.com/path/file/5and5and5and5and5.jpg'); 342 | expect(links[6]).toEqual('http://domain.com/path/file/6and6and6and6and6.jpg'); 343 | expect(links[7]).toEqual('http://domain.com/path/file/7and7and7and7and7.jpg'); 344 | expect(links[8]).toEqual('http://domain.com/path/file/8and8and8and8and8.jpg'); 345 | expect(links[9]).toEqual('http://domain.com/path/file/9and9and9and9and9.jpg'); 346 | }); 347 | 348 | it('URL - Fuskable file [a-z] {0} {0} {0} {0}', function () { 349 | var url = 'http://domain.com/path/file/[a-z]and{0}and{0}and{0}and{0}.jpg', 350 | links = Fuskr.GetLinks(url); 351 | 352 | expect(links).toEqual(jasmine.any(Array)); 353 | expect(links.length).toEqual(26); 354 | expect(links[0]).toEqual('http://domain.com/path/file/aandaandaandaanda.jpg'); 355 | expect(links[1]).toEqual('http://domain.com/path/file/bandbandbandbandb.jpg'); 356 | expect(links[2]).toEqual('http://domain.com/path/file/candcandcandcandc.jpg'); 357 | expect(links[3]).toEqual('http://domain.com/path/file/danddanddanddandd.jpg'); 358 | expect(links[4]).toEqual('http://domain.com/path/file/eandeandeandeande.jpg'); 359 | expect(links[5]).toEqual('http://domain.com/path/file/fandfandfandfandf.jpg'); 360 | expect(links[6]).toEqual('http://domain.com/path/file/gandgandgandgandg.jpg'); 361 | expect(links[7]).toEqual('http://domain.com/path/file/handhandhandhandh.jpg'); 362 | expect(links[8]).toEqual('http://domain.com/path/file/iandiandiandiandi.jpg'); 363 | expect(links[25]).toEqual('http://domain.com/path/file/zandzandzandzandz.jpg'); 364 | }); 365 | }); 366 | -------------------------------------------------------------------------------- /Tests/fuskr/Fuskr.IsFuskable.spec.js: -------------------------------------------------------------------------------- 1 | /*globals describe:false, it:false, expect:false, Fuskr, jasmine */ 2 | 3 | describe('IsFuskable', function () { 4 | 5 | it('Function exists', function () { 6 | expect(Fuskr.IsFuskable).toEqual(jasmine.any(Function)); 7 | }); 8 | 9 | it('Null url', function () { 10 | var url; 11 | expect(Fuskr.IsFuskable(url)).toEqual(false); 12 | }); 13 | 14 | it('Empty string Url', function () { 15 | var url = ''; 16 | expect(Fuskr.IsFuskable(url)).toEqual(false); 17 | }); 18 | 19 | it('Object / Invalid parameter Url', function () { 20 | var url = { hey: 'ho' }; 21 | expect(Fuskr.IsFuskable(url)).toEqual(false); 22 | }); 23 | 24 | it('Array / Invalid parameter Url', function () { 25 | var url = ['string', 1234, { obj: 'ject' }]; 26 | expect(Fuskr.IsFuskable(url)).toEqual(false); 27 | }); 28 | 29 | it('URL - Unfuskable - no numbers', function () { 30 | var url = 'http://domain.com/path/file/'; 31 | expect(Fuskr.IsFuskable(url)).toEqual(false); 32 | }); 33 | 34 | //URL - Unfuskable (unclosed) 35 | it('URL - Unfuskable (unclosed)', function () { 36 | var url = 'http://domain.com/path/file/[0-9.jpg'; 37 | expect(Fuskr.IsFuskable(url)).toEqual(false); 38 | 39 | url = 'http://domain.com/path/file/[a-z.jpg'; 40 | expect(Fuskr.IsFuskable(url)).toEqual(false); 41 | }); 42 | 43 | //URL - Unfuskable (unopen) 44 | it('URL - Unfuskable (unopen)', function () { 45 | var url = 'http://domain.com/path/file/0-9].jpg'; 46 | expect(Fuskr.IsFuskable(url)).toEqual(false); 47 | 48 | url = 'http://domain.com/path/file/a-z].jpg'; 49 | expect(Fuskr.IsFuskable(url)).toEqual(false); 50 | }); 51 | 52 | //URL - Unfuskable (symbols) 53 | it('URL - Unfuskable (symbols)', function () { 54 | var url = 'http://domain.com/path/file/[0-$&].jpg'; 55 | expect(Fuskr.IsFuskable(url)).toEqual(false); 56 | 57 | url = 'http://domain.com/path/file/[a-$&].jpg'; 58 | expect(Fuskr.IsFuskable(url)).toEqual(false); 59 | }); 60 | 61 | //URL - Unfuskable (malformed) 62 | it('URL - Unfuskable (malformed)', function () { 63 | var url = 'http://domain.com/path/file/[0-45[.jpg'; 64 | expect(Fuskr.IsFuskable(url)).toEqual(false); 65 | 66 | url = 'http://domain.com/path/file/[a-z[.jpg'; 67 | expect(Fuskr.IsFuskable(url)).toEqual(false); 68 | }); 69 | 70 | it('URL - Fuskable file [0-9]/[a-z]', function () { 71 | var url = 'http://domain.com/path/file/[0-9].jpg'; 72 | expect(Fuskr.IsFuskable(url)).toEqual(true); 73 | 74 | url = 'http://domain.com/path/file/[a-z].jpg'; 75 | expect(Fuskr.IsFuskable(url)).toEqual(true); 76 | }); 77 | 78 | it('URL - Fuskable path with file [0-9]/[a-z]', function () { 79 | var url = 'http://domain.com/path[0-9]/file.jpg'; 80 | expect(Fuskr.IsFuskable(url)).toEqual(true); 81 | 82 | url = 'http://domain.com/path[a-z]/file.jpg'; 83 | expect(Fuskr.IsFuskable(url)).toEqual(true); 84 | }); 85 | 86 | it('URL - Fuskable path with no file [0-9]/[a-z]', function () { 87 | var url = 'http://domain.com/path[0-9]/'; 88 | expect(Fuskr.IsFuskable(url)).toEqual(true); 89 | 90 | url = 'http://domain.com/path[a-z]/'; 91 | expect(Fuskr.IsFuskable(url)).toEqual(true); 92 | }); 93 | 94 | it('URL - Fuskable path with no file [0-9]/[a-z] and no trailing slash', function () { 95 | var url = 'http://domain.com/path[0-9]'; 96 | expect(Fuskr.IsFuskable(url)).toEqual(true); 97 | 98 | url = 'http://domain.com/path[a-z]'; 99 | expect(Fuskr.IsFuskable(url)).toEqual(true); 100 | }); 101 | 102 | it('URL - Fuskable domain with file [0-9]/[a-z]', function () { 103 | var url = 'http://domain[0-9].com/path/file.jpg'; 104 | expect(Fuskr.IsFuskable(url)).toEqual(true); 105 | 106 | url = 'http://domain[a-z].com/path/file.jpg'; 107 | expect(Fuskr.IsFuskable(url)).toEqual(true); 108 | }); 109 | 110 | it('URL - Fuskable domain with path only [0-9]/[a-z]', function () { 111 | var url = 'http://domain[0-9].com/path'; 112 | expect(Fuskr.IsFuskable(url)).toEqual(true); 113 | 114 | url = 'http://domain[a-z].com/path'; 115 | expect(Fuskr.IsFuskable(url)).toEqual(true); 116 | }); 117 | 118 | /*********************************/ 119 | it('URL - Fuskable file - [0-9]/[a-z] (multiple fusks)', function () { 120 | var url = 'http://domain.com/path/file[0-9]another[0-9].jpg'; 121 | expect(Fuskr.IsFuskable(url)).toEqual(true); 122 | 123 | url = 'http://domain.com/path/file[a-z]another[a-z].jpg'; 124 | expect(Fuskr.IsFuskable(url)).toEqual(true); 125 | }); 126 | 127 | it('URL - Fuskable path with file [0-9]/[a-z] (multiple fusks)', function () { 128 | var url = 'http://domain.com/path[0-9]another[0-9]/file.jpg'; 129 | expect(Fuskr.IsFuskable(url)).toEqual(true); 130 | 131 | url = 'http://domain.com/path[a-z]another[a-z]/file.jpg'; 132 | expect(Fuskr.IsFuskable(url)).toEqual(true); 133 | }); 134 | 135 | it('URL - Fuskable path with no file [0-9]/[a-z] (multiple fusks)', function () { 136 | var url = 'http://domain.com/path[0-9]another[0-9]/'; 137 | expect(Fuskr.IsFuskable(url)).toEqual(true); 138 | 139 | url = 'http://domain.com/path[a-z]another[a-z]/'; 140 | expect(Fuskr.IsFuskable(url)).toEqual(true); 141 | }); 142 | 143 | it('URL - Fuskable path with no file [0-9]/[a-z] and no trailing slash (multiple fusks)', function () { 144 | var url = 'http://domain.com/path[0-9]another[0-9]'; 145 | expect(Fuskr.IsFuskable(url)).toEqual(true); 146 | 147 | url = 'http://domain.com/path[a-z]another[a-z]'; 148 | expect(Fuskr.IsFuskable(url)).toEqual(true); 149 | }); 150 | 151 | it('URL - Fuskable domain with file [0-9]/[a-z] (multiple fusks)', function () { 152 | var url = 'http://domain[0-9]another[0-9].com/path/file.jpg'; 153 | expect(Fuskr.IsFuskable(url)).toEqual(true); 154 | 155 | url = 'http://domain[a-z]another[a-z].com/path/file.jpg'; 156 | expect(Fuskr.IsFuskable(url)).toEqual(true); 157 | }); 158 | 159 | it('URL - Fuskable domain with path only [0-9]/[a-z] (multiple fusks)', function () { 160 | var url = 'http://domain[0-9]another[0-9].com/path'; 161 | expect(Fuskr.IsFuskable(url)).toEqual(true); 162 | 163 | url = 'http://domain[a-z]another[a-z].com/path'; 164 | expect(Fuskr.IsFuskable(url)).toEqual(true); 165 | }); 166 | 167 | /*********************************/ 168 | 169 | it('URL - Fuskable file - [0-9]/[a-z] (dual fusks after)', function () { 170 | var url = 'http://domain.com/path/file[0-9]another{0}.jpg'; 171 | expect(Fuskr.IsFuskable(url)).toEqual(true); 172 | 173 | url = 'http://domain.com/path/file[a-z]another{0}.jpg'; 174 | expect(Fuskr.IsFuskable(url)).toEqual(true); 175 | }); 176 | 177 | it('URL - Fuskable path with file [0-9]/[a-z] (dual fusks after)', function () { 178 | var url = 'http://domain.com/path[0-9]another{0}/file.jpg'; 179 | expect(Fuskr.IsFuskable(url)).toEqual(true); 180 | 181 | url = 'http://domain.com/path[a-z]another{0}/file.jpg'; 182 | expect(Fuskr.IsFuskable(url)).toEqual(true); 183 | }); 184 | 185 | it('URL - Fuskable path with no file [0-9/[a-z]] (dual fusks after)', function () { 186 | var url = 'http://domain.com/path[0-9]another{0}'; 187 | expect(Fuskr.IsFuskable(url)).toEqual(true); 188 | 189 | url = 'http://domain.com/path[a-z]another{0}'; 190 | expect(Fuskr.IsFuskable(url)).toEqual(true); 191 | }); 192 | 193 | it('URL - Fuskable path with no file [0-9]/[a-z] and no trailing slash (dual fusks after)', function () { 194 | var url = 'http://domain.com/path[0-9]another{0}'; 195 | expect(Fuskr.IsFuskable(url)).toEqual(true); 196 | 197 | url = 'http://domain.com/path[a-z]another{0}'; 198 | expect(Fuskr.IsFuskable(url)).toEqual(true); 199 | }); 200 | 201 | it('URL - Fuskable domain with file [0-9]/[a-z] (dual fusks after)', function () { 202 | var url = 'http://domain[0-9]another{0}.com/path/file.jpg'; 203 | expect(Fuskr.IsFuskable(url)).toEqual(true); 204 | 205 | url = 'http://domain[a-z]another{0}.com/path/file.jpg'; 206 | expect(Fuskr.IsFuskable(url)).toEqual(true); 207 | }); 208 | 209 | it('URL - Fuskable domain with path only [0-9]/[a-z] (dual fusks after)', function () { 210 | var url = 'http://domain[0-9]another{0}.com/path'; 211 | expect(Fuskr.IsFuskable(url)).toEqual(true); 212 | 213 | url = 'http://domain[a-z]another{0}.com/path'; 214 | expect(Fuskr.IsFuskable(url)).toEqual(true); 215 | }); 216 | 217 | it('URL - Fuskable file {0} (dual fusks before)', function () { 218 | var url = 'http://domain.com/path/file/{0}another[0-9].jpg'; 219 | expect(Fuskr.IsFuskable(url)).toEqual(true); 220 | 221 | url = 'http://domain.com/path/file/{0}another[a-z].jpg'; 222 | expect(Fuskr.IsFuskable(url)).toEqual(true); 223 | }); 224 | 225 | it('URL - Fuskable path with file {0} (dual fusk before)', function () { 226 | var url = 'http://domain.com/path{0}another[0-9]/file.jpg'; 227 | expect(Fuskr.IsFuskable(url)).toEqual(true); 228 | 229 | url = 'http://domain.com/path{0}another[a-z]/file.jpg'; 230 | expect(Fuskr.IsFuskable(url)).toEqual(true); 231 | }); 232 | 233 | it('URL - Fuskable path with no file {0} (dual fusk before)', function () { 234 | var url = 'http://domain.com/path{0}another[0-9]/'; 235 | expect(Fuskr.IsFuskable(url)).toEqual(true); 236 | 237 | url = 'http://domain.com/path{0}another[a-z]/'; 238 | expect(Fuskr.IsFuskable(url)).toEqual(true); 239 | }); 240 | 241 | it('URL - Fuskable path with no file {0} and no trailing slash (dual fusk before)', function () { 242 | var url = 'http://domain.com/path{4}another[0-9]'; 243 | expect(Fuskr.IsFuskable(url)).toEqual(true); 244 | 245 | url = 'http://domain.com/path{4}another[a-z]'; 246 | expect(Fuskr.IsFuskable(url)).toEqual(true); 247 | }); 248 | 249 | it('URL - Fuskable domain with file {0} (dual fusk before)', function () { 250 | var url = 'http://domain{1}another[0-9].com/path/file.jpg'; 251 | expect(Fuskr.IsFuskable(url)).toEqual(true); 252 | 253 | url = 'http://domain{1}another[a-z].com/path/file.jpg'; 254 | expect(Fuskr.IsFuskable(url)).toEqual(true); 255 | }); 256 | 257 | it('URL - Fuskable domain with path only {0} (dual fusk before)', function () { 258 | var url = 'http://domain{2}another[0-9].com/path'; 259 | expect(Fuskr.IsFuskable(url)).toEqual(true); 260 | 261 | url = 'http://domain{2}another[a-z].com/path'; 262 | expect(Fuskr.IsFuskable(url)).toEqual(true); 263 | }); 264 | }); 265 | -------------------------------------------------------------------------------- /Tests/fuskr/Fuskr.spec.js: -------------------------------------------------------------------------------- 1 | /*globals describe:false, it:false, expect:false, Fuskr, jasmine */ 2 | 3 | describe('Fuskr', function () { 4 | 5 | it('Fuskr Object Exists', function () { 6 | expect(Fuskr).toBeDefined(); 7 | expect(Fuskr).toEqual(jasmine.any(Object)); 8 | }); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /_locales/en_GB/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "ManifestName": { 3 | "message": "Fuskr", 4 | "description": "The title of the application, displayed in the web store." 5 | }, 6 | "ManifestDescription": { 7 | "message": "Create photo galleries from a single image based on the image name.", 8 | "description": "The description of the application, displayed in the web store." 9 | }, 10 | "ManifestLanguage": { 11 | "message": "en-GB", 12 | "description": "The BCP-47 language code." 13 | }, 14 | "Application_ContextMenu_Fusk": { 15 | "message": "Fusk" 16 | }, 17 | "Application_ContextMenu_10": { 18 | "message": "10" 19 | }, 20 | "Application_ContextMenu_20": { 21 | "message": "20" 22 | }, 23 | "Application_ContextMenu_50": { 24 | "message": "50" 25 | }, 26 | "Application_ContextMenu_100": { 27 | "message": "100" 28 | }, 29 | "Application_ContextMenu_200": { 30 | "message": "200" 31 | }, 32 | "Application_ContextMenu_500": { 33 | "message": "500" 34 | }, 35 | "Application_ContextMenu_Other": { 36 | "message": "Other" 37 | }, 38 | "Application_ContextMenu_Infinite": { 39 | "message": "Infinite" 40 | }, 41 | "Application_ContextMenu_CreateFromSelection": { 42 | "message": "Create from selection" 43 | }, 44 | "Application_ContextMenu_Manual": { 45 | "message": "Manual" 46 | }, 47 | "Application_ContextMenu_Help": { 48 | "message": "Help" 49 | }, 50 | "Application_ContextMenu_Recent": { 51 | "message": "Recent" 52 | }, 53 | "Application_ContextMenu_ClearRecentActivity": { 54 | "message": "Clear recent activity" 55 | }, 56 | "Application_ContextMenu_Options": { 57 | "message": "Options" 58 | }, 59 | "Application_ContextMenu_Saved": { 60 | "message": "Saved" 61 | }, 62 | "Application_Prompt_PleaseEnterTheUrl": { 63 | "message": "Please enter the url:" 64 | }, 65 | "Application_Prompt_HowMany": { 66 | "message": "How many?:" 67 | }, 68 | "Application_Prompt_NotAValidFusk": { 69 | "message": "This is not a valid fusk - https://example.com/[1-8].jpg" 70 | }, 71 | "Application_Prompt_NotAValidNumber": { 72 | "message": "This is not a valid number." 73 | }, 74 | "Images_Done": { 75 | "message": "Done!" 76 | }, 77 | "Images_Images": { 78 | "message": "{} Images" 79 | }, 80 | "Images_Loaded": { 81 | "message": "{} Loaded" 82 | }, 83 | "Images_Failed": { 84 | "message": "{} Failed" 85 | }, 86 | "Images_DownloadImages": { 87 | "message": "Download Images" 88 | }, 89 | "Images_SaveFusk": { 90 | "message": "Save Fusk" 91 | }, 92 | "Images_Top": { 93 | "message": "Top" 94 | }, 95 | "Images_Bottom": { 96 | "message": "Bottom" 97 | }, 98 | "Images_Resubmit": { 99 | "message": "Resubmit" 100 | }, 101 | "Images_ClickToOpenInNewTab": { 102 | "message": "Click to open this image in a new tab" 103 | }, 104 | "Images_ClickForNextPicture": { 105 | "message": "Click image for next picture" 106 | }, 107 | "Images_ResizeImagesToFullWidth": { 108 | "message": "Resize images to full width" 109 | }, 110 | "Images_ResizeImagesToFitOnPage": { 111 | "message": "Resize images to fit on page" 112 | }, 113 | "Images_ResizeImagesToFillPage": { 114 | "message": "Resize images to fill the page" 115 | }, 116 | "Images_ResizeImagesToThumbnails": { 117 | "message": "Resize images to thumbnails" 118 | }, 119 | "Images_GoToTop": { 120 | "message": "Go to the top of the page" 121 | }, 122 | "Images_GoToBottom": { 123 | "message": "Go to the bottom of the page" 124 | }, 125 | "Images_ShowImageUrls": { 126 | "message": "Show generated urls" 127 | }, 128 | "Images_ShowImagesInViewer": { 129 | "message": "Show images in viewer" 130 | }, 131 | "Images_ToggleBrokenImages": { 132 | "message": "Toggle broken images" 133 | }, 134 | "Images_RemoveBrokenImages": { 135 | "message": "Remove broken images" 136 | }, 137 | "Images_DownloadDialog_Title": { 138 | "message": "Download Images" 139 | }, 140 | "Images_DownloadDialog_Unique": { 141 | "message": "Unique" 142 | }, 143 | "Images_DownloadDialog_UniqueInfo": { 144 | "message": "To avoid duplication, the filename will be appended with a number eg " 145 | }, 146 | "Images_DownloadDialog_UniqueInfoFilename": { 147 | "message": "filename1 (1).jpg" 148 | }, 149 | "Images_DownloadDialog_Overwrite": { 150 | "message": "Overwrite" 151 | }, 152 | "Images_DownloadDialog_OverwriteInfo": { 153 | "message": "The existing file will be overwritten with the new file." 154 | }, 155 | "Images_DownloadDialog_Prompt": { 156 | "message": "Prompt" 157 | }, 158 | "Images_DownloadDialog_PromptInfo": { 159 | "message": "You will be prompted to choose a filename." 160 | }, 161 | "Images_DownloadDialog_ConflictQuestion": { 162 | "message": "In case of a conflict of filenames, what do you want to do?" 163 | }, 164 | "Images_SaveDialog_Title": { 165 | "message": "Save Fusk" 166 | }, 167 | "Images_SaveDialog_Question": { 168 | "message": "What name do you wish to give this fusk (defaults to this url if empty)?" 169 | }, 170 | "Options_Title": { 171 | "message": "Fuskr Options" 172 | }, 173 | "Options_Tab_Settings": { 174 | "message": "Settings" 175 | }, 176 | "Options_Tab_Foreground": { 177 | "message": "Foreground" 178 | }, 179 | "Options_Tab_History": { 180 | "message": "History" 181 | }, 182 | "Options_Tab_VersionHistory": { 183 | "message": "Version History" 184 | }, 185 | "Options_Tab_About": { 186 | "message": "About Fuskr" 187 | }, 188 | "Options_DarkMode": { 189 | "message": "Show Fuskr with a dark background" 190 | }, 191 | "Options_DarkMode_Description": { 192 | "message": "Changes the background of image sets to a darker colour. Will take effect once you refresh pages." 193 | }, 194 | "Options_OpenNewFusksInForeground": { 195 | "message": "Open new fusks in the foreground" 196 | }, 197 | "Options_OpenNewFusksInForeground_Description": { 198 | "message": "When you create a new gallery, you may not wish to open it in the foreground. By unchecking this option, any new gallery will appear in the background instead." 199 | }, 200 | "Options_History_Keep": { 201 | "message": "Keep a recent history of fusks" 202 | }, 203 | "Options_History_Keep_Description": { 204 | "message": "Every time you create a new gallery, the extension records the created url (up to the last 10). Clicking on the url will re-generate that gallery." 205 | }, 206 | "Options_History_Keep_Limit": { 207 | "message": "

Last fusks. Fuskr only displays the last 10. Click to revisit.

" 208 | }, 209 | "Options_History_NoRecent" : { 210 | "message": "There are currently no records of any galleries created. When you start using Fuskr, the gallery links will appear below!" 211 | }, 212 | "Options_Credits_Title": { 213 | "message": "Credits" 214 | }, 215 | "Options_Credits_Description": { 216 | "message": "

Fuskr (originally called ChromeFusk) was created in 2011 by Dan Atkinson with oodles of help from Jonathon Bolster.

The Fuskr logos were created by Richard Stelmach of Creative Binge.

" 217 | }, 218 | "Options_About_Description": { 219 | "message": "

Fuskr was borne of a frustation that my favourite Firefox plugin Firefusk (dead link) created by Jonathan Soma was not available for Chrome.

Once I had stopped crying, I remembered that I was a developer and could do almost anything if I put my mind to it.

Then, I remembered that I was actually a pretty rubbish developer and asked for help from Jonathon Bolster.

Jonathon gratefully helped and actually rewrote pretty much all of the code for Fuskr to make it so damned extensible and also provided lots of unit tests for it.

" 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | /*globals module:false, require */ 2 | module.exports = function (grunt) { 3 | 'use strict'; 4 | 5 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 6 | 7 | const sass = require('node-sass'); 8 | 9 | grunt.initConfig({ 10 | pkg: grunt.file.readJSON('package.json'), 11 | 12 | config: { 13 | manifest: grunt.file.readJSON('manifest.json'), 14 | dist: 'dist', 15 | app: { 16 | src: [ 17 | 'Scripts/fuskr.js', 18 | 'Scripts/app/app.js', 19 | 'Scripts/app/**/*.js' 20 | ] 21 | }, 22 | background: { 23 | src: [ 24 | 'Scripts/fuskr.js', 25 | 'Scripts/background.js' 26 | ] 27 | }, 28 | optionsjs: { 29 | src: [ 30 | 'Scripts/options.js' 31 | ] 32 | }, 33 | vendor: { 34 | src: [ 35 | 'node_modules/angular/angular.js', 36 | 'node_modules/angular-sanitize/angular-sanitize.js', 37 | 'node_modules/file-saver/dist/FileSaver.js', 38 | 'node_modules/jszip/dist/jszip.js' 39 | ] 40 | }, 41 | styles: { 42 | src: ['Styles/styles.scss'] 43 | }, 44 | html: { 45 | src: ['Html/**/*'] 46 | }, 47 | images: { 48 | src: ['Images/**/*'] 49 | }, 50 | chromeFiles: { 51 | src: [ 52 | 'manifest.json', 53 | 'README.markdown', 54 | 'LICENCE', 55 | '_locales/**/*' 56 | ] 57 | } 58 | }, 59 | sass: { 60 | app: { 61 | files: [{ 62 | expand: true, 63 | src: ['<%= config.styles.src %>'], 64 | dest: '<%= config.dist %>/Styles', 65 | ext: '.css', 66 | flatten: true 67 | }] 68 | }, 69 | options: { 70 | implementation: sass 71 | } 72 | }, 73 | concat: { 74 | options: { 75 | sourceMap: true 76 | }, 77 | app: { 78 | src: ['<%= config.app.src %>'], 79 | dest: '<%= config.dist %>/Scripts/app.js' 80 | }, 81 | background: { 82 | src: ['<%= config.background.src %>'], 83 | dest: '<%= config.dist %>/Scripts/background.js' 84 | }, 85 | optionsjs: { 86 | src: ['<%= config.optionsjs.src %>'], 87 | dest: '<%= config.dist %>/Scripts/options.js' 88 | }, 89 | vendor: { 90 | src: ['<%= config.vendor.src %>'], 91 | dest: '<%= config.dist %>/Scripts/vendor.js' 92 | } 93 | }, 94 | copy: { 95 | html: { 96 | expand: true, 97 | src: ['<%= config.html.src %>'], 98 | dest: '<%= config.dist %>' 99 | }, 100 | images: { 101 | expand: true, 102 | src: ['<%= config.images.src %>'], 103 | dest: '<%= config.dist %>' 104 | }, 105 | chromeFiles: { 106 | expand: true, 107 | src: ['<%= config.chromeFiles.src %>'], 108 | dest: '<%= config.dist %>' 109 | } 110 | }, 111 | watch: { 112 | styles: { 113 | files: ['<%= config.styles.src %>'], 114 | tasks: ['compile:styles'] 115 | }, 116 | vendorStyles: { 117 | files: ['<%= config.vendorStyles.src %>'], 118 | tasks: ['compile:vendorStyles'] 119 | }, 120 | app: { 121 | files: ['<%= concat.app.src %>'], 122 | tasks: ['compile:app', 'karma:release'] 123 | }, 124 | background: { 125 | files: ['<%= concat.background.src %>'], 126 | tasks: ['compile:background'] 127 | }, 128 | optionsjs: { 129 | files: ['<%= concat.optionsjs.src %>'], 130 | tasks: ['compile:optionsjs'] 131 | }, 132 | vendor: { 133 | files: ['<%= concat.vendor.src %>'], 134 | tasks: ['compile:vendor'] 135 | }, 136 | html: { 137 | files: ['<%= config.html.src %>'], 138 | tasks: ['copy:html'] 139 | }, 140 | images: { 141 | files: ['<%= config.images.src %>'], 142 | tasks: ['copy:images'] 143 | }, 144 | tests: { 145 | files: ['Tests/**/*.spec.js'], 146 | tasks: ['karma:release'] 147 | }, 148 | chromeFiles: { 149 | files: ['<%= config.chromeFiles.src %>'], 150 | tasks: ['copy:chromeFiles'] 151 | }, 152 | gruntFile: { 153 | files: ['gruntfile.js'], 154 | tasks: ['build'] 155 | } 156 | }, 157 | concurrent: { 158 | dev: { 159 | tasks: ['build', 'watch'], 160 | options: { 161 | logConcurrentOutput: true 162 | } 163 | } 164 | }, 165 | karma: { 166 | release: { 167 | options: { 168 | singleRun: true, 169 | browsers: ['PhantomJS'], 170 | frameworks: ['jasmine'], 171 | files: [ 172 | '<%= config.dist %>/Scripts/vendor.js', 173 | '<%= config.dist %>/Scripts/app.js', 174 | 'Tests/lib/**/*.js', 175 | 'Tests/**/*.spec.js' 176 | ] 177 | } 178 | }, 179 | dev: { 180 | options: { 181 | singleRun: true, 182 | browsers: ['PhantomJS'], 183 | frameworks: ['jasmine'], 184 | files: [ 185 | 'node_modules/angular/angular.js', 186 | 'node_modules/angular-sanitize/angular-sanitize.js', 187 | 'node_modules/file-saver/dist/FileSaver.js', 188 | 'node_modules/jszip/dist/jszip.js', 189 | 'Scripts/Fuskr.js', 190 | 'Scripts/app/*.js', 191 | 'Tests/lib/**/*.js', 192 | 'Tests/**/*.spec.js' 193 | ] 194 | } 195 | } 196 | }, 197 | clean: { 198 | vendorStyles: ['dist/Styles/vendor'], 199 | dist: ['<%= config.dist %>/**/*'], 200 | removeSourceMaps: ['<%= config.dist %>/**/*.css.map', '<%= config.dist %>/**/*.js.map'] 201 | }, 202 | compress: { 203 | release: { 204 | options: { 205 | archive: 'fuskr-<%= config.manifest.version %>.zip' 206 | }, 207 | expand: true, 208 | cwd: 'dist/', 209 | src: ['**/*'], 210 | dest: '/' 211 | } 212 | } 213 | }); 214 | 215 | grunt.registerTask('incrementVersion', function() { 216 | const manifestFilename = 'manifest.json'; 217 | const manifestVersionRegex = /(\d+\.\d+\.)(\d+)/; 218 | 219 | //Read our manifest file in. 220 | var manifestJson = grunt.file.readJSON(manifestFilename); 221 | 222 | if (!manifestJson || !manifestJson.version.match(manifestVersionRegex)) { 223 | return; 224 | } 225 | 226 | var getVersionName = function(versionNumber) { 227 | var date = new Date(); 228 | var returnValue = "v" + versionNumber + " built at: "; 229 | 230 | var day = date.getFullYear() + "-" + (date.getMonth() < 9 ? "0" : "") + (date.getMonth() + 1) + "-" + (date.getDate() < 10 ? "0" : "") + date.getDate(); 231 | returnValue += day + " "; 232 | 233 | var time = (date.getHours() < 10 ? "0" : "") + date.getHours() + ":" + (date.getMinutes() < 10 ? "0" : "") + date.getMinutes() + ":" + (date.getSeconds() < 10 ? "0" : "") + date.getSeconds() 234 | returnValue += time; 235 | 236 | return returnValue; 237 | }; 238 | 239 | //Grab the build number and increment it. Then update the version name. 240 | var manifestBuildNumber = parseInt(manifestJson.version.match(manifestVersionRegex)[2], 10); 241 | manifestJson.version = manifestJson.version.match(manifestVersionRegex)[1] + (++manifestBuildNumber); 242 | manifestJson.version_name = getVersionName(manifestJson.version); 243 | 244 | grunt.log.writeln('Manifest version is now', manifestJson.version); 245 | grunt.log.writeln('Manifest version name is now', manifestJson.version_name); 246 | 247 | //Write the manifest back in and format it. 248 | grunt.file.write(manifestFilename, JSON.stringify(manifestJson, null, 4)); 249 | }); 250 | 251 | grunt.registerTask('compile', ['compile:app', 'compile:background', 'compile:optionsjs', 'compile:vendor', 'compile:styles']); 252 | grunt.registerTask('compile:app', ['concat:app']); 253 | grunt.registerTask('compile:background', ['concat:background']); 254 | grunt.registerTask('compile:optionsjs', ['concat:optionsjs']); 255 | grunt.registerTask('compile:vendor', ['concat:vendor']); 256 | grunt.registerTask('compile:styles', ['sass:app']); 257 | 258 | grunt.registerTask('default', ['concurrent:dev']); 259 | grunt.registerTask('build', ['clean:dist', 'compile', 'incrementVersion', 'copy', 'karma:release']); 260 | grunt.registerTask('release', ['build', 'compress:release']); 261 | grunt.registerTask('test', ['karma:dev']); 262 | 263 | grunt.registerTask('travis:build', ['clean:dist', 'compile', 'copy']); 264 | grunt.registerTask('travis:test', ['karma:release']); 265 | }; 266 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fuskr @ GitHub 6 | 7 | 30 | 34 | 40 | 41 | 42 | Fork me on GitHub 43 | 44 |
45 | 46 |
47 | 48 | 49 | 50 | 51 |
52 | 53 |

54 | Fuskr 55 | by 56 | Dan Atkinson 57 | and 58 | Jonathon Bolster 59 | 60 |

61 | 62 |
63 |

Fuskr is a Google Chome image gallery extension and is a port of FireFusk.

64 |
65 | 66 | 67 | 68 |

Authors

69 |

70 | Dan Atkinson (fuskr@dan-atkinson.com) 71 |
72 | Jonathon Bolster 73 |

74 | 75 |

Design

76 |

Logos designed by Richard Stelmach

77 | 78 |

How to use

79 |
    80 |
  1. Go ahead and install Fuskr from the Google Chrome extensions gallery.
  2. 81 |
  3. On your desired image, right click and choose 'Fusk'.
  4. 82 |
  5. 83 | Choose which direction: 84 |
      85 |
    • '+/-' will return a gallery with images that come before and after it.
    • 86 |
    • '+' will return a gallery with images that only come after it.
    • 87 |
    • '-' will return a gallery with images that only come before it.
    • 88 |
    89 |
  6. 90 |
  7. Now choose how large you want your gallery to be - 10/20/50/100/200/500 or 'Other' (you choose!).
  8. 91 |
  9. Your gallery will appear in the tab next to your current one. If an image isn't returned (404 or some other error), it will be hidden from view, but you can toggle that too!
  10. 92 |
93 | 94 |

Why Fuskr?

95 |

From the 'Fusker' Wikipedia article - A Fusker is a type of website or utility that extracts images from a web page, typically from free hosted galleries. Fusker software allows users to identify a sequence of images with a single pattern, for example: `http://www.example.com/images/pic[1-16].jpg`.

96 | 97 |

Version History

98 | 248 | 249 |

Contact

250 |

251 | Dan Atkinson (fuskr@dan-atkinson.com) 252 |
253 | Jonathon Bolster 254 |

255 | 256 |

Download

257 |

258 | You can download this project in either 259 | zip or 260 | tar formats. 261 |

262 |

You can also clone the project with Git 263 | by running: 264 |

$ git clone git://github.com/DanAtkinson/Fuskr
265 |

266 | 267 | 270 |
271 | 272 | 283 | 284 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_ManifestName__", 4 | "description": "__MSG_ManifestDescription__", 5 | "version": "4.0.84", 6 | "version_name": "v4.0.84 built at: 2019-07-11 16:36:15", 7 | "default_locale": "en_GB", 8 | "author": "Dan Atkinson and Jonathon Bolster", 9 | "background": { 10 | "scripts": [ 11 | "Scripts/background.js" 12 | ], 13 | "persistent": false 14 | }, 15 | "options_ui": { 16 | "page": "Html/options.html", 17 | "open_in_tab": false 18 | }, 19 | "incognito": "split", 20 | "omnibox": { 21 | "keyword": "fuskr" 22 | }, 23 | "permissions": [ 24 | "tabs", 25 | "downloads", 26 | "http://*/*", 27 | "https://*/*", 28 | "contextMenus", 29 | "activeTab", 30 | "storage" 31 | ], 32 | "minimum_chrome_version": "73", 33 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self';", 34 | "homepage_url": "https://danatkinson.github.io/Fuskr/", 35 | "icons": { 36 | "16": "Images/16x16.png", 37 | "48": "Images/48x48.png", 38 | "128": "Images/128x128.png" 39 | } 40 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuskr", 3 | "description": "Fuskr is an extension for Google Chrome which allows users to create image galleries from a single image based on their filename.", 4 | "version": "3.2.0", 5 | "main": "index.htm", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "angular": "^1.8.0", 9 | "angular-sanitize": "^1.7.8", 10 | "file-saver": "^2.0.2", 11 | "grunt": "^1.5.3", 12 | "grunt-concurrent": "~2.3.1", 13 | "grunt-contrib-clean": "~2.0.0", 14 | "grunt-contrib-compress": "~1.4.3", 15 | "grunt-contrib-concat": "~1.0.1", 16 | "grunt-contrib-copy": "~1.0.0", 17 | "grunt-contrib-watch": "^1.1.0", 18 | "grunt-html": "~10.1.0", 19 | "grunt-karma": "^3.0.2", 20 | "grunt-sass": "~3.0.2", 21 | "jasmine-core": "~3.3.0", 22 | "jshint": "^2.13.6", 23 | "jszip": "^3.8.0", 24 | "karma": "^6.3.16", 25 | "karma-jasmine": "~2.0.1", 26 | "karma-phantomjs-launcher": "~1.0.4", 27 | "matchdep": "~2.0.0", 28 | "node-sass": "^7.0.3" 29 | }, 30 | "scripts": { 31 | "test": "grunt travis:test" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/DanAtkinson/Fuskr.git" 36 | }, 37 | "keywords": [ 38 | "image", 39 | "slideshow" 40 | ], 41 | "author": "Dan Atkinson and Jonathon Bolster", 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/DanAtkinson/Fuskr/issues" 45 | }, 46 | "homepage": "https://github.com/DanAtkinson/Fuskr" 47 | } 48 | --------------------------------------------------------------------------------