Blocks NSFW bonk maps
当前为
// ==UserScript==
// @name Bonk NSFW map filter
// @description Blocks NSFW bonk maps
// @namespace salama.xyz
// @author Salama
// @version 0.2
// @match https://bonk.io/gameframe-release.html
// @match https://bonkisback.io/gameframe-release.html
// @grant none
// ==/UserScript==
////////////////////////
//// SETTINGS ////
////////////////////////
// No semicolons for ease of use
// true = yes
// false = no
// If false, maps will be blurred instead
const HIDE_MAPS_FROM_MAP_SELECTOR = true
// If true, map details won't be blurred
const BLUR_ONLY_MAP_PREVIEW = false
const UNBLUR_MAP_ON_MOUSE_HOVER = false
const INCLUDE_REMIXES_OF_NSFW_MAPS = true
// Blocklist is cached for 5 * 60 sec = 5 minutes
const CACHE_DURATION = 5 * 60
/* DEFAULT SETTINGS
const HIDE_MAPS_FROM_MAP_SELECTOR = true
const BLUR_ONLY_MAP_PREVIEW = false
const UNBLUR_MAP_ON_MOUSE_HOVER = false
const INCLUDE_REMIXES_OF_NSFW_MAPS = true
const CACHE_DURATION = 5 * 60
*/
////////////////////////
/// CODE ////
////////////////////////
'use strict';
const NSFWLIST_VERSION = 0;
const global = {
cacheTime: 0,
NSFWList: [],
NSFWMaps: new Set()
};
function requestHandler(original) {
return function(url,body,success,type) {
// Send request
const response = original.apply(this, arguments);
// Hijack response callback
const responseDone = response.done;
response.done = function(responseCallback) {
/* The originally synchronous responseCallback can
* be replaced with an asynchronous function, because
* its return value is never saved or used anywhere.
*/
const originalResponseCallback = responseCallback;
responseCallback = async function(data, status) {
// Data is sometimes string and sometimes JSON
let wasParsed = false;
if (typeof data === "string") {
try {
let parsed = JSON.parse(data);
wasParsed = true;
data = parsed;
}
catch {
wasParsed = false;
}
}
if (typeof data === "object") {
// If the request response contains a map array
if (Object.keys(data).includes("maps")) {
const NSFWList = await getNSFWList();
// TODO finish checks
if (/^G/.test(read(pretty_top_level)) ||
await isOK([read(pretty_top_name)]) ||
pushOK(read(pretty_top_name))
) {
data.maps.ok = true;
}
for (let i = 0; i < data.maps.length; i++) {
const map = data.maps[i];
const hash = await sha256(
["Amye9CHqRTs", await sha256(map.id.toString()), map.authorname].join("")
);
if(await isOK(map)) continue;
if (NSFWList.includes(hash)) {
global.NSFWMaps.add(map.id);
}
else if (INCLUDE_REMIXES_OF_NSFW_MAPS) {
if (map.remixid > 0) {
const rxhash = await sha256(
["Amye9CHqRTs", await sha256(map.remixid.toString()), map.rxa].join("")
);
if (NSFWList.includes(rxhash)) {
global.NSFWMaps.add(map.id);
}
}
}
}
}
}
if(wasParsed) {
data = JSON.stringify(data);
}
// Call original response callback
return originalResponseCallback.apply(this, arguments);
}
// Set our own function as the response callback
return responseDone.call(this, responseCallback);
}
return response;
}
}
async function isOK(map) {
return new Promise(async resolve => {
resolve(getOK().includes(
await sha256(map[Object.keys(map).sort((a, b) => a.localeCompare(b))[0]])
));
});
}
async function getNSFWList() {
return new Promise(resolve => {
if (Date.now() - global.cacheTime > CACHE_DURATION) {
window.$.get("https://gist.githubusercontent.com/Salama/c93f26e0468aa743453339c8c993adaa/raw").done(r => {
let NSFWList = r.split("\n");
let version = parseInt(NSFWList.splice(0, 1));
if(version !== NSFWLIST_VERSION) {
alert("NSFW map blocker is outdated!");
// Prevent future requests
global.cacheTime = Infinity;
resolve([]);
return;
}
global.NSFWList = NSFWList;
});
}
resolve(global.NSFWList);
});
}
async function sha256(text) {
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await window.crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
return hashHex;
}
async function pushOK(props) {
let current = getOK();
current.push(await sha256(props));
return window.localStorage.setItem("nsfwok", current.join(""));
}
function getOK() {
let ok = window.localStorage.getItem("nsfwok");
if (!ok) {
window.localStorage.setItem("nsfwok", "");
return [];
}
return [...ok.match(/.{64}/g)];
}
function read(e) {
return e.textContent;
}
function addBlurStyle() {
let blurStyle = document.createElement("style");
blurStyle.innerHTML = `
.blurNSFW {
overflow: hidden;
}
.blurNSFW > .maploadwindowtextname {
filter: blur(6px);
}
.blurNSFW > .maploadwindowtextauthor {
filter: blur(4px);
}
.blurNSFW > img {
filter: blur(12px);
}
.hoverUnblurNSFW:hover > .maploadwindowtextname {
filter: unset !important;
}
.hoverUnblurNSFW:hover > .maploadwindowtextauthor {
filter: unset !important;
}
.hoverUnblurNSFW:hover > img {
filter: unset !important;
}
`;
document.head.appendChild(blurStyle);
}
(async () => {
addBlurStyle();
// Hijack requests
const jqGet = requestHandler(window.$.get);
const jqPost = requestHandler(window.$.post);
window.$.get = jqGet;
window.$.post = jqPost;
getNSFWList();
const mapObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (global.NSFWMaps.has(node.map.m.dbid)) {
if (HIDE_MAPS_FROM_MAP_SELECTOR) {
node.remove();
}
else {
node.classList.add("blurNSFW");
if (BLUR_ONLY_MAP_PREVIEW) {
node.getElementsByClassName("maploadwindowtextname")[0].style.filter = "unset";
node.getElementsByClassName("maploadwindowtextauthor")[0].style.filter = "unset";
}
if (UNBLUR_MAP_ON_MOUSE_HOVER) {
node.classList.add("hoverUnblurNSFW");
}
}
}
}
}
});
mapObserver.observe(document.getElementById("maploadwindowmapscontainer"), {childList: true});
})();