Bonk NSFW map filter

Blocks NSFW bonk maps

当前为 2024-07-18 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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});
})();