├── v2 ├── data │ ├── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ ├── 512.png │ │ └── 64.png │ ├── window │ │ ├── working.gif │ │ ├── fontello.woff │ │ ├── fontello.css │ │ ├── persist.js │ │ ├── elements.js │ │ ├── external.js │ │ ├── index.css │ │ ├── index.html │ │ └── index.js │ └── options │ │ ├── index.js │ │ └── index.html ├── manifest.json ├── common.js └── LICENSE ├── v3 ├── data │ ├── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ ├── 512.png │ │ └── 64.png │ └── window │ │ ├── fonts │ │ ├── fontello.woff │ │ └── fontello.css │ │ ├── images │ │ └── working.gif │ │ ├── persist.js │ │ ├── elements.js │ │ ├── external.js │ │ ├── index.css │ │ ├── index.html │ │ └── index.js ├── manifest.json ├── worker.js └── LICENSE └── .github └── FUNDING.yml /v2/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/icons/128.png -------------------------------------------------------------------------------- /v2/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/icons/16.png -------------------------------------------------------------------------------- /v2/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/icons/256.png -------------------------------------------------------------------------------- /v2/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/icons/32.png -------------------------------------------------------------------------------- /v2/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/icons/48.png -------------------------------------------------------------------------------- /v2/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/icons/512.png -------------------------------------------------------------------------------- /v2/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/icons/64.png -------------------------------------------------------------------------------- /v3/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/icons/128.png -------------------------------------------------------------------------------- /v3/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/icons/16.png -------------------------------------------------------------------------------- /v3/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/icons/256.png -------------------------------------------------------------------------------- /v3/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/icons/32.png -------------------------------------------------------------------------------- /v3/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/icons/48.png -------------------------------------------------------------------------------- /v3/data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/icons/512.png -------------------------------------------------------------------------------- /v3/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/icons/64.png -------------------------------------------------------------------------------- /v2/data/window/working.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/window/working.gif -------------------------------------------------------------------------------- /v2/data/window/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v2/data/window/fontello.woff -------------------------------------------------------------------------------- /v3/data/window/fonts/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/window/fonts/fontello.woff -------------------------------------------------------------------------------- /v3/data/window/images/working.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/bulk-media-downloader/HEAD/v3/data/window/images/working.gif -------------------------------------------------------------------------------- /v2/data/options/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function save() { 4 | chrome.storage.local.set({ 5 | notify: document.getElementById('notify').checked, 6 | faqs: document.getElementById('faqs').checked 7 | }, () => { 8 | const status = document.getElementById('status'); 9 | status.textContent = 'Options saved.'; 10 | setTimeout(() => status.textContent = '', 750); 11 | }); 12 | } 13 | 14 | function restore() { 15 | chrome.storage.local.get({ 16 | notify: true, 17 | faqs: true 18 | }, prefs => { 19 | document.getElementById('notify').checked = prefs.notify; 20 | document.getElementById('faqs').checked = prefs.faqs; 21 | }); 22 | } 23 | document.addEventListener('DOMContentLoaded', restore); 24 | document.getElementById('save').addEventListener('click', save); 25 | -------------------------------------------------------------------------------- /v2/data/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My Test Extension Options 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 |

27 | 28 | 29 |

30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: webextension 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /v2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bulk Media Downloader", 3 | "description": "Grab and download media (image and video) sources by monitoring network (like FlashGot)", 4 | "version": "0.2.2", 5 | "manifest_version": 2, 6 | "permissions": [ 7 | "storage", 8 | "", 9 | "webRequest", 10 | "downloads", 11 | "notifications", 12 | "contextMenus" 13 | ], 14 | "background": { 15 | "scripts": [ 16 | "common.js" 17 | ] 18 | }, 19 | "browser_action": {}, 20 | "homepage_url": "https://add0n.com/media-tools.html", 21 | "icons": { 22 | "16": "data/icons/16.png", 23 | "32": "data/icons/32.png", 24 | "48": "data/icons/48.png", 25 | "64": "data/icons/64.png", 26 | "128": "data/icons/128.png", 27 | "256": "data/icons/256.png", 28 | "512": "data/icons/512.png" 29 | }, 30 | "web_accessible_resources": [ 31 | "data/inject/index.html" 32 | ], 33 | "options_ui": { 34 | "page": "data/options/index.html", 35 | "chrome_style": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /v3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bulk Media Downloader", 3 | "description": "Grab and download media (image and video) sources by monitoring network (like FlashGot)", 4 | "version": "0.3.2", 5 | "manifest_version": 3, 6 | "permissions": [ 7 | "storage", 8 | "webRequest", 9 | "downloads", 10 | "notifications", 11 | "contextMenus" 12 | ], 13 | "host_permissions": [ 14 | "*://*/*" 15 | ], 16 | "background": { 17 | "service_worker": "worker.js", 18 | "scripts": ["worker.js"] 19 | }, 20 | "action": {}, 21 | "homepage_url": "https://webextension.org/listing/bulk-media-downloader.html", 22 | "icons": { 23 | "16": "/data/icons/16.png", 24 | "32": "/data/icons/32.png", 25 | "48": "/data/icons/48.png", 26 | "64": "/data/icons/64.png", 27 | "128": "/data/icons/128.png", 28 | "256": "/data/icons/256.png", 29 | "512": "/data/icons/512.png" 30 | }, 31 | "commands": { 32 | "_execute_action": {} 33 | }, 34 | "browser_specific_settings": { 35 | "gecko": { 36 | "id": "{72b2e02b-3a71-4895-886c-fd12ebe36ba3}", 37 | "strict_min_version": "128.0" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /v2/data/window/fontello.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'fontello'; 3 | src: url('fontello.woff?42558604') format('woff'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | [class^="icon-"]:before, [class*=" icon-"]:before { 9 | font-family: "fontello"; 10 | font-style: normal; 11 | font-weight: normal; 12 | speak: none; 13 | 14 | display: inline-block; 15 | text-decoration: inherit; 16 | width: 1em; 17 | margin-right: .2em; 18 | text-align: center; 19 | /* opacity: .8; */ 20 | 21 | /* For safety - reset parent styles, that can break glyph codes*/ 22 | font-variant: normal; 23 | text-transform: none; 24 | 25 | /* fix buttons height, for twitter bootstrap */ 26 | line-height: 1em; 27 | 28 | /* Animation center compensation - margins should be symmetric */ 29 | /* remove if not needed */ 30 | margin-left: .2em; 31 | 32 | /* you can be more comfortable with increased icons size */ 33 | /* font-size: 120%; */ 34 | 35 | /* Font smoothing. That was taken from TWBS */ 36 | -webkit-font-smoothing: antialiased; 37 | -moz-osx-font-smoothing: grayscale; 38 | 39 | /* Uncomment for 3D effect */ 40 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 41 | } 42 | 43 | .icon-check:before { content: '\e800'; } /* '' */ 44 | .icon-check-empty:before { content: '\e801'; } /* '' */ 45 | -------------------------------------------------------------------------------- /v3/data/window/fonts/fontello.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'fontello'; 3 | src: url('fontello.woff?42558604') format('woff'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | [class^="icon-"]:before, [class*=" icon-"]:before { 9 | font-family: "fontello"; 10 | font-style: normal; 11 | font-weight: normal; 12 | speak: none; 13 | 14 | display: inline-block; 15 | text-decoration: inherit; 16 | width: 1em; 17 | margin-right: .2em; 18 | text-align: center; 19 | /* opacity: .8; */ 20 | 21 | /* For safety - reset parent styles, that can break glyph codes*/ 22 | font-variant: normal; 23 | text-transform: none; 24 | 25 | /* fix buttons height, for twitter bootstrap */ 26 | line-height: 1em; 27 | 28 | /* Animation center compensation - margins should be symmetric */ 29 | /* remove if not needed */ 30 | margin-left: .2em; 31 | 32 | /* you can be more comfortable with increased icons size */ 33 | /* font-size: 120%; */ 34 | 35 | /* Font smoothing. That was taken from TWBS */ 36 | -webkit-font-smoothing: antialiased; 37 | -moz-osx-font-smoothing: grayscale; 38 | 39 | /* Uncomment for 3D effect */ 40 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 41 | } 42 | 43 | .icon-check:before { content: '\e800'; } /* '' */ 44 | .icon-check-empty:before { content: '\e801'; } /* '' */ 45 | -------------------------------------------------------------------------------- /v2/data/window/persist.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2017 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: http://add0n.com/media-tools.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ */ 9 | 10 | /* globals $ */ 11 | 'use strict'; 12 | 13 | const persist = {}; 14 | 15 | chrome.storage.local.get({ 16 | 'external': 'idm', 17 | 'details-header': false, 18 | 'details-external': false, 19 | 'quotes': false 20 | }, prefs => { 21 | $.external.select.value = prefs.external; 22 | $.external.select.dispatchEvent(new Event('change')); 23 | $.header.details.open = prefs['details-header']; 24 | $.external.details.open = prefs['details-external']; 25 | $.external.quotes.checked = prefs.quotes; 26 | }); 27 | 28 | $.header.summary.addEventListener('click', () => { 29 | persist.save('details-header', !$.header.details.open); 30 | }); 31 | $.external.summary.addEventListener('click', () => { 32 | persist.save('details-external', !$.external.details.open); 33 | }); 34 | $.external.quotes.addEventListener('change', ({target}) => { 35 | persist.save('quotes', target.checked); 36 | }); 37 | 38 | $.external.select.addEventListener('change', ({target}) => { 39 | persist.save('external', target.value); 40 | }); 41 | 42 | persist.save = (id, value) => chrome.storage.local.set({ 43 | [id]: value 44 | }); 45 | -------------------------------------------------------------------------------- /v3/data/window/persist.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2025 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: https://webextension.org/listing/bulk-media-downloader.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ 9 | */ 10 | 11 | /* globals $ */ 12 | 'use strict'; 13 | 14 | const persist = {}; 15 | 16 | chrome.storage.local.get({ 17 | 'external': 'idm', 18 | 'details-header': false, 19 | 'details-external': false, 20 | 'quotes': false 21 | }, prefs => { 22 | $.external.select.value = prefs.external; 23 | $.external.select.dispatchEvent(new Event('change')); 24 | $.header.details.open = prefs['details-header']; 25 | $.external.details.open = prefs['details-external']; 26 | $.external.quotes.checked = prefs.quotes; 27 | }); 28 | 29 | $.header.summary.addEventListener('click', () => { 30 | persist.save('details-header', !$.header.details.open); 31 | }); 32 | $.external.summary.addEventListener('click', () => { 33 | persist.save('details-external', !$.external.details.open); 34 | }); 35 | $.external.quotes.addEventListener('change', ({target}) => { 36 | persist.save('quotes', target.checked); 37 | }); 38 | 39 | $.external.select.addEventListener('change', ({target}) => { 40 | persist.save('external', target.value); 41 | }); 42 | 43 | persist.save = (id, value) => chrome.storage.local.set({ 44 | [id]: value 45 | }); 46 | -------------------------------------------------------------------------------- /v2/data/window/elements.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2017 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: http://add0n.com/media-tools.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ */ 9 | 10 | 'use strict'; 11 | 12 | var $ = { 13 | stats: document.getElementById('stats'), 14 | head: document.getElementById('links-head'), 15 | links: document.getElementById('links'), 16 | tools: document.getElementById('tools'), 17 | tr: document.querySelector('#tr tr'), 18 | tbody: document.querySelector('#links tbody'), 19 | filter: document.querySelector('[data-cmd="toggle-filter"] span'), 20 | size: document.querySelector('[data-cmd="toggle-size"] span'), 21 | pause: document.querySelector('[data-cmd="pause"]'), 22 | }; 23 | 24 | $.header = { 25 | details: document.querySelector('details'), 26 | summary: document.querySelector('summary') 27 | }; 28 | 29 | $.filters = { 30 | parent: document.getElementById('filters'), 31 | all: document.querySelector('#filters [value="all_files"]'), 32 | images: document.querySelector('#filters [value="images"]'), 33 | videos: document.querySelector('#filters [value="videos"]'), 34 | audios: document.querySelector('#filters [value="audios"]'), 35 | applications: document.querySelector('#filters [value="applications"]'), 36 | documents: document.querySelector('#filters [value="documents"]'), 37 | tab: document.querySelector('#filters [value="tab"]'), 38 | archives: document.querySelector('#filters [value="archives"]'), 39 | regexp: document.querySelector('#filters [type=text]') 40 | }; 41 | 42 | $.buttons = { 43 | browser: document.querySelector('[data-cmd="download-browser"]'), 44 | links: document.querySelector('[data-cmd="copy-links"]') 45 | }; 46 | 47 | $.external = { 48 | summary: document.querySelector(('#external summary')), 49 | details: document.querySelector(('#external details')), 50 | quotes: document.getElementById('quotes'), 51 | path: document.getElementById('external-path'), 52 | args: document.getElementById('external-args'), 53 | select: document.querySelector('#external select'), 54 | run: document.querySelector('#external [data-cmd=run]'), 55 | save: document.querySelector('#external [data-cmd=save]'), 56 | actions: document.getElementById('external-actions') 57 | }; 58 | -------------------------------------------------------------------------------- /v3/data/window/elements.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2025 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: https://webextension.org/listing/bulk-media-downloader.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ 9 | */ 10 | 11 | 'use strict'; 12 | 13 | // eslint-disable-next-line no-var 14 | var $ = { 15 | stats: document.getElementById('stats'), 16 | head: document.getElementById('links-head'), 17 | links: document.getElementById('links'), 18 | tools: document.getElementById('tools'), 19 | tr: document.querySelector('#tr tr'), 20 | tbody: document.querySelector('#links tbody'), 21 | filter: document.querySelector('[data-cmd="toggle-filter"] span'), 22 | size: document.querySelector('[data-cmd="toggle-size"] span'), 23 | pause: document.querySelector('[data-cmd="pause"]') 24 | }; 25 | 26 | $.header = { 27 | details: document.querySelector('details'), 28 | summary: document.querySelector('summary') 29 | }; 30 | 31 | $.filters = { 32 | parent: document.getElementById('filters'), 33 | all: document.querySelector('#filters [value="all_files"]'), 34 | images: document.querySelector('#filters [value="images"]'), 35 | videos: document.querySelector('#filters [value="videos"]'), 36 | audios: document.querySelector('#filters [value="audios"]'), 37 | applications: document.querySelector('#filters [value="applications"]'), 38 | documents: document.querySelector('#filters [value="documents"]'), 39 | tab: document.querySelector('#filters [value="tab"]'), 40 | archives: document.querySelector('#filters [value="archives"]'), 41 | regexp: document.querySelector('#filters [type=text]') 42 | }; 43 | 44 | $.buttons = { 45 | browser: document.querySelector('[data-cmd="download-browser"]'), 46 | links: document.querySelector('[data-cmd="copy-links"]') 47 | }; 48 | 49 | $.external = { 50 | summary: document.querySelector(('#external summary')), 51 | details: document.querySelector(('#external details')), 52 | quotes: document.getElementById('quotes'), 53 | path: document.getElementById('external-path'), 54 | args: document.getElementById('external-args'), 55 | select: document.querySelector('#external select'), 56 | run: document.querySelector('#external [data-cmd=run]'), 57 | save: document.querySelector('#external [data-cmd=save]'), 58 | actions: document.getElementById('external-actions') 59 | }; 60 | -------------------------------------------------------------------------------- /v3/worker.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2025 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: https://webextension.org/listing/bulk-media-downloader.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ 9 | */ 10 | 11 | 'use strict'; 12 | 13 | chrome.action.onClicked.addListener(async tab => { 14 | const resp = await chrome.runtime.sendMessage({ 15 | cmd: 'bring-to-front' 16 | }).catch(() => {}); 17 | 18 | if (resp === true) { 19 | chrome.tabs.sendMessage(tab.id, { 20 | cmd: 'update-id', 21 | id: tab.id 22 | }); 23 | } 24 | else { 25 | const win = await chrome.windows.getCurrent(); 26 | 27 | const prefs = await chrome.storage.local.get({ 28 | width: 800, 29 | height: 600 30 | }); 31 | chrome.windows.create({ 32 | url: 'data/window/index.html?tabId=' + tab.id, 33 | width: prefs.width, 34 | height: prefs.height, 35 | left: win.left + Math.round((win.width - prefs.width) / 2), 36 | top: win.top + Math.round((win.height - prefs.height) / 2), 37 | type: 'popup' 38 | }); 39 | } 40 | }); 41 | 42 | chrome.runtime.onMessage.addListener((request, sender) => { 43 | if (request.cmd === 'focus') { 44 | chrome.tabs.update(sender.tab.id, { 45 | highlighted: true 46 | }); 47 | chrome.windows.update(sender.tab.windowId, { 48 | focused: true 49 | }); 50 | } 51 | }); 52 | 53 | // Image Downloader (Open modified @belaviyo's image downloader UI [with developer's permission]) 54 | { 55 | const once = () => { 56 | if (once.done) { 57 | return; 58 | } 59 | once.done = true; 60 | 61 | chrome.contextMenus.create({ 62 | title: 'Download all Images', 63 | contexts: ['action'], 64 | documentUrlPatterns: ['*://*/*'], 65 | id: 'save-images' 66 | }); 67 | chrome.contextMenus.create({ 68 | title: 'Download Live Streams', 69 | contexts: ['action'], 70 | documentUrlPatterns: ['*://*/*'], 71 | id: 'hls-downloader' 72 | }); 73 | }; 74 | chrome.runtime.onInstalled.addListener(once); 75 | chrome.runtime.onStartup.addListener(once); 76 | } 77 | chrome.contextMenus.onClicked.addListener(info => { 78 | if (info.menuItemId === 'save-images' || info.menuItemId === 'hls-downloader') { 79 | const {href} = Object.assign(new URL(chrome.runtime.getManifest().homepage_url), { 80 | pathname: 'listing/' + info.menuItemId + '.html' 81 | }); 82 | chrome.tabs.create({ 83 | url: href 84 | }); 85 | } 86 | }); 87 | 88 | /* FAQs & Feedback */ 89 | { 90 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; 91 | if (navigator.webdriver !== true) { 92 | const {homepage_url: page, name, version} = getManifest(); 93 | onInstalled.addListener(({reason, previousVersion}) => { 94 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 95 | 'faqs': true, 96 | 'last-update': 0 97 | }, prefs => { 98 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 99 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 100 | if (doUpdate && previousVersion !== version) { 101 | tabs.query({active: true, lastFocusedWindow: true}, tbs => tabs.create({ 102 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, 103 | active: reason === 'install', 104 | ...(tbs && tbs.length && {index: tbs[0].index + 1}) 105 | })); 106 | storage.local.set({'last-update': Date.now()}); 107 | } 108 | } 109 | })); 110 | }); 111 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /v2/data/window/external.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2017 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: http://add0n.com/media-tools.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ */ 9 | 10 | /* globals persist, visible */ 11 | 'use strict'; 12 | { 13 | const os = navigator.userAgent.indexOf('Firefox') !== -1 ? 'firefox' : ( 14 | navigator.userAgent.indexOf('OPR') === -1 ? 'chrome' : 'opera' 15 | ); 16 | const id = { 17 | chrome: 'bifmfjgpgndemajpeeoiopbeilbaifdo', 18 | opera: 'enemdfoackoekaedijjmjlckkleokhih', 19 | firefox: '{65b77238-bb05-470a-a445-ec0efe1d66c4}' 20 | }; 21 | const urls = { 22 | chrome: 'https://chrome.google.com/webstore/detail/bifmfjgpgndemajpeeoiopbeilbaifdo', 23 | opera: 'https://addons.opera.com/extensions/details/external-application-button/', 24 | firefox: 'https://addons.mozilla.org/firefox/addon/external-application/' 25 | }; 26 | 27 | const map = { 28 | 'wget': { 29 | path: 'wget', 30 | args: '[HREF]' 31 | }, 32 | 'idm': { 33 | path: '%ProgramFiles(x86)%\\Internet Download Manager\\IDMan.exe', 34 | args: '/d "[URL]"' 35 | }, 36 | 'fdm': { 37 | path: '%ProgramFiles%\\FreeDownloadManager.ORG\\Free Download Manager\\fdm.exe', 38 | args: '"[HREF]"' 39 | }, 40 | 'cus1': { 41 | path: 'application I path', 42 | args: '[HREF]' 43 | }, 44 | 'cus2': { 45 | path: 'application II path', 46 | args: '[HREF]' 47 | }, 48 | 'cus3': { 49 | path: 'application III path', 50 | args: '[HREF]' 51 | }, 52 | }; 53 | const {path, args, select, save, actions} = $.external; 54 | select.addEventListener('change', ({target}) => { 55 | chrome.storage.local.get({ 56 | [target.value]: map[target.value] || '' 57 | }, prefs => { 58 | args.value = prefs[target.value].args; 59 | path.value = prefs[target.value].path; 60 | path.dispatchEvent(new Event('input')); 61 | }); 62 | }); 63 | path.addEventListener('input', ({target}) => { 64 | save.disabled = target.value === ''; 65 | }); 66 | 67 | const send = ({url, referrer, filename}, callback) => { 68 | chrome.runtime.sendMessage(id[os], { 69 | app: { 70 | args: args.value, 71 | quotes: $.external.quotes.checked, 72 | path: path.value, 73 | filename, 74 | referrer, 75 | }, 76 | tab: { 77 | url 78 | }, 79 | selectionText: 'Sent by Bulk Media Downloader' 80 | }, resp => { 81 | console.log(resp); 82 | if (resp === false) { 83 | window.alert('External Application Button rejected the execution!'); 84 | } 85 | else if (resp === true) { 86 | callback(); 87 | } 88 | else { 89 | window.alert('To run native commands, you need to install the External Application Button extension'); 90 | chrome.tabs.create({ 91 | url: urls[os] 92 | }); 93 | } 94 | }); 95 | }; 96 | 97 | actions.addEventListener('click', ({target}) => { 98 | const cmd = target.dataset.cmd; 99 | if (cmd === 'save') { 100 | persist.save(select.value, { 101 | path: path.value, 102 | args: args.value 103 | }); 104 | } 105 | else if (cmd === 'run') { 106 | target.disabled = true; 107 | const items = [...$.links.querySelectorAll(':checked')] 108 | .filter(item => visible(item)); 109 | if (items.length > 10) { 110 | if (!window.confirm('Are you sure you want to run external command ' + items.length + ' times?')) { 111 | target.disabled = false; 112 | return; 113 | } 114 | } 115 | const objs = items.map(e => e.closest('tr')) 116 | .map(tr => ({ 117 | url: tr.dataset.url, 118 | referrer: tr.dataset.referrer, 119 | filename: tr.dataset.filename 120 | })); 121 | const one = () => { 122 | const obj = objs.shift(); 123 | if (obj) { 124 | send(obj, one); 125 | } 126 | else { 127 | target.disabled = false; 128 | } 129 | }; 130 | one(); 131 | } 132 | }); 133 | } 134 | -------------------------------------------------------------------------------- /v3/data/window/external.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2025 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: https://webextension.org/listing/bulk-media-downloader.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ 9 | */ 10 | 11 | /* global persist, visible, $ */ 12 | 'use strict'; 13 | { 14 | const os = navigator.userAgent.includes('Firefox') ? 'firefox' : ( 15 | navigator.userAgent.includes('OPR') ? 'opera' : ( 16 | navigator.userAgent.includes('Edg/') ? 'edge' : 'chrome' 17 | ) 18 | ); 19 | const id = { 20 | chrome: 'bifmfjgpgndemajpeeoiopbeilbaifdo', 21 | edge: 'icfhhpfimihpdgglmdlnmpaadfeaacbk', 22 | opera: 'enemdfoackoekaedijjmjlckkleokhih', 23 | firefox: '{65b77238-bb05-470a-a445-ec0efe1d66c4}' 24 | }; 25 | const urls = { 26 | edge: 'https://microsoftedge.microsoft.com/addons/detail/icfhhpfimihpdgglmdlnmpaadfeaacbk', 27 | chrome: 'https://chrome.google.com/webstore/detail/bifmfjgpgndemajpeeoiopbeilbaifdo', 28 | opera: 'https://addons.opera.com/extensions/details/external-application-button/', 29 | firefox: 'https://addons.mozilla.org/firefox/addon/external-application/' 30 | }; 31 | 32 | const map = { 33 | 'wget': { 34 | path: 'wget', 35 | args: '[HREF]' 36 | }, 37 | 'idm': { 38 | path: '%ProgramFiles(x86)%\\Internet Download Manager\\IDMan.exe', 39 | args: '/d "[URL]"' 40 | }, 41 | 'fdm': { 42 | path: '%ProgramFiles%\\FreeDownloadManager.ORG\\Free Download Manager\\fdm.exe', 43 | args: '"[HREF]"' 44 | }, 45 | 'cus1': { 46 | path: 'application I path', 47 | args: '[HREF]' 48 | }, 49 | 'cus2': { 50 | path: 'application II path', 51 | args: '[HREF]' 52 | }, 53 | 'cus3': { 54 | path: 'application III path', 55 | args: '[HREF]' 56 | } 57 | }; 58 | const {path, args, select, save, actions} = $.external; 59 | select.addEventListener('change', ({target}) => { 60 | chrome.storage.local.get({ 61 | [target.value]: map[target.value] || '' 62 | }, prefs => { 63 | args.value = prefs[target.value].args; 64 | path.value = prefs[target.value].path; 65 | path.dispatchEvent(new Event('input')); 66 | }); 67 | }); 68 | path.addEventListener('input', ({target}) => { 69 | save.disabled = target.value === ''; 70 | }); 71 | 72 | const send = ({url, referrer, filename}, callback) => { 73 | chrome.runtime.sendMessage(id[os], { 74 | app: { 75 | args: args.value, 76 | quotes: $.external.quotes.checked, 77 | path: path.value, 78 | filename, 79 | referrer 80 | }, 81 | tab: { 82 | url 83 | }, 84 | selectionText: 'Sent by Bulk Media Downloader' 85 | }, resp => { 86 | if (resp === false) { 87 | window.alert('External Application Button rejected this execution!'); 88 | } 89 | else if (resp === true) { 90 | callback(); 91 | } 92 | else { 93 | window.alert('To run native commands, please install the "External Application Button" extension'); 94 | chrome.tabs.create({ 95 | url: urls[os] 96 | }); 97 | } 98 | }); 99 | }; 100 | 101 | actions.addEventListener('click', ({target}) => { 102 | const cmd = target.dataset.cmd; 103 | if (cmd === 'save') { 104 | persist.save(select.value, { 105 | path: path.value, 106 | args: args.value 107 | }); 108 | } 109 | else if (cmd === 'run') { 110 | target.disabled = true; 111 | const items = [...$.links.querySelectorAll(':checked')] 112 | .filter(item => visible(item)); 113 | if (items.length > 10) { 114 | if (!window.confirm('Are you sure you want to run external command ' + items.length + ' times?')) { 115 | target.disabled = false; 116 | return; 117 | } 118 | } 119 | const objs = items.map(e => e.closest('tr')) 120 | .map(tr => ({ 121 | url: tr.dataset.url, 122 | referrer: tr.dataset.referrer, 123 | filename: tr.dataset.filename 124 | })); 125 | const one = () => { 126 | const obj = objs.shift(); 127 | if (obj) { 128 | send(obj, one); 129 | } 130 | else { 131 | target.disabled = false; 132 | } 133 | }; 134 | one(); 135 | } 136 | }); 137 | } 138 | -------------------------------------------------------------------------------- /v2/data/window/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | box-sizing: border-box; 5 | min-width: 600px; 6 | } 7 | body { 8 | font-family: arial,sans-serif; 9 | font-size: 12px; 10 | margin: 0; 11 | box-sizing: border-box; 12 | color: #222; 13 | background-color: #ececec; 14 | -webkit-user-select: none; 15 | user-select: none; 16 | overflow-x: hidden; 17 | } 18 | table { 19 | width: 100%; 20 | border-spacing: 0; 21 | } 22 | thead { 23 | background-color: #f6f6f6; 24 | } 25 | th { 26 | text-align: left; 27 | font-weight: normal; 28 | border-bottom: 1px solid #d6d6d6; 29 | text-indent: 3px; 30 | } 31 | th:not(:first-child) { 32 | border-left: 1px solid #d6d6d6; 33 | } 34 | tbody tr { 35 | height: 22px; 36 | cursor: pointer; 37 | } 38 | tbody tr:nth-child(even) { 39 | background-color: #f5f5f5; 40 | } 41 | tbody tr[data-selected=true] { 42 | background-color: #d4d4d4; 43 | } 44 | input[type=text] { 45 | flex: 1; 46 | } 47 | input[type=text][readonly] { 48 | background-color: transparent; 49 | border: none; 50 | } 51 | input[type=button] { 52 | flex: 1; 53 | width: 100%; 54 | box-sizing: border-box; 55 | outline: none; 56 | transition: opacity .25s ease-in-out; 57 | } 58 | input[type=button]:active { 59 | opacity: 0.5; 60 | } 61 | 62 | input[type=button], 63 | input[type=text] { 64 | height: 22px; 65 | border: solid 1px #bdbdbd; 66 | outline: none; 67 | } 68 | input:focus { 69 | border-color: #8e8e8e; 70 | } 71 | input[type=submit], 72 | input[type=button] { 73 | cursor: pointer; 74 | background-color: #fff; 75 | color: #000; 76 | } 77 | input:disabled { 78 | opacity: 0.3; 79 | } 80 | 81 | #head { 82 | align-items: center; 83 | min-height: 60px; 84 | } 85 | #details { 86 | flex: 1; 87 | align-items: flex-start !important; 88 | } 89 | summary { 90 | outline: none; 91 | } 92 | #head { 93 | padding: 10px; 94 | } 95 | #head>ul { 96 | flex: 1; 97 | text-align: justify; 98 | } 99 | #stats { 100 | padding: 5px; 101 | } 102 | body:not([data-active=true]) #head img { 103 | display: none; 104 | } 105 | 106 | #filter { 107 | display: block; 108 | } 109 | 110 | #links { 111 | flex: 1; 112 | background-color: #fff; 113 | border-bottom: solid 1px #bdbdbd; 114 | overflow-y: auto; 115 | overflow-y: overlay; 116 | } 117 | #links-head { 118 | border-top: solid 1px #bdbdbd; 119 | position: relative; 120 | } 121 | #links tr[data-error=true] { 122 | color: red; 123 | } 124 | #links-head table, 125 | #links table { 126 | table-layout: fixed; 127 | } 128 | #links-head th, 129 | #links td { 130 | white-space: nowrap; 131 | overflow: hidden; 132 | text-overflow: ellipsis; 133 | } 134 | #links-head th:nth-child(1), 135 | #links-head th:nth-child(2), 136 | #links-head th:nth-child(3) { 137 | text-align: center; 138 | cursor: pointer; 139 | } 140 | #links-head span { 141 | pointer-events: none; 142 | } 143 | body[data-filter-image=false] #links tbody tr[data-type=image] { 144 | display: none; 145 | } 146 | body[data-filter-video=false] #links tbody tr[data-type=video] { 147 | display: none; 148 | } 149 | body[data-filter-audio=false] #links tbody tr[data-type=audio] { 150 | display: none; 151 | } 152 | 153 | body[data-size="100k"] #links tbody tr:not([data-size*=i]), 154 | body[data-size="1m"] #links tbody tr:not([data-size*=ii]), 155 | body[data-size="10m"] #links tbody tr:not([data-size*=iii]) { 156 | display: none; 157 | } 158 | 159 | #links-head>ul { 160 | border: solid 1px #bdbdbd; 161 | position: absolute; 162 | top: 8px; 163 | left: 16px; 164 | background-color: #f9f9f9; 165 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 166 | min-width: 50px; 167 | padding: 10px; 168 | list-style-type: none; 169 | text-align: left; 170 | padding: 5px 0; 171 | } 172 | #links-head:not([data-select=true])>ul[type=select] { 173 | display: none; 174 | } 175 | #links-head:not([data-filter=true])>ul[type=filter] { 176 | display: none; 177 | } 178 | #links-head:not([data-size=true])>ul[type=size] { 179 | display: none; 180 | } 181 | #links-head>ul[type=filter] { 182 | left: 45px; 183 | } 184 | #links-head>ul[type=size] { 185 | left: 136px; 186 | } 187 | #links-head>ul li { 188 | cursor: pointer; 189 | height: 24px; 190 | line-height: 24px; 191 | padding: 0 10px; 192 | white-space: nowrap; 193 | } 194 | #links-head>ul li:hover { 195 | background-color: #f1f1f1; 196 | } 197 | 198 | #filters { 199 | padding: 5px 10px; 200 | background-color: rgba(0, 0, 0, 0.05); 201 | } 202 | 203 | #filters input[type=checkbox]{ 204 | display: none; 205 | } 206 | input[type=text] { 207 | text-indent: 5px; 208 | } 209 | input[type=button] { 210 | margin: 2px; 211 | overflow: hidden; 212 | text-overflow: ellipsis; 213 | flex: 1; 214 | border-radius: 0; 215 | } 216 | input[type=text], 217 | input[type=button] { 218 | height: 22px; 219 | box-sizing: border-box; 220 | } 221 | 222 | #filters label { 223 | overflow: hidden; 224 | text-overflow: ellipsis; 225 | white-space: nowrap; 226 | padding: 0 5px; 227 | cursor: pointer; 228 | } 229 | #filters span:before { 230 | content: '\e801'; 231 | } 232 | #filters input:checked ~ span:before { 233 | content: '\e800'; 234 | } 235 | #tools { 236 | padding: 10px; 237 | } 238 | #tools input[type=button] { 239 | max-width: 200px; 240 | } 241 | #external { 242 | padding: 10px; 243 | } 244 | #external input[type=button] { 245 | min-width: 80px; 246 | } 247 | #external p { 248 | font-size: 90%; 249 | color: #555; 250 | } 251 | #external { 252 | background-color: rgba(0, 0, 0, 0.05); 253 | } 254 | #external summary { 255 | margin-bottom: 10px; 256 | } 257 | #external * { 258 | margin: 2px; 259 | } 260 | #external input[type=button] { 261 | max-width: 80px; 262 | } 263 | 264 | body:not([data-os=chrome]) .chrome { 265 | display: none; 266 | } 267 | body[data-os=firefox] #external { 268 | display: none; 269 | } 270 | 271 | [hbox] { 272 | display: flex; 273 | flex-direction: row; 274 | } 275 | [vbox] { 276 | display: flex; 277 | flex-direction: column; 278 | } 279 | [flex="1"] { 280 | flex: 1; 281 | } 282 | [pack=center] { 283 | justify-content: center; 284 | } 285 | [pack=end] { 286 | justify-content: flex-end; 287 | } 288 | [align=center] { 289 | align-items: center; 290 | } 291 | -------------------------------------------------------------------------------- /v3/data/window/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | box-sizing: border-box; 5 | min-width: 600px; 6 | } 7 | body { 8 | font-family: arial, sans-serif; 9 | font-size: 12px; 10 | margin: 0; 11 | box-sizing: border-box; 12 | color: #222; 13 | background-color: #e9e9e9; 14 | -webkit-user-select: none; 15 | user-select: none; 16 | overflow-x: hidden; 17 | } 18 | table { 19 | width: 100%; 20 | border-spacing: 0; 21 | } 22 | thead { 23 | background-color: #f6f6f6; 24 | } 25 | th { 26 | font-weight: normal; 27 | border-bottom: 1px solid #d6d6d6; 28 | padding: 10px 2px; 29 | text-align: center; 30 | } 31 | th:not(:first-child) { 32 | border-left: 1px solid #d6d6d6; 33 | } 34 | tbody tr { 35 | height: 22px; 36 | cursor: pointer; 37 | } 38 | tbody tr:nth-child(even) { 39 | background-color: #f5f5f5; 40 | } 41 | tbody tr[data-selected=true] { 42 | background-color: #d4d4d4; 43 | } 44 | input[type=button], 45 | input[type=text] { 46 | outline: none; 47 | box-sizing: border-box; 48 | padding: 5px; 49 | } 50 | input[type=text] { 51 | flex: 1; 52 | border: none; 53 | padding: 6px; 54 | } 55 | input[type=submit], 56 | input[type=button] { 57 | cursor: pointer; 58 | box-sizing: border-box; 59 | outline: none; 60 | transition: opacity 0.25s ease-in-out; 61 | margin: 0; 62 | overflow: hidden; 63 | text-overflow: ellipsis; 64 | flex: 1; 65 | border-radius: 0; 66 | padding: 6px; 67 | border: solid 1px rgba(0, 0, 0, 0.25); 68 | color: #444; 69 | background-image: linear-gradient(rgb(237, 237, 237), rgb(237, 237, 237) 38%, rgb(222, 222, 222)); 70 | box-shadow: rgba(0, 0, 0, 0.08) 0 1px 0, rgba(255, 255, 255, 0.75) 0 1px 2px inset; 71 | text-shadow: rgb(240, 240, 240) 0 1px 0; 72 | } 73 | input[type=button]:active { 74 | opacity: 0.5; 75 | } 76 | input:focus { 77 | border-color: #8e8e8e; 78 | } 79 | input:disabled { 80 | opacity: 0.2; 81 | cursor: default; 82 | } 83 | summary { 84 | outline: none; 85 | } 86 | select { 87 | padding: 5px; 88 | border: none; 89 | outline: none; 90 | } 91 | 92 | #head { 93 | align-items: center; 94 | padding: 10px; 95 | } 96 | #details { 97 | flex: 1; 98 | align-items: flex-start !important; 99 | } 100 | #head > ul { 101 | flex: 1; 102 | text-align: justify; 103 | } 104 | #stats { 105 | color: #878787; 106 | padding: 0 10px; 107 | } 108 | body:not([data-active=true]) #head img { 109 | display: none; 110 | } 111 | 112 | #filter { 113 | display: block; 114 | } 115 | 116 | #links { 117 | flex: 1; 118 | background-color: #fff; 119 | overflow-y: auto; 120 | overflow-y: overlay; 121 | } 122 | #links-head { 123 | border-top: solid 1px #bdbdbd; 124 | position: relative; 125 | } 126 | #links tr[data-error=true] { 127 | color: red; 128 | } 129 | #links-head table, 130 | #links table { 131 | table-layout: fixed; 132 | } 133 | #links-head th, 134 | #links td { 135 | white-space: nowrap; 136 | overflow: hidden; 137 | text-overflow: ellipsis; 138 | } 139 | #links-head th:nth-child(1), 140 | #links-head th:nth-child(2), 141 | #links-head th:nth-child(3) { 142 | text-align: center; 143 | cursor: pointer; 144 | } 145 | #links-head span { 146 | pointer-events: none; 147 | } 148 | body[data-filter-image=false] #links tbody tr[data-type=image] { 149 | display: none; 150 | } 151 | body[data-filter-video=false] #links tbody tr[data-type=video] { 152 | display: none; 153 | } 154 | body[data-filter-audio=false] #links tbody tr[data-type=audio] { 155 | display: none; 156 | } 157 | 158 | body[data-size="100k"] #links tbody tr:not([data-size*=i]), 159 | body[data-size="1m"] #links tbody tr:not([data-size*=ii]), 160 | body[data-size="10m"] #links tbody tr:not([data-size*=iii]) { 161 | display: none; 162 | } 163 | 164 | #links-head > ul { 165 | border: solid 1px #bdbdbd; 166 | position: absolute; 167 | top: 8px; 168 | left: 16px; 169 | background-color: #f9f9f9; 170 | box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); 171 | min-width: 50px; 172 | list-style-type: none; 173 | text-align: left; 174 | padding: 5px 0; 175 | } 176 | #links-head:not([data-select=true]) > ul[type=select] { 177 | display: none; 178 | } 179 | #links-head:not([data-filter=true]) > ul[type=filter] { 180 | display: none; 181 | } 182 | #links-head:not([data-size=true]) > ul[type=size] { 183 | display: none; 184 | } 185 | #links-head > ul[type=filter] { 186 | left: 45px; 187 | } 188 | #links-head > ul[type=size] { 189 | left: 136px; 190 | } 191 | #links-head > ul li { 192 | cursor: pointer; 193 | height: 24px; 194 | line-height: 24px; 195 | padding: 0 10px; 196 | white-space: nowrap; 197 | } 198 | #links-head > ul li:hover { 199 | background-color: #f1f1f1; 200 | } 201 | 202 | #filters { 203 | padding: 5px 10px; 204 | } 205 | 206 | #filters input[type=checkbox] { 207 | display: none; 208 | } 209 | 210 | #filters label { 211 | overflow: hidden; 212 | text-overflow: ellipsis; 213 | white-space: nowrap; 214 | padding: 0 5px; 215 | cursor: pointer; 216 | } 217 | #filters span::before { 218 | content: '\e801'; 219 | } 220 | #filters input:checked ~ span::before { 221 | content: '\e800'; 222 | } 223 | #tools { 224 | padding: 5px 10px; 225 | display: grid; 226 | grid-template-columns: repeat(4, 1fr); 227 | grid-gap: 5px; 228 | } 229 | 230 | #external { 231 | padding: 10px; 232 | } 233 | #external input[type=button] { 234 | min-width: 80px; 235 | } 236 | #external p { 237 | font-size: 90%; 238 | color: #555; 239 | } 240 | #external summary { 241 | margin-bottom: 10px; 242 | } 243 | #external input[type=button] { 244 | max-width: 80px; 245 | } 246 | #external details > * { 247 | margin: 5px 0; 248 | gap: 5px; 249 | } 250 | 251 | #external textarea { 252 | width: 100%; 253 | box-sizing: border-box; 254 | background-color: transparent; 255 | border: none; 256 | outline: none; 257 | } 258 | 259 | body:not([data-os=chrome]) .chrome { 260 | display: none; 261 | } 262 | body[data-os=firefox] #external { 263 | display: none; 264 | } 265 | 266 | [hbox] { 267 | display: flex; 268 | flex-direction: row; 269 | } 270 | [vbox] { 271 | display: flex; 272 | flex-direction: column; 273 | } 274 | [flex="1"] { 275 | flex: 1; 276 | } 277 | [pack=start] { 278 | justify-content: start; 279 | } 280 | [pack=center] { 281 | justify-content: center; 282 | } 283 | [pack=end] { 284 | justify-content: flex-end; 285 | } 286 | [align=center] { 287 | align-items: center; 288 | } 289 | -------------------------------------------------------------------------------- /v2/common.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2017 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: http://add0n.com/media-tools.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ */ 9 | 10 | 'use strict'; 11 | 12 | const os = navigator.userAgent.indexOf('Firefox') !== -1 ? 'firefox' : ( 13 | navigator.userAgent.indexOf('OPR') === -1 ? 'chrome' : 'opera' 14 | ); 15 | 16 | const ports = []; 17 | chrome.runtime.onConnect.addListener(port => { 18 | ports.push(port); 19 | port.onMessage.addListener(request => { 20 | if (request.method === 'resize') { 21 | chrome.storage.local.set({ 22 | left: request.left, 23 | top: request.top, 24 | width: request.width, 25 | height: request.height 26 | }); 27 | } 28 | }); 29 | port.onDisconnect.addListener(() => { 30 | const n = ports.indexOf(port); 31 | ports.splice(n, 1); 32 | monitor.deactivate(); 33 | }); 34 | }); 35 | 36 | const stats = { 37 | total: 0, 38 | media: 0 39 | }; 40 | 41 | const notify = message => chrome.notifications.create({ 42 | type: 'basic', 43 | iconUrl: '/data/icons/48.png', 44 | title: chrome.runtime.getManifest().name, 45 | message 46 | }); 47 | 48 | const monitor = { 49 | observe: d => { 50 | if (d.tabId === -1) { 51 | return; 52 | } 53 | // prevent YouTube video link detection 54 | if (os === 'chrome' && d.url.indexOf('googlevideo.') !== -1) { 55 | return; 56 | } 57 | let type = d.responseHeaders.filter(o => o.name === 'content-type' || o.name === 'Content-Type'); 58 | 59 | // remove range from stream URL if possible; 60 | d.url = d.url.replace(/&range=\d+-\d+/, ''); 61 | 62 | if (type.length) { 63 | stats.total += 1; 64 | type = type[0].value; 65 | 66 | const length = d.responseHeaders 67 | .filter(o => o.name === 'content-length' || o.name === 'Content-Length') 68 | .map(l => l.value).shift(); 69 | 70 | if ( 71 | type.startsWith('image') || 72 | type.startsWith('video') || 73 | type.startsWith('audio') || 74 | (type.startsWith('application') && type.indexOf('javascript') === -1) 75 | ) { 76 | stats.media += 1; 77 | chrome.runtime.sendMessage({ 78 | cmd: 'append', 79 | id: d.requestId, 80 | url: d.url, 81 | otype: d.type, 82 | tabId: d.tabId, 83 | timeStamp: d.timeStamp, 84 | methd: d.method, 85 | length, 86 | disposition: d.responseHeaders 87 | .filter(o => o.name === 'content-disposition' || o.name === 'Content-Disposition') 88 | .map(o => o.value) 89 | .shift(), 90 | type, 91 | stats 92 | }); 93 | } 94 | } 95 | }, 96 | activate: () => { 97 | chrome.webRequest.onHeadersReceived.addListener(monitor.observe, 98 | {urls: ['']}, 99 | ['responseHeaders'] 100 | ); 101 | chrome.browserAction.setBadgeText({text: 'R'}); 102 | }, 103 | deactivate: () => { 104 | chrome.webRequest.onHeadersReceived.removeListener(monitor.observe); 105 | chrome.browserAction.setBadgeText({text: ''}); 106 | } 107 | }; 108 | 109 | chrome.browserAction.onClicked.addListener(tab => { 110 | function create() { 111 | chrome.storage.local.get({ 112 | width: 750, 113 | height: 600, 114 | left: screen.availLeft + Math.round((screen.availWidth - 700) / 2), 115 | top: screen.availTop + Math.round((screen.availHeight - 600) / 2) 116 | }, prefs => { 117 | chrome.windows.create({ 118 | url: chrome.extension.getURL('data/window/index.html?tabId=' + tab.id), 119 | width: prefs.width, 120 | height: prefs.height, 121 | left: prefs.left, 122 | top: prefs.top, 123 | type: 'popup' 124 | }, () => monitor.activate()); 125 | }); 126 | } 127 | if (ports.length) { 128 | const tab = ports[0].sender.tab; 129 | chrome.windows.update(tab.windowId, { 130 | focused: true 131 | }); 132 | chrome.tabs.sendMessage(tab.id, { 133 | cmd: 'update-id', 134 | id: tab.id 135 | }); 136 | } 137 | else { 138 | create(); 139 | } 140 | }); 141 | 142 | chrome.runtime.onMessage.addListener(message => { 143 | if (message === 'pause') { 144 | monitor.deactivate(); 145 | } 146 | else if (message === 'resume') { 147 | monitor.activate(); 148 | } 149 | else if (message.cmd === 'download-browser') { 150 | const options = { 151 | url: message.url 152 | }; 153 | if (message.filename && message.filename !== '-') { 154 | options.filename = message.filename 155 | .replace(/[`~!@#$%^&*()_|+\-=?;:'",<>{}[\]\\/]/gi, ''); 156 | } 157 | 158 | chrome.downloads.download(options, () => { 159 | if (chrome.runtime.lastError) { 160 | const a = document.createElement('a'); 161 | a.href = options.url; 162 | a.setAttribute('download', options.filename || 'unknown_name'); 163 | a.dispatchEvent(new MouseEvent('click')); 164 | } 165 | }); 166 | } 167 | }); 168 | 169 | // Image Downloader (Open modified @belaviyo's image downloader UI [with developer's permission]) 170 | chrome.contextMenus.create({ 171 | title: 'Download all Images', 172 | contexts: ['browser_action'], 173 | documentUrlPatterns: ['*://*/*'], 174 | onclick() { 175 | chrome.tabs.create({ 176 | url: 'https://add0n.com/save-images.html' 177 | }); 178 | } 179 | }); 180 | 181 | /* FAQs & Feedback */ 182 | { 183 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; 184 | if (navigator.webdriver !== true) { 185 | const page = getManifest().homepage_url; 186 | const {name, version} = getManifest(); 187 | onInstalled.addListener(({reason, previousVersion}) => { 188 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 189 | 'faqs': true, 190 | 'last-update': 0 191 | }, prefs => { 192 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 193 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 194 | if (doUpdate && previousVersion !== version) { 195 | tabs.query({active: true, currentWindow: true}, tbs => tabs.create({ 196 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, 197 | active: reason === 'install', 198 | ...(tbs && tbs.length && {index: tbs[0].index + 1}) 199 | })); 200 | storage.local.set({'last-update': Date.now()}); 201 | } 202 | } 203 | })); 204 | }); 205 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /v3/data/window/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bulk Media Downloader (active) 7 | 8 | 9 | 10 | 11 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
---
45 |
46 | 90 | 105 |
106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
116 |
117 | 118 | 119 | 120 | 121 |
122 |
123 |
124 |
125 | External Executable: 126 |
127 | 135 | 136 |
137 |
138 | 139 | 140 |
141 |
142 | 143 | 144 |
145 |
146 |
147 |
148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /v2/data/window/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bulk Media Downloader (active) 6 | 7 | 8 | 9 | 10 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
---
43 |
44 | 88 | 103 |
104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
114 |
115 | 116 | 117 | 118 | 119 |
120 |
121 |
122 |
123 | External Executable: 124 |
125 | 133 | 134 |
135 |
136 | 137 | 138 |
139 |
140 | 141 | 142 |
143 | 144 |
145 |
146 |
147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /v2/data/window/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2017 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: http://add0n.com/media-tools.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ */ 9 | 10 | /* globals $, persist */ 11 | 'use strict'; 12 | 13 | document.body.dataset.os = navigator.userAgent.indexOf('Firefox') !== -1 ? 'firefox' : ( 14 | navigator.userAgent.indexOf('OPR') === -1 ? 'chrome' : 'opera' 15 | ); 16 | if (window.location.search) { 17 | document.body.dataset.tabId = window.location.search.replace('?tabId=', ''); 18 | } 19 | 20 | const update = (function(callback) { 21 | $.filters.parent.addEventListener('change', callback); 22 | $.filters.parent.addEventListener('keyup', callback); 23 | 24 | return () => callback(); 25 | })(function() { 26 | [...$.tbody.querySelectorAll('tr')] 27 | .forEach(tr => tr.querySelector('[type=checkbox]').checked = isChecked(tr)); 28 | state(); 29 | }); 30 | 31 | const is = { 32 | application: tr => tr.dataset.type === 'application', 33 | document: tr => tr.dataset.type === 'document' || /\.(txt|docm|xps|odc|otc|odb|odf|odft|odg|otg|odi|oti|odp|otp|ods|ots|odt|odm|ott|oth|pptx|sldx|ppsx|potx|xlsx|xltx|docx|dotx)$/.test(tr.dataset.url), 34 | video: tr => tr.dataset.type === 'video' || /\.(3gp|3g2|h261|h263|h264|jpgv|jpm|jpgm|mj2|mjp2|ts|mp4|mp4v|mpg4|mpeg|mpg|mpe|m1v|m2v|ogv|qt|mov|uvh|uvvh|uvm|uvvm|uvp|uvvp|uvs|uvvs|uvv|uvvv|dvb|fvt|mxu|m4u|pyv|uvu|uvvu|viv|webm|f4v|fli|flv|m4v|mkv|mk3d|mks|mng|asf|asx|vob|wm|wmv|wmx|wvx|avi|movie|smv)$/.test(tr.dataset.url), 35 | audio: tr => tr.dataset.type === 'audio' || /\.(adp|au|snd|mid|midi|kar|rmi|m4a|mp3|mpga|mp2|mp2a|m2a|m3a|oga|ogg|spx|s3m|sil|uva|uvva|eol|dra|dts|dtshd|lvp|pya|rip|weba|aac|aif|aiff|aifc|caf|flac|mka|m3u|wax|wma|ra|rmp|wav)$/.test(tr.dataset.url), 36 | archive: tr => tr.dataset.type === 'archive' || /\.(zip|rar|jar|apk|xpi|crx|joda|tao)$/.test(tr.dataset.url), 37 | image: tr => tr.dataset.type === 'image' || /\.(bmp|cgm|g3|gif|ief|jpeg|jpg|jpe|ktx|png|btif|sgi|svg|svgz|tiff|tif|psd|uvi|uvvi|uvg|uvvg|djv|sub|dwg|dxf|fbs|fpx|fst|mmr|rlc|mdi|wdp|npx|wbmp|xif|webp|3ds|ras|cmx|fh|fhc|fh4|fh5|fh7|ico|sid|pcx|pic|pct|pnm|pbm|pgm|ppm|rgb|tga|xbm|xpm|xwd)$/.test(tr.dataset.url), 38 | tab: tr => tr.dataset.tabId === document.body.dataset.tabId 39 | }; 40 | 41 | let urls = []; 42 | 43 | const config = { 44 | _filter: 'all', 45 | _monitor: true, 46 | _size: 'all' // 'all', '100k', '1m', '10m' 47 | }; 48 | Object.defineProperty(config, 'filter', { 49 | enumerable: true, 50 | configurable: true, 51 | get() { 52 | return config._filter; 53 | }, 54 | set(val) { 55 | config._filter = val; 56 | document.body.dataset.filterVideo = val === 'video' || val === 'all' || val === 'media'; 57 | document.body.dataset.filterAudio = val === 'audio' || val === 'all' || val === 'media'; 58 | document.body.dataset.filterImage = val === 'image' || val === 'all'; 59 | document.body.dataset.filterApp = val === 'application' || val === 'all'; 60 | $.filter.textContent = `Type (${val})`; 61 | persist.save('filter', val); 62 | } 63 | }); 64 | Object.defineProperty(config, 'monitor', { 65 | enumerable: true, 66 | configurable: true, 67 | get() { 68 | return config._monitor; 69 | }, 70 | set(val) { 71 | config._monitor = val; 72 | chrome.runtime.sendMessage(val ? 'resume' : 'pause'); 73 | document.body.dataset.active = val; 74 | $.pause.dataset.cmd = val ? 'pause' : 'resume'; 75 | $.pause.value = val ? 'Pause' : 'Resume'; 76 | document.title = `Bulk Media Downloader (${val ? 'active' : 'paused'})`; 77 | } 78 | }); 79 | Object.defineProperty(config, 'size', { 80 | enumerable: true, 81 | configurable: true, 82 | get() { 83 | return config._size; 84 | }, 85 | set(val) { 86 | config._size = val; 87 | document.body.dataset.size = val; 88 | $.size.textContent = `Size (${val})`; 89 | persist.save('size', val); 90 | } 91 | }); 92 | 93 | function notify(message) { 94 | chrome.storage.local.get({ 95 | notify: true 96 | }, prefs => prefs.notify && chrome.notifications.create(null, { 97 | type: 'basic', 98 | iconUrl: '/data/icons/48.png', 99 | title: 'Bulk Media Downloader', 100 | message 101 | })); 102 | } 103 | function visible(e) { 104 | return Boolean(e.offsetWidth || e.offsetHeight || e.getClientRects().length); 105 | } 106 | 107 | function state() { 108 | const disabled = [...$.links.querySelectorAll('[type=checkbox]:checked')].filter(visible).length === 0; 109 | $.buttons.browser.disabled = 110 | $.buttons.links.disabled = 111 | $.external.run.disabled = 112 | disabled; 113 | } 114 | 115 | function isChecked(tr) { 116 | let checked = false; 117 | if ($.filters.all.checked) { 118 | checked = true; 119 | } 120 | else { 121 | if ($.filters.applications.checked) { 122 | checked = checked || (tr.dataset.type === 'application' || /\.(exe|xpi)/.test(tr.dataset.url)); 123 | } 124 | if ($.filters.images.checked) { 125 | checked = checked || is.image(tr); 126 | } 127 | if ($.filters.videos.checked) { 128 | checked = checked || is.video(tr); 129 | } 130 | if ($.filters.audios.checked) { 131 | checked = checked || is.audio(tr); 132 | } 133 | if ($.filters.archives.checked) { 134 | checked = checked || is.archive(tr); 135 | } 136 | if ($.filters.documents.checked) { 137 | checked = checked || is.document(tr); 138 | } 139 | if ($.filters.tab.checked) { 140 | checked = checked || is.tab(tr); 141 | } 142 | if ($.filters.regexp.value) { 143 | try { 144 | const r = new RegExp($.filters.regexp.value); 145 | checked = checked || r.test(tr.dataset.url); 146 | if (tr.dataset.filename && tr.dataset.filename !== '-') { 147 | checked = checked || r.test(tr.dataset.filename); 148 | } 149 | } 150 | catch (e) {} 151 | } 152 | } 153 | return checked; 154 | } 155 | 156 | function bytesToSize(bytes) { 157 | if (bytes === 0 || bytes === '0') { 158 | return '0 Byte'; 159 | } 160 | const k = 1024; 161 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 162 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 163 | return (bytes / Math.pow(k, i)).toFixed(i ? 1 : 0) + ' ' + sizes[i]; 164 | } 165 | // display or hide a top menu 166 | document.addEventListener('click', e => { 167 | const cmd = e.target.dataset.cmd; 168 | $.head.dataset.select = cmd === 'toggle-select'; 169 | $.head.dataset.filter = cmd === 'toggle-filter'; 170 | $.head.dataset.size = cmd === 'toggle-size'; 171 | }); 172 | 173 | // toggle item's selection on click 174 | document.addEventListener('click', e => { 175 | const target = e.target; 176 | const tr = target.closest('tr'); 177 | if (tr && tr.closest('#links')) { 178 | if (target.localName !== 'input') { 179 | const input = tr.querySelector('[type=checkbox]'); 180 | input.checked = !input.checked; 181 | } 182 | state(); 183 | } 184 | }); 185 | 186 | // commands 187 | document.addEventListener('click', e => { 188 | const cmd = e.target.dataset.cmd; 189 | if (cmd === 'pause' || cmd === 'resume') { 190 | config.monitor = cmd === 'resume'; 191 | } 192 | else if (cmd === 'clear') { 193 | $.tbody.textContent = ''; 194 | urls = []; 195 | state(); 196 | } 197 | else if (cmd === 'download-browser') { 198 | const items = [...$.links.querySelectorAll(':checked')] 199 | .filter(item => visible(item)); 200 | if (items.length > 10) { 201 | if (!window.confirm('Are you sure you want to download ' + items.length + ' items at once?')) { 202 | return; 203 | } 204 | } 205 | items.map(e => e.closest('tr')) 206 | .forEach(tr => chrome.runtime.sendMessage({ 207 | cmd, 208 | url: tr.dataset.url, 209 | referrer: tr.dataset.referrer, 210 | filename: tr.dataset.filename 211 | })); 212 | if (cmd === 'download-browser') { 213 | notify(items.length + ' link' + (items.length > 1 ? 's are' : ' is') + ' being downloaded'); 214 | } 215 | } 216 | else if (cmd === 'copy-links') { 217 | const links = [...$.links.querySelectorAll(':checked')] 218 | .filter(item => visible(item)) 219 | .map(e => e.closest('tr')) 220 | .map(tr => tr.dataset.url); 221 | document.oncopy = e => { 222 | e.clipboardData.setData('text/plain', links.join('\n')); 223 | e.preventDefault(); 224 | }; 225 | window.focus(); 226 | document.execCommand('Copy', false, null); 227 | notify(links.length + ' link' + (links.length > 1 ? 's are' : ' is') + ' copied to the clipboard'); 228 | } 229 | }); 230 | // top menu selection or filter changes 231 | document.addEventListener('click', e => { 232 | const cmd = e.target.dataset.cmd; 233 | 234 | if (cmd && cmd.startsWith('select-')) { 235 | [...$.links.querySelectorAll('[type=checkbox]')] 236 | .forEach(e => { 237 | if (cmd === 'select-all' || cmd === 'select-none') { 238 | e.checked = cmd === 'select-all'; 239 | } 240 | else { 241 | e.checked = is[cmd.replace('select-', '')](e.closest('tr')); 242 | } 243 | }); 244 | state(); 245 | } 246 | else if (cmd && cmd.startsWith('filter-')) { 247 | config.filter = cmd.replace('filter-', ''); 248 | state(); 249 | } 250 | else if (cmd && cmd.startsWith('size-')) { 251 | config.size = cmd.replace('size-', ''); 252 | state(); 253 | } 254 | }); 255 | 256 | function findTitle(message) { 257 | if (message.disposition) { 258 | if (message.disposition.indexOf('inline') !== -1) { 259 | const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(message.disposition); 260 | if (matches && matches.length) { 261 | return matches[1].replace(/['"]/g, ''); 262 | } 263 | } 264 | else { 265 | const matches = /filename=([^;]*)/.exec(message.disposition); 266 | if (matches && matches.length) { 267 | return matches[1].replace(/["']$/, '').replace(/^["']/, ''); 268 | } 269 | } 270 | } 271 | let name = message.url.split('/').pop().split('?').shift(); 272 | let extension = /\.([^.]+)$/.exec(name); 273 | if (extension && extension.length) { 274 | extension = extension[1]; 275 | name = name.replace('.' + extension, ''); 276 | } 277 | else { 278 | extension = message.type.split('/').pop().split('+').shift().split(';').shift(); 279 | } 280 | return name + '.' + extension; 281 | } 282 | 283 | const referrer = (() => { 284 | const cache = {}; 285 | 286 | chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { 287 | if (changeInfo.url) { 288 | cache[tabId] = changeInfo.url; 289 | } 290 | }); 291 | 292 | return (id, tr) => { 293 | if (cache[id]) { 294 | tr.dataset.referrer = cache[id]; 295 | } 296 | else { 297 | chrome.tabs.get(id, t => { 298 | cache[id] = t.url; 299 | tr.dataset.referrer = cache[id]; 300 | }); 301 | } 302 | }; 303 | })(); 304 | 305 | chrome.runtime.onMessage.addListener(message => { 306 | if (message.cmd === 'append') { 307 | if (urls.indexOf(message.url) !== -1) { 308 | return; 309 | } 310 | const tr = $.tr.cloneNode(true); 311 | const tds = tr.querySelectorAll('td'); 312 | tds[1].title = tds[1].textContent = message.type.split(';').shift(); 313 | tds[2].textContent = isNaN(message.length) ? 'N/A' : bytesToSize(message.length); 314 | tds[3].title = tds[3].textContent = message.url; 315 | tds[4].title = tds[4].textContent = (new Date()).toLocaleTimeString(); 316 | tds[6].title = tds[6].textContent = message.tabId; 317 | 318 | Object.assign(tr.dataset, { 319 | id: message.id, 320 | url: message.url, 321 | type: message.type.split('/')[0], 322 | size: isNaN(message.length) || message.length > 10 * 1024 * 1024 ? 'iii' : ( 323 | message.length > 1 * 1024 * 1024 ? 'ii' : ( 324 | message.length > 100 * 1024 ? 'i' : '' 325 | ) 326 | ) 327 | }); 328 | referrer(message.tabId, tr); 329 | 330 | tr.dataset.filename = tds[5].title = tds[5].textContent = findTitle(message); 331 | 332 | tr.dataset.tabId = message.tabId; 333 | 334 | const shouldScroll = $.links.scrollHeight - $.links.clientHeight === $.links.scrollTop; 335 | $.tbody.appendChild(tr); 336 | if (shouldScroll) { 337 | $.links.scrollTop = $.links.scrollHeight - $.links.clientHeight; 338 | } 339 | tr.querySelector('input').checked = isChecked(tr); 340 | state(); 341 | $.stats.textContent = message.stats.media + '/' + message.stats.total; 342 | urls.push(message.url); 343 | } 344 | else if (message.cmd === 'error') { 345 | const tr = document.querySelector(`[data-id="${message.id}"]`); 346 | if (tr) { 347 | tr.dataset.error = true; 348 | const tds = tr.querySelectorAll('td'); 349 | tds[5].textContent = message.msg; 350 | } 351 | } 352 | else if (message.cmd === 'update-id') { 353 | document.body.dataset.tabId = message.id; 354 | update(); 355 | } 356 | }); 357 | // load 358 | chrome.storage.local.get({ 359 | filter: 'media', 360 | size: 'all' 361 | }, prefs => { 362 | Object.assign(config, prefs); 363 | }); 364 | // unload 365 | const port = chrome.runtime.connect({ 366 | name: 'window' 367 | }); 368 | 369 | window.addEventListener('beforeunload', () => { 370 | port.postMessage({ 371 | method: 'resize', 372 | left: window.screenX, 373 | top: window.screenY, 374 | width: Math.max(window.outerWidth, 100), 375 | height: Math.max(window.outerHeight, 100) 376 | }); 377 | }); 378 | 379 | -------------------------------------------------------------------------------- /v3/data/window/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2014-2025 InBasic 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | * Home: https://webextension.org/listing/bulk-media-downloader.html 8 | * GitHub: https://github.com/inbasic/bulk-media-downloader/ 9 | */ 10 | 11 | /* globals $, persist */ 12 | 'use strict'; 13 | 14 | document.body.dataset.os = navigator.userAgent.includes('Firefox') ? 'firefox' : ( 15 | navigator.userAgent.includes('OPR') ? 'opera' : 'chrome' 16 | ); 17 | if (window.location.search) { 18 | document.body.dataset.tabId = window.location.search.replace('?tabId=', ''); 19 | } 20 | 21 | const stats = { 22 | total: 0, 23 | media: 0 24 | }; 25 | 26 | const ui = { 27 | append(request) { 28 | if (urls.indexOf(request.url) !== -1) { 29 | return; 30 | } 31 | const tr = $.tr.cloneNode(true); 32 | const tds = tr.querySelectorAll('td'); 33 | tds[1].title = tds[1].textContent = request.type.split(';').shift(); 34 | tds[2].textContent = isNaN(request.length) ? 'N/A' : bytesToSize(request.length); 35 | tds[3].title = tds[3].textContent = request.url; 36 | tds[4].title = tds[4].textContent = (new Date()).toLocaleTimeString(); 37 | tds[6].title = tds[6].textContent = request.tabId; 38 | 39 | Object.assign(tr.dataset, { 40 | id: request.id, 41 | url: request.url, 42 | type: request.type.split('/')[0], 43 | size: isNaN(request.length) || request.length > 10 * 1024 * 1024 ? 'iii' : ( 44 | request.length > 1 * 1024 * 1024 ? 'ii' : ( 45 | request.length > 100 * 1024 ? 'i' : '' 46 | ) 47 | ) 48 | }); 49 | referrer(request.tabId, tr); 50 | 51 | tr.dataset.filename = tds[5].title = tds[5].textContent = findTitle(request); 52 | 53 | tr.dataset.tabId = request.tabId; 54 | 55 | const shouldScroll = $.links.scrollHeight - $.links.clientHeight === $.links.scrollTop; 56 | $.tbody.appendChild(tr); 57 | if (shouldScroll) { 58 | $.links.scrollTop = $.links.scrollHeight - $.links.clientHeight; 59 | } 60 | tr.querySelector('input').checked = isChecked(tr); 61 | state(); 62 | $.stats.textContent = request.stats.media + '/' + request.stats.total; 63 | urls.push(request.url); 64 | } 65 | }; 66 | 67 | const monitor = { 68 | observe: d => { 69 | if (d.tabId === -1) { 70 | return; 71 | } 72 | // prevent YouTube video link detection 73 | if (d.url.indexOf('googlevideo.') !== -1) { 74 | return; 75 | } 76 | let type = d.responseHeaders.filter(o => o.name === 'content-type' || o.name === 'Content-Type'); 77 | 78 | // remove range from stream URL if possible; 79 | d.url = d.url.replace(/&range=\d+-\d+/, ''); 80 | 81 | if (type.length) { 82 | stats.total += 1; 83 | type = type[0].value; 84 | 85 | const length = d.responseHeaders 86 | .filter(o => o.name === 'content-length' || o.name === 'Content-Length') 87 | .map(l => l.value).shift(); 88 | 89 | if ( 90 | type.startsWith('image') || 91 | type.startsWith('video') || 92 | type.startsWith('audio') || 93 | (type.startsWith('application') && type.indexOf('javascript') === -1) 94 | ) { 95 | stats.media += 1; 96 | ui.append({ 97 | id: d.requestId, 98 | url: d.url, 99 | otype: d.type, 100 | tabId: d.tabId, 101 | timeStamp: d.timeStamp, 102 | methd: d.method, 103 | length, 104 | disposition: d.responseHeaders 105 | .filter(o => o.name === 'content-disposition' || o.name === 'Content-Disposition') 106 | .map(o => o.value) 107 | .shift(), 108 | type, 109 | stats 110 | }); 111 | } 112 | } 113 | }, 114 | activate: () => { 115 | chrome.webRequest.onHeadersReceived.addListener(monitor.observe, 116 | {urls: ['*://*/*']}, 117 | ['responseHeaders'] 118 | ); 119 | chrome.action.setBadgeText({text: 'R'}); 120 | }, 121 | deactivate: () => { 122 | chrome.webRequest.onHeadersReceived.removeListener(monitor.observe); 123 | chrome.action.setBadgeText({text: ''}); 124 | } 125 | }; 126 | monitor.activate(); 127 | 128 | const update = (function(callback) { 129 | $.filters.parent.addEventListener('change', callback); 130 | $.filters.parent.addEventListener('keyup', callback); 131 | 132 | return () => callback(); 133 | })(function() { 134 | [...$.tbody.querySelectorAll('tr')] 135 | .forEach(tr => tr.querySelector('[type=checkbox]').checked = isChecked(tr)); 136 | state(); 137 | }); 138 | 139 | const is = { 140 | application: tr => tr.dataset.type === 'application', 141 | document: tr => tr.dataset.type === 'document' || /\.(txt|docm|xps|odc|otc|odb|odf|odft|odg|otg|odi|oti|odp|otp|ods|ots|odt|odm|ott|oth|pptx|sldx|ppsx|potx|xlsx|xltx|docx|dotx)$/.test(tr.dataset.url), 142 | video: tr => tr.dataset.type === 'video' || /\.(3gp|3g2|h261|h263|h264|jpgv|jpm|jpgm|mj2|mjp2|ts|mp4|mp4v|mpg4|mpeg|mpg|mpe|m1v|m2v|ogv|qt|mov|uvh|uvvh|uvm|uvvm|uvp|uvvp|uvs|uvvs|uvv|uvvv|dvb|fvt|mxu|m4u|pyv|uvu|uvvu|viv|webm|f4v|fli|flv|m4v|mkv|mk3d|mks|mng|asf|asx|vob|wm|wmv|wmx|wvx|avi|movie|smv)$/.test(tr.dataset.url), 143 | audio: tr => tr.dataset.type === 'audio' || /\.(adp|au|snd|mid|midi|kar|rmi|m4a|mp3|mpga|mp2|mp2a|m2a|m3a|oga|ogg|spx|s3m|sil|uva|uvva|eol|dra|dts|dtshd|lvp|pya|rip|weba|aac|aif|aiff|aifc|caf|flac|mka|m3u|wax|wma|ra|rmp|wav)$/.test(tr.dataset.url), 144 | archive: tr => tr.dataset.type === 'archive' || /\.(zip|rar|jar|apk|xpi|crx|joda|tao)$/.test(tr.dataset.url), 145 | image: tr => tr.dataset.type === 'image' || /\.(bmp|cgm|g3|gif|ief|jpeg|jpg|jpe|ktx|png|btif|sgi|svg|svgz|tiff|tif|psd|uvi|uvvi|uvg|uvvg|djv|sub|dwg|dxf|fbs|fpx|fst|mmr|rlc|mdi|wdp|npx|wbmp|xif|webp|3ds|ras|cmx|fh|fhc|fh4|fh5|fh7|ico|sid|pcx|pic|pct|pnm|pbm|pgm|ppm|rgb|tga|xbm|xpm|xwd)$/.test(tr.dataset.url), 146 | tab: tr => tr.dataset.tabId === document.body.dataset.tabId 147 | }; 148 | 149 | let urls = []; 150 | 151 | const config = { 152 | _filter: 'all', 153 | _monitor: true, 154 | _size: 'all' // 'all', '100k', '1m', '10m' 155 | }; 156 | Object.defineProperty(config, 'filter', { 157 | enumerable: true, 158 | configurable: true, 159 | get() { 160 | return config._filter; 161 | }, 162 | set(val) { 163 | config._filter = val; 164 | document.body.dataset.filterVideo = val === 'video' || val === 'all' || val === 'media'; 165 | document.body.dataset.filterAudio = val === 'audio' || val === 'all' || val === 'media'; 166 | document.body.dataset.filterImage = val === 'image' || val === 'all'; 167 | document.body.dataset.filterApp = val === 'application' || val === 'all'; 168 | $.filter.textContent = `Type (${val})`; 169 | persist.save('filter', val); 170 | } 171 | }); 172 | Object.defineProperty(config, 'monitor', { 173 | enumerable: true, 174 | configurable: true, 175 | get() { 176 | return config._monitor; 177 | }, 178 | set(val) { 179 | config._monitor = val; 180 | if (val) { 181 | monitor.activate(); 182 | } 183 | else { 184 | monitor.deactivate(); 185 | } 186 | document.body.dataset.active = val; 187 | $.pause.dataset.cmd = val ? 'pause' : 'resume'; 188 | $.pause.value = val ? 'Pause' : 'Resume'; 189 | document.title = `Bulk Media Downloader (${val ? 'active' : 'paused'})`; 190 | } 191 | }); 192 | Object.defineProperty(config, 'size', { 193 | enumerable: true, 194 | configurable: true, 195 | get() { 196 | return config._size; 197 | }, 198 | set(val) { 199 | config._size = val; 200 | document.body.dataset.size = val; 201 | $.size.textContent = `Size (${val})`; 202 | persist.save('size', val); 203 | } 204 | }); 205 | 206 | async function notify(message) { 207 | const prefs = await chrome.storage.local.get({ 208 | notify: true 209 | }); 210 | if (prefs.notify) { 211 | const id = await chrome.notifications.create({ 212 | type: 'basic', 213 | iconUrl: '/data/icons/48.png', 214 | title: 'Bulk Media Downloader', 215 | message 216 | }); 217 | setTimeout(chrome.notifications.clear, 3000, id); 218 | } 219 | } 220 | function visible(e) { 221 | return Boolean(e.offsetWidth || e.offsetHeight || e.getClientRects().length); 222 | } 223 | 224 | function state() { 225 | const disabled = [...$.links.querySelectorAll('[type=checkbox]:checked')].filter(visible).length === 0; 226 | $.buttons.browser.disabled = 227 | $.buttons.links.disabled = 228 | $.external.run.disabled = 229 | disabled; 230 | } 231 | 232 | function isChecked(tr) { 233 | let checked = false; 234 | if ($.filters.all.checked) { 235 | checked = true; 236 | } 237 | else { 238 | if ($.filters.applications.checked) { 239 | checked = checked || (tr.dataset.type === 'application' || /\.(exe|xpi)/.test(tr.dataset.url)); 240 | } 241 | if ($.filters.images.checked) { 242 | checked = checked || is.image(tr); 243 | } 244 | if ($.filters.videos.checked) { 245 | checked = checked || is.video(tr); 246 | } 247 | if ($.filters.audios.checked) { 248 | checked = checked || is.audio(tr); 249 | } 250 | if ($.filters.archives.checked) { 251 | checked = checked || is.archive(tr); 252 | } 253 | if ($.filters.documents.checked) { 254 | checked = checked || is.document(tr); 255 | } 256 | if ($.filters.tab.checked) { 257 | checked = checked || is.tab(tr); 258 | } 259 | if ($.filters.regexp.value) { 260 | try { 261 | const r = new RegExp($.filters.regexp.value); 262 | checked = checked || r.test(tr.dataset.url); 263 | if (tr.dataset.filename && tr.dataset.filename !== '-') { 264 | checked = checked || r.test(tr.dataset.filename); 265 | } 266 | } 267 | catch (e) {} 268 | } 269 | } 270 | return checked; 271 | } 272 | 273 | function bytesToSize(bytes) { 274 | if (bytes === 0 || bytes === '0') { 275 | return '0 Byte'; 276 | } 277 | const k = 1024; 278 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 279 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 280 | return (bytes / Math.pow(k, i)).toFixed(i ? 1 : 0) + ' ' + sizes[i]; 281 | } 282 | // display or hide a top menu 283 | document.addEventListener('click', e => { 284 | const cmd = e.target.dataset.cmd; 285 | $.head.dataset.select = cmd === 'toggle-select'; 286 | $.head.dataset.filter = cmd === 'toggle-filter'; 287 | $.head.dataset.size = cmd === 'toggle-size'; 288 | }); 289 | 290 | // toggle item's selection on click 291 | document.addEventListener('click', e => { 292 | const target = e.target; 293 | const tr = target.closest('tr'); 294 | if (tr && tr.closest('#links')) { 295 | if (target.localName !== 'input') { 296 | const input = tr.querySelector('[type=checkbox]'); 297 | input.checked = !input.checked; 298 | } 299 | state(); 300 | } 301 | }); 302 | 303 | // commands 304 | document.addEventListener('click', async e => { 305 | const cmd = e.target.dataset.cmd; 306 | if (cmd === 'pause' || cmd === 'resume') { 307 | config.monitor = cmd === 'resume'; 308 | } 309 | else if (cmd === 'clear') { 310 | $.tbody.textContent = ''; 311 | urls = []; 312 | state(); 313 | } 314 | else if (cmd === 'download-browser') { 315 | const items = [...$.links.querySelectorAll(':checked')] 316 | .filter(item => visible(item)); 317 | if (items.length > 10) { 318 | if (!window.confirm('Are you sure you want to download ' + items.length + ' items at once?')) { 319 | return; 320 | } 321 | } 322 | notify(items.length + ' link' + (items.length > 1 ? 's are' : ' is') + ' being downloaded'); 323 | for (let i = 0; i < items.length; i += 5) { 324 | for (let j = 0; j < 5; j += 1) { 325 | const tr = items[i + j]?.closest('tr'); 326 | if (tr) { 327 | const options = { 328 | url: tr.dataset.url 329 | }; 330 | const filename = tr.dataset.filename; 331 | if (filename && filename !== '-') { 332 | options.filename = filename 333 | .replace(/[`~!@#$%^&*()_|+\-=?;:'",<>{}[\]\\/]/gi, ''); 334 | } 335 | 336 | chrome.downloads.download(options, () => { 337 | if (chrome.runtime.lastError) { 338 | delete options.filename; 339 | chrome.downloads.download(options); 340 | } 341 | }); 342 | } 343 | } 344 | await new Promise(resolve => setTimeout(resolve, 2000)); 345 | } 346 | } 347 | else if (cmd === 'copy-links') { 348 | const links = [...$.links.querySelectorAll(':checked')] 349 | .filter(item => visible(item)) 350 | .map(e => e.closest('tr')) 351 | .map(tr => tr.dataset.url); 352 | document.oncopy = e => { 353 | e.clipboardData.setData('text/plain', links.join('\n')); 354 | e.preventDefault(); 355 | }; 356 | window.focus(); 357 | document.execCommand('Copy', false, null); 358 | notify(links.length + ' link' + (links.length > 1 ? 's are' : ' is') + ' copied to the clipboard'); 359 | } 360 | }); 361 | // top menu selection or filter changes 362 | document.addEventListener('click', e => { 363 | const cmd = e.target.dataset.cmd; 364 | 365 | if (cmd && cmd.startsWith('select-')) { 366 | [...$.links.querySelectorAll('[type=checkbox]')] 367 | .forEach(e => { 368 | if (cmd === 'select-all' || cmd === 'select-none') { 369 | e.checked = cmd === 'select-all'; 370 | } 371 | else { 372 | e.checked = is[cmd.replace('select-', '')](e.closest('tr')); 373 | } 374 | }); 375 | state(); 376 | } 377 | else if (cmd && cmd.startsWith('filter-')) { 378 | config.filter = cmd.replace('filter-', ''); 379 | state(); 380 | } 381 | else if (cmd && cmd.startsWith('size-')) { 382 | config.size = cmd.replace('size-', ''); 383 | state(); 384 | } 385 | }); 386 | 387 | function findTitle(message) { 388 | if (message.disposition) { 389 | if (message.disposition.indexOf('inline') !== -1) { 390 | const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(message.disposition); 391 | if (matches && matches.length) { 392 | return matches[1].replace(/['"]/g, ''); 393 | } 394 | } 395 | else { 396 | const matches = /filename=([^;]*)/.exec(message.disposition); 397 | if (matches && matches.length) { 398 | return matches[1].replace(/["']$/, '').replace(/^["']/, ''); 399 | } 400 | } 401 | } 402 | let name = message.url.split('/').pop().split('?').shift(); 403 | let extension = /\.([^.]+)$/.exec(name); 404 | if (extension && extension.length) { 405 | extension = extension[1]; 406 | name = name.replace('.' + extension, ''); 407 | } 408 | else { 409 | extension = message.type.split('/').pop().split('+').shift().split(';').shift(); 410 | } 411 | return name + '.' + extension; 412 | } 413 | 414 | const referrer = (() => { 415 | const cache = {}; 416 | 417 | chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { 418 | if (changeInfo.url) { 419 | cache[tabId] = changeInfo.url; 420 | } 421 | }); 422 | 423 | return (id, tr) => { 424 | if (cache[id]) { 425 | tr.dataset.referrer = cache[id]; 426 | } 427 | else { 428 | chrome.tabs.get(id, t => { 429 | cache[id] = t.url; 430 | tr.dataset.referrer = cache[id]; 431 | }); 432 | } 433 | }; 434 | })(); 435 | 436 | chrome.runtime.onMessage.addListener((message, sender, response) => { 437 | if (message.cmd === 'update-id') { 438 | document.body.dataset.tabId = message.id; 439 | update(); 440 | } 441 | else if (message.cmd === 'bring-to-front') { 442 | response(true); 443 | chrome.runtime.sendMessage({cmd: 'focus'}); 444 | } 445 | }); 446 | 447 | // load 448 | chrome.storage.local.get({ 449 | filter: 'all', 450 | size: 'all' 451 | }, prefs => { 452 | Object.assign(config, prefs); 453 | }); 454 | 455 | // resize 456 | addEventListener('resize', () => { 457 | chrome.storage.local.set({ 458 | width: Math.max(window.outerWidth, 100), 459 | height: Math.max(window.outerHeight, 100) 460 | }); 461 | }); 462 | 463 | // unload 464 | addEventListener('beforeunload', () => { 465 | monitor.deactivate(); 466 | }); 467 | 468 | -------------------------------------------------------------------------------- /v2/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /v3/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------