// ==UserScript==
// @name Netflix 备份工具
// @namespace NetflixBackupTools
// @version 0.4
// @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/shakti/vadb0bb81";
try {
netflixApi = unsafeWindow.netflix.reactContext.models.playerModel.data.config.ui.initParams.apiUrl;
} catch {
console.log("获取 Netflix API 失败");
}
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";
toast.style.backgroundColor = "rgba(255, 255, 255, 1.0)";
toast.style.color = "black";
toast.style.zIndex = "9999";
toast.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.5)";
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);
})();