- // ==UserScript==
- // @name Netflix 备份工具
- // @namespace NetflixBackupTools
- // @version 0.6
- // @description 快速备份和迁移 Netflix 账号内容
- // @author TGSAN
- // @include /https{0,1}\:\/\/www.netflix.com/browse(\/.*){0,1}/
- // @run-at document-end
- // @grant GM_unregisterMenuCommand
- // @grant GM_registerMenuCommand
- // @grant unsafeWindow
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- let netflixApi = "https://www.netflix.com/api/aui";
- try {
- netflixApi = unsafeWindow.netflix.reactContext.models.playerModel.data.config.ui.initParams.apiUrl;
- } catch {
- console.log("获取 Netflix API 失败");
- try {
- netflixApi = "https://www.netflix.com/api/shakti/" + netflix.appContext.state.model.models.serverDefs.data.BUILD_IDENTIFIER
- } catch {
- console.log("获取 Netflix UI Version 失败");
- }
- }
-
- function createToast() {
- let toast = document.createElement("div");
- toast.style.position = "fixed";
- toast.style.top = "20px";
- toast.style.left = "50%";
- toast.style.transform = "translateX(-50%)";
- toast.style.padding = "10px 20px";
- toast.style.backgroundColor = "rgba(250, 250, 250, 1.0)";
- toast.style.color = "rgba(32, 32, 32, 1.0)";
- toast.style.fontSize = "12px";
- toast.style.fontWeight = "600";
- toast.style.zIndex = "9999";
- toast.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.25)";
- toast.style.borderRadius = "30px";
- toast.style.opacity = "0.0";
- toast.style.transition = "opacity 0.5s";
- document.body.appendChild(toast);
- return toast;
- }
-
- function showToast(message, time = 1500) {
- let toast = createToast();
- toast.innerText = message;
- setTimeout(function () {
- toast.style.opacity = "1.0";
- setTimeout(function () {
- toast.style.opacity = "0.0";
- setTimeout(function () {
- document.body.removeChild(toast);
- }, 500);
- }, time);
- }, 1);
- }
-
- function dateFormat(dataObj, fmt) {
- var o = {
- "M+": dataObj.getMonth() + 1, //月份
- "d+": dataObj.getDate(), //日
- "h+": dataObj.getHours(), //小时
- "m+": dataObj.getMinutes(), //分
- "s+": dataObj.getSeconds(), //秒
- "q+": Math.floor((dataObj.getMonth() + 3) / 3), //季度
- "S": dataObj.getMilliseconds() //毫秒
- };
- if (/(y+)/.test(fmt)) {
- fmt = fmt.replace(RegExp.$1, (dataObj.getFullYear() + "").substr(4 - RegExp.$1.length));
- }
- for (var k in o) {
- if (new RegExp("(" + k + ")").test(fmt)) {
- fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
- }
- }
- return fmt;
- }
-
- function backup() {
- let userAuthURL;
- try {
- userAuthURL = unsafeWindow.netflix.reactContext.models.userInfo.data.authURL;
- } catch {
- alert("无法获取 Netflix API 验证密钥");
- return;
- }
- let userName = "UnknownUser";
- try {
- userName = unsafeWindow.netflix.reactContext.models.userInfo.data.name;
- } catch {
- alert("无法获取用户名称,将使用默认名称备份");
- }
- let userGuid = "UnknownGUID";
- try {
- userGuid = unsafeWindow.netflix.reactContext.models.userInfo.data.userGuid;
- } catch { }
-
- let body = "path=" +
- encodeURIComponent(JSON.stringify([
- "mylist",
- {
- "from": 0,
- "to": 1000 // Netflix 最多支持一次吐 1333 个(0-1332)
- }
- ])) +
- "&authURL=" +
- encodeURIComponent(userAuthURL);
- fetch(
- netflixApi + "/pathEvaluator",
- {
- "body": body,
- "method": "POST",
- "mode": "cors",
- "credentials": "include"
- }
- ).then(function (res) {
- if (res.ok) {
- res.json().then(function (jsonRes) {
- let mylist = new Array();
- if (jsonRes.value != undefined && jsonRes.value.mylist != undefined) {
- let jsonResList = jsonRes.value.mylist;
- // jsonResList = [["", ""]];
- console.log(jsonResList);
- for (const [key, value] of Object.entries(jsonResList)) {
- if (value != undefined) {
- if (value.length > 1) {
- if (value[1] != undefined) {
- mylist.push(value[1]);
- }
- }
- }
- }
- }
- if (mylist.length > 0) {
- var enc = new TextEncoder();
- const link = document.createElement('a');
- link.href = URL.createObjectURL(new Blob([enc.encode(JSON.stringify(mylist))]));
- link.download = "netflix-mylist-" + userName + "-" + userGuid + "-" + dateFormat(new Date(), "yyyyMMddhhmmss") + ".json";
- link.click();
- window.URL.revokeObjectURL(link.href);
- alert("备份完成!(备份中共有 " + mylist.length + " 个剧集)");
- } else {
- alert("播放列表为空(包括锁区内容),如果播放列表非空可尝试进入播放列表页面后再试");
- }
- }).catch(function (err) {
- alert("无法获取播放列表(无法解析接口返回的结果),备份失败\n" + err);
- })
- } else {
- alert("无法获取播放列表(接口访问失败),备份失败\nStatus: " + res.status);
- }
- }).catch(function (err) {
- alert("无法获取播放列表,备份失败\n" + err);
- });
- }
-
- function backupOld() {
- let mylistData;
- try {
- mylistData = unsafeWindow.netflix.falcorCache.mylist;
- } catch { }
- if (mylistData != undefined) {
- let userName = "UnknownUser";
- try {
- userName = unsafeWindow.netflix.reactContext.models.userInfo.data.name;
- } catch {
- alert("无法获取用户名称,将使用默认名称备份");
- }
- let userGuid = "UnknownGUID";
- try {
- userGuid = unsafeWindow.netflix.reactContext.models.userInfo.data.userGuid;
- } catch { }
- let mylist = new Array();
- for (const [key, value] of Object.entries(mylistData)) {
- if (value?.value && value.value[1] != undefined) {
- mylist.push(value.value[1]);
- }
- }
- // let mylist = Object.keys(netflix.falcorCache.videos);
- if (mylist.length > 0) {
- var enc = new TextEncoder();
- const link = document.createElement('a');
- link.href = URL.createObjectURL(new Blob([enc.encode(JSON.stringify(mylist))]));
- link.download = "netflix-mylist-" + userName + "-" + userGuid + "-" + dateFormat(new Date(), "yyyyMMddhhmmss") + ".json";
- link.click();
- window.URL.revokeObjectURL(link.href);
- alert("备份完成!(备份中共有 " + mylist.length + " 个剧集)");
- } else {
- alert("播放列表为空(包括锁区内容),如果播放列表非空可尝试进入播放列表页面后再试");
- }
- } else {
- if (document.location.pathname != "/browse/my-list") {
- alert("无法获取播放列表,即将跳转到播放列表页面,跳转结束后请重新尝试备份");
- document.location = "/browse/my-list";
- } else {
- alert("无法获取播放列表,备份失败");
- }
- }
-
- }
-
- function restore() {
- let userAuthURL;
- try {
- userAuthURL = unsafeWindow.netflix.reactContext.models.userInfo.data.authURL;
- } catch {
- alert("无法获取 Netflix API 验证密钥");
- return;
- }
- const fileInput = document.createElement("input");
- fileInput.id = "file";
- fileInput.type = "file";
- fileInput.style.display = "none";
- fileInput.addEventListener('change', function () {
- if (this.files.length === 0) {
- return;
- }
- const reader = new FileReader();
- reader.onload = function () {
- let result = reader.result;
- try {
- let mylist = JSON.parse(result);
- if (mylist.length < 1) {
- alert("备份文件为空");
- }
- try {
- let index = 0;
- let doFetch = function () {
- if (index < mylist.length) {
- showToast("正在还原备份(" + (index + 1) + "/" + mylist.length + ")");
- let body = {
- "operation": "add",
- "videoId": mylist[index],
- // "trackId": 253896178,
- "authURL": userAuthURL
- };
- fetch(
- netflixApi + "/playlistop",
- {
- "body": JSON.stringify(body),
- "method": "POST",
- "mode": "cors",
- "credentials": "include"
- }
- ).then(function () {
- index++;
- doFetch();
- }).catch(function () {
- alert("还原备份失败");
- });
- } else {
- alert("还原备份成功!(备份中共有 " + mylist.length + " 个剧集)");
- }
- };
- doFetch();
- } catch {
- alert("还原备份失败");
- }
- } catch {
- alert("解析备份失败");
- }
- };
- reader.onerror = function () {
- alert("读取备份失败");
- };
- reader.readAsText(this.files[0]);
- });
- // document.body.appendChild(fileInput);
- fileInput.click();
- // document.body.removeChild(fileInput);
- }
-
- GM_registerMenuCommand("备份播放列表", backup);
- GM_registerMenuCommand("还原播放列表", restore);
- })();