commit a7d3a7d8fd0efb287a6e146a9a2830924284dd81 Author: shirtjs <2660574+shirt-dev@users.noreply.github.com> Date: Fri Mar 26 21:53:49 2021 -0400 initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6cc57a9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 shirt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4194257 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# netflix-international +Extension to play Netflix with all dubs & subs, 1080p, and 5.1. + +- Firefox: https://addons.mozilla.org/en-US/firefox/addon/netflix-international/ +- Chrome (currently taken down): https://chrome.google.com/webstore/detail/netflix-international/pbbaoiomplacehgkfnlejmibhmbebaal + +Contact me on discord: shirt#1337 \ No newline at end of file diff --git a/background.js b/background.js new file mode 100644 index 0000000..db65091 --- /dev/null +++ b/background.js @@ -0,0 +1,52 @@ +// https://stackoverflow.com/a/45985333 +function getBrowser() { + if (typeof chrome !== "undefined") { + if (typeof browser !== "undefined") { + return "Firefox"; + } else { + return "Chrome"; + } + } else { + return "Edge"; + } +} + +chrome.webRequest.onBeforeRequest.addListener( + function (details) { + /* Allow our shim to load an untouched copy */ + if (details.url.endsWith("?no_filter")) { + return {}; + } + + if (getBrowser() == "Chrome") { + return { + redirectUrl: chrome.extension.getURL("cadmium-playercore-shim.js") + }; + } + + /* Work around funky CORS behaviour on Firefox */ + else if (getBrowser() == "Firefox") { + let filter = browser.webRequest.filterResponseData(details.requestId); + let encoder = new TextEncoder(); + filter.onstop = event => { + fetch(browser.extension.getURL("cadmium-playercore-shim.js")). + then(response => response.text()). + then(text => { + filter.write(encoder.encode(text)); + filter.close(); + }); + }; + return {}; + } + + else { + console.error("Unsupported web browser."); + return {}; + } + }, { + urls: [ + "*://assets.nflxext.com/*/ffe/player/html/*", + "*://www.assets.nflxext.com/*/ffe/player/html/*" + ] + }, ["blocking"] +); diff --git a/cadmium-playercore-shim.js b/cadmium-playercore-shim.js new file mode 100644 index 0000000..5d5b2a0 --- /dev/null +++ b/cadmium-playercore-shim.js @@ -0,0 +1,108 @@ +/* This script runs as a drop-in replacement of the original cadmium-playercore */ +console.log("Netflix International script active!"); + +// promisify chrome storage API for easier chaining +function chromeStorageGet(opts) { + if (getBrowser() == "Firefox") { + return chrome.storage.sync.get(opts); + } + else { + return new Promise(resolve => { + chrome.storage.sync.get(opts, resolve); + }); + } +} + +function do_patch(desc, needle, replacement) { + var match = cadmium_src.match(needle); + if (!match) { + alert("Failed to find patch: " + JSON.stringify(desc)); + } else { + cadmium_src = cadmium_src.replace(needle, replacement); + console.log("[+] Patched: " + JSON.stringify(desc)); + if (match[0].length < 200) { // avoid spamming the console + console.log(JSON.stringify(match[0]) + " -> " + JSON.stringify(replacement)); + } + } +} + +/* We need to do a synchronous request because we need to eval +the response before the body of this script finishes executing */ +var request = new XMLHttpRequest(); +var cadmium_url = document.getElementById("player-core-js").src; +request.open("GET", cadmium_url + "?no_filter", false); // synchronous +request.send(); + +var cadmium_src = request.responseText; + +function get_profile_list() { + custom_profiles = [ + "playready-h264mpl30-dash", + "playready-h264mpl31-dash", + "playready-h264mpl40-dash", + "playready-h264hpl30-dash", + "playready-h264hpl31-dash", + "playready-h264hpl40-dash", + "heaac-2-dash", + "heaac-2hq-dash", + "simplesdh", + "nflx-cmisc", + "BIF240", + "BIF320" + ]; + + if (!globalOptions.disableVP9) { + custom_profiles = custom_profiles.concat([ + "vp9-profile0-L30-dash-cenc", + "vp9-profile0-L31-dash-cenc", + "vp9-profile0-L40-dash-cenc", + ]); + } + + if (globalOptions.use6Channels) { + custom_profiles.push("heaac-5.1-dash"); + } + + return custom_profiles; +} + +do_patch( + "Hello world", + /(.*)/, + "console.log('Netflix International script injected!'); $1" +); + +do_patch( + "Custom profiles", + /(viewableId:.,profiles:).,/, + "$1 get_profile_list()," +); + +do_patch( + "Custom profiles 2", + /(name:"default",profiles:).}/, + "$1 get_profile_list()}" +) + +do_patch( + "Re-enable Ctrl+Shift+Alt+S menu", + /this\...\....\s*\&\&\s*this\.toggle\(\);/, + "this.toggle();" +); + +if (globalOptions.use6Channels) { + do_patch("Show channel indicator", + /displayName:([^\.]{1})\.([^,]{2}),/, + "displayName:$1.$2 + \" - \" + $1.channelsFormat," + ) +} + +if (globalOptions.showAllTracks) { + do_patch("Show all audio tracks", + /"showAllSubDubTracks",!1/, + "\"showAllSubDubTracks\",!0" + ) +} + +// run our patched copy of playercore +eval(cadmium_src); diff --git a/content_script.js b/content_script.js new file mode 100644 index 0000000..cea255a --- /dev/null +++ b/content_script.js @@ -0,0 +1,69 @@ + +script_urls = []; + +urls = [ + 'netflix_max_bitrate.js' +]; + +// https://stackoverflow.com/a/45985333 +function getBrowser() { + if (typeof chrome !== "undefined") { + if (typeof browser !== "undefined") { + return "Firefox"; + } else { + return "Chrome"; + } + } else { + return "Edge"; + } +} + +// promisify chrome storage API for easier chaining +function chromeStorageGet(opts) { + if (getBrowser() == "Firefox") { + return chrome.storage.sync.get(opts); + } + else { + return new Promise(resolve => { + chrome.storage.sync.get(opts, resolve); + }); + } +} + +function attachScript(resp) { + let xhr = resp.target; + let mainScript = document.createElement('script'); + mainScript.type = 'application/javascript'; + if (xhr.status == 200) { + mainScript.text = xhr.responseText; + document.documentElement.appendChild(mainScript); + } +} + +chromeStorageGet({ + use6Channels: true, + showAllTracks: true, + setMaxBitrate: false, + disableVP9: false, +}).then(items => { + // very messy workaround for accessing chrome storage outside of background / content scripts + let mainScript = document.createElement('script'); + mainScript.type = 'application/javascript'; + mainScript.text = `var globalOptions = JSON.parse('${JSON.stringify(items)}');`; + document.documentElement.appendChild(mainScript); +}).then(() => { + // attach and include additional scripts after we have loaded the main configuration + for (let i = 0; i < script_urls.length; i++) { + let script = document.createElement('script'); + script.src = script_urls[i]; + document.documentElement.appendChild(script); + } + + for (let i = 0; i < urls.length; i++) { + let mainScriptUrl = chrome.extension.getURL(urls[i]); + let xhr = new XMLHttpRequest(); + xhr.open('GET', mainScriptUrl, true); + xhr.onload = attachScript; + xhr.send(); + } +}); \ No newline at end of file diff --git a/img/icon128.png b/img/icon128.png new file mode 100644 index 0000000..ba0355e Binary files /dev/null and b/img/icon128.png differ diff --git a/img/icon48.png b/img/icon48.png new file mode 100644 index 0000000..d987966 Binary files /dev/null and b/img/icon48.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..f69148a --- /dev/null +++ b/manifest.json @@ -0,0 +1,51 @@ +{ + "manifest_version": 2, + "name": "Netflix International", + "description": "Displays all available Netflix audio tracks.", + "version": "2.0.0", + "author": "shirt", + "browser_action": { + "default_icon": "img/icon128.png" + }, + "browser_specific_settings": { + "gecko": { + "id": "netflix-international@shirt" + } + }, + "icons": { + "48": "img/icon48.png", + "128": "img/icon128.png" + }, + "content_scripts": [{ + "matches": [ + "*://assets.nflxext.com/*/ffe/player/html/*", + "*://www.assets.nflxext.com/*/ffe/player/html/*", + "*://netflix.com/*", + "*://www.netflix.com/*" + ], + "all_frames": true, + "js": ["content_script.js"], + "run_at": "document_start" + }], + "background": { + "scripts": ["background.js"] + }, + "options_ui": { + "page": "pages/options.html", + "open_in_tab": false + }, + "web_accessible_resources": [ + "cadmium-playercore-shim.js", + "netflix_max_bitrate.js" + ], + "permissions": [ + "storage", + "webRequest", + "webRequestBlocking", + "*://assets.nflxext.com/*/ffe/player/html/*", + "*://www.assets.nflxext.com/*/ffe/player/html/*", + "*://netflix.com/*", + "*://www.netflix.com/*" + ] + } + \ No newline at end of file diff --git a/netflix_max_bitrate.js b/netflix_max_bitrate.js new file mode 100644 index 0000000..a2f3551 --- /dev/null +++ b/netflix_max_bitrate.js @@ -0,0 +1,62 @@ +let getElementByXPath = function (xpath) { + return document.evaluate( + xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null + ).singleNodeValue; +}; + +const fn = function () { + window.dispatchEvent(new KeyboardEvent('keydown', { + keyCode: 83, + ctrlKey: true, + altKey: true, + shiftKey: true, + })); + + const VIDEO_SELECT = getElementByXPath("//div[text()='Video Bitrate']"); + const AUDIO_SELECT = getElementByXPath("//div[text()='Audio Bitrate']"); + const BUTTON = getElementByXPath("//button[text()='Override']"); + + if (!(VIDEO_SELECT && AUDIO_SELECT && BUTTON)){ + return false; + } + + [VIDEO_SELECT, AUDIO_SELECT].forEach(function (el) { + let parent = el.parentElement; + + let options = parent.querySelectorAll('select > option'); + + for (var i = 0; i < options.length - 1; i++) { + options[i].removeAttribute('selected'); + } + + options[options.length - 1].setAttribute('selected', 'selected'); + }); + + BUTTON.click(); + + return true; +}; + +let run = function () { + if (!fn()) { + setTimeout(run, 100); + } +}; + +const WATCH_REGEXP = /netflix.com\/watch\/.*/; + +let oldLocation; + +if(globalOptions.setMaxBitrate) { + console.log("netflix_max_bitrate.js enabled"); + setInterval(function () { + let newLocation = window.location.toString(); + + if (newLocation !== oldLocation) { + oldLocation = newLocation; + if (WATCH_REGEXP.test(newLocation)) { + run(); + } + } + }, 500); +} diff --git a/pages/options.html b/pages/options.html new file mode 100644 index 0000000..8fdee4a --- /dev/null +++ b/pages/options.html @@ -0,0 +1,23 @@ + + + +
+