From a7d3a7d8fd0efb287a6e146a9a2830924284dd81 Mon Sep 17 00:00:00 2001 From: shirtjs <2660574+shirt-dev@users.noreply.github.com> Date: Fri, 26 Mar 2021 21:53:49 -0400 Subject: [PATCH] initial commit --- LICENSE | 21 ++++++++ README.md | 7 +++ background.js | 52 ++++++++++++++++++ cadmium-playercore-shim.js | 108 +++++++++++++++++++++++++++++++++++++ content_script.js | 69 ++++++++++++++++++++++++ img/icon128.png | Bin 0 -> 953 bytes img/icon48.png | Bin 0 -> 482 bytes manifest.json | 51 ++++++++++++++++++ netflix_max_bitrate.js | 62 +++++++++++++++++++++ pages/options.html | 23 ++++++++ pages/options.js | 36 +++++++++++++ 11 files changed, 429 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 background.js create mode 100644 cadmium-playercore-shim.js create mode 100644 content_script.js create mode 100644 img/icon128.png create mode 100644 img/icon48.png create mode 100644 manifest.json create mode 100644 netflix_max_bitrate.js create mode 100644 pages/options.html create mode 100644 pages/options.js 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 0000000000000000000000000000000000000000..ba0355ecefea5a796480fd90aad384178664a229 GIT binary patch literal 953 zcmV;q14jIbP)l=thXeos11?EKK~!koo!L)o6Hyce@JWn_3q$k+Xw1f)_7g}c z#jRbWi!Q_>xRm+{YL^QB+XjSg)g-zSB$K#wk;YUZZmMb33I%Itp#A}=&bZJ{?!9l4 z=A5^j;*a;X506EOT z2Lezrd?Elf%v%hgZm~cdp(TL{0;oALfPjkOBLS#R*<=7G1{82&00BMB69iDR!;AvX zFr$F#lr09ZodO@>iRly=u$=+}wo_oB5$Ivgy+Z*r%rM}aVa9>F?i3s_ouYuHaRAdP zIG~1klL7QFu02xEX96@$USRw%RTr98PfO>Sl^l`wK z|3U6maX@uS!~klT8^jUhJ0)xrfa;Vl1fV+QYZV97F#lly)hWagbf>flKn?R60jN&d zCV(L^j1LTn0RXzG!#qBqJEctks@m5G;JLhn?do{IH37U`5Ww#$zJuO5d5gpjdif|N z2IL4wqm&pBAQz?jNyN$V1SOHiUZ!F(_0|;aT`%VD?cbHLN z(cU5oIGut5P7EmEbP5VMoq_^R3v9~&`@4t^1e&k<{f__m z(akH#*E5v|>*2q@$0vV69I_VroyNnf<*Cx?g8sqr6Q$|dN;iz-(W88!JZ1l>T$$4J boY}!&9?DL_t(I%XN`EPQySH0A~{1O`}%00ijT2$%xj5n!AJ ze-be~z*Yn#0-VK+9Oy;>0<78)7zfag7|#9!Jitu^BvMT`O{1N)1PHK}gkX`?ix|!V zrxD-<*pv`#81R=7fB?A-ftdm(0c_i_)z2oF13oR!)F1&!B{bHbs&}e^0E~gW7aCum zc9G>#Q1-(`^89Ce?MCM==9TXb1%=OB;oL6g}KOs@}$;CMruG z#bmjwwlSmLRm1qTbymZe=Rg`U{B513bKwohH89(0ngH) 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 @@ + + + + + Netflix International Options + + + + +
+ +
+ +
+ + +
+ + + + + + \ No newline at end of file diff --git a/pages/options.js b/pages/options.js new file mode 100644 index 0000000..894a21f --- /dev/null +++ b/pages/options.js @@ -0,0 +1,36 @@ +function save_options() { + const use6Channels = document.getElementById("use51").checked; + const showAllTracks = document.getElementById("showAllTracks").checked; + const setMaxBitrate = document.getElementById("setMaxBitrate").checked; + const disableVP9 = document.getElementById("disableVP9").checked; + + chrome.storage.sync.set({ + use6Channels, + showAllTracks, + setMaxBitrate, + disableVP9, + }, function() { + var status = document.getElementById('status'); + status.textContent = 'Options saved.'; + setTimeout(function() { + status.textContent = ''; + }, 750); + }); +} + +function restore_options() { + chrome.storage.sync.get({ + use6Channels: true, + showAllTracks: true, + setMaxBitrate: false, + disableVP9: false, + }, function(items) { + document.getElementById("use51").checked = items.use6Channels; + document.getElementById("showAllTracks").checked = items.showAllTracks; + document.getElementById("setMaxBitrate").checked = items.setMaxBitrate; + document.getElementById("disableVP9").checked = items.disableVP9; + }); +} + +document.addEventListener('DOMContentLoaded', restore_options); +document.getElementById('save').addEventListener('click', save_options); \ No newline at end of file