// ==UserScript== // @name Git游戏存档隔离脚本 // @version 0.1.1 // @description 隔离gityx不同游戏的存储内容,防止存储冲突造成坏档、丢档 // @author DreamNya // @match *://*.github.io/* // @match *://*.g8hh.com.cn/* // @match *://*.g8hh.com/* // @icon https://gityx.com/static/web/images/github.png // @grant none // @run-at document-start // @license MIT // @namespace https://scriptcat.org/zh-CN/users/55086 // ==/UserScript== const prefix = getPrefix(); const prototype = Reflect.ownKeys(Storage.prototype); const deepProp = ["getItem", "setItem", "removeItem"]; const deepProxy = {}; // 根据网址中游戏名计算前缀 function getPrefix() { if (location.hostname.includes("github.io") || location.hostname.includes("yx.g8hh.com")) { return location.pathname.split("/").filter((i) => i)[0] + "_"; } if (location.hostname.includes("g8hh.com")) { return location.hostname.split(".")[0] + "_"; } return ""; } deepProp.forEach((key) => { deepProxy[key] = new Proxy(localStorage[key], { // 拦截方法调用 apply: (target, thisArgument, args) => { args[0] = prefix + args[0]; return Reflect.apply(target, thisArgument, args); }, }); }); window.realLocalStorage = window.localStorage; window.hooklocalStorage = new Proxy(window.localStorage, { // 拦截不用方法而是直接访问的傻鸟 get: (target, property, receiver) => { // 原型链上方法不劫持 if (!prototype.includes(property)) { property = prefix + property; } // 特殊处理 if (property == "length") { return Object.keys(window.hooklocalStorage).length; } if (property == "key") { return (index) => Object.values(window.hooklocalStorage)[index]; } if (property == "clear") { return () => Object.keys(window.hooklocalStorage).forEach((k) => window.hooklocalStorage.removeItem(k)); } const ref = deepProp.includes(property) ? Reflect.get(deepProxy, property, receiver) : Reflect.get(target, property, receiver); // 方法拦截需要绑定this,否则抛出非法调用错误 return ref?.bind ? ref.bind(window.realLocalStorage) : ref; }, set: (target, property, newValue, receiver) => { if (!prototype.includes(property)) { property = prefix + property; } return Reflect.set(target, property, newValue, receiver); }, // 拦截遍历方法 ownKeys: (target) => { const replacement = new RegExp("^" + prefix); return Reflect.ownKeys(target) .filter((key) => key.startsWith(prefix)) .map((key) => key.replace(replacement, "")); }, // Object.keys() 通过ownKeys拦截 无法返回不存在的属性 // 解决方案:https://stackoverflow.com/questions/65339985/ // 属实牛逼 太优雅了 👍 // eslint-disable-next-line no-unused-vars getOwnPropertyDescriptor: (target, prop) => { return { enumerable: true, configurable: true, }; }, has: (target, property) => { if (!prototype.includes(property)) { property = prefix + property; } return Reflect.has(target, property); }, }); // Proxy 做不到的事来了 Object.defineProperty(window, "localStorage", { get: () => window.hooklocalStorage, }); console.log("%c Git游戏存档隔离脚本 加载成功\nprefix: " + prefix, "color:blue");