Greasy Fork is available in English.

tarkov help unlocker

Разблокировывает карты tarkov.help, закрытые за премиумом

// ==UserScript==
// @name         tarkov help unlocker
// @namespace    http://tampermonkey.net/
// @version      1.0.7
// @description  Разблокировывает карты tarkov.help, закрытые за премиумом
// @author       pidorator
// @match        https://tarkov.help/*
// @grant        none
// @license      GNU GPLv3
// @run-at document-start
// ==/UserScript==

(function () {
	"use strict";

	let streetsData,
		shorelineData,
		labsData,
		interchangeData,
		reserveData,
		lighthouseData,
		mapLinkContainer;

	function setupData(data) {
		streetsData = {
			info: data.fakeStreetsInfo,
			markers: data.fakeStreetsMarkers,
			containers: data.fakeStreetsContainers,
			spawns: data.fakeStreetsSpawns,
		};

		shorelineData = {
			info: data.fakeShorelineInfo,
			markers: data.fakeShorelineMarkers,
			containers: data.fakeShorelineContainers,
			spawns: data.fakeShorelineSpawns,
		};

		labsData = {
			info: data.fakeLabsInfo,
			markers: data.fakeLabsMarkers,
			containers: data.fakeLabsContainers,
			spawns: data.fakeLabsSpawns,
		};

		interchangeData = {
			info: data.fakeInterchangeInfo,
			markers: data.fakeInterchangeMarkers,
			containers: data.fakeInterchangeContainers,
			spawns: data.fakeInterchangeSpawns,
		};

		reserveData = {
			info: data.fakeReserveInfo,
			markers: data.fakeReserveMarkers,
			containers: data.fakeReserveContainers,
			spawns: data.fakeReserveSpawns,
		};

		lighthouseData = {
			info: data.fakeLighthouseInfo,
			markers: data.fakeLighthouseMarkers,
			containers: data.fakeLighthouseContainers,
			spawns: data.fakeLighthouseSpawns,
		};

		mapLinkContainer = {
			11: streetsData,
			ulici: streetsData,
			4: shorelineData,
			bereg: shorelineData,
			5: labsData,
			laboratoriya: labsData,
			8: interchangeData,
			razvyazka: interchangeData,
			9: reserveData,
			rezerv: reserveData,
			6: lighthouseData,
			mayak: lighthouseData,
		};
	}

	const dataPromise = fetch("https://glassysundew.github.io/mock.json")
		.then((response) => response.json())
		.then((data) => setupData(data));

	// Сохраняем оригинальный метод `Function.prototype.call`
	const originalCall = Function.prototype.call;

	// Обёртка для метода `call`
	Function.prototype.call = function (...args) {
		let result; // Переменная для хранения результата оригинального вызова

		try {
			// Проверяем, связан ли вызов с объектом `a(300)`
			if (
				args[0] &&
				args[1] &&
				args[0] !== args[0].window &&
				args[1] !== args[1].window &&
				args[1].exports == args[0] &&
				this.name == 300
			) {
				// Выполняем оригинальный вызов и сохраняем результат
				result = originalCall.apply(this, args);

				if (args[1].exports) {
					const originalExports = args[1].exports;

					args[1].exports = new Proxy(originalExports, {
						get(target, prop) {
							if (prop === "d4") {
								const originalD4 = target[prop];

								// Возвращаем обёртку для `d4`
								return function (...innerArgs) {
									var selector = originalD4.apply(
										this,
										innerArgs
									);

									if (
										typeof selector === "object" &&
										selector != null
									) {
										selector.is_premium = true;
									}

									if (selector == null) {
										selector = {
											boosty: false,
											id: 12345,
											is_admin: true,
											is_premium: true,
											roles: [],
											twitch: false,
											username: "noname",
										};
									}

									// Для других типов данных просто возвращаем selector
									return selector;
								};
							}

							// Возвращаем оригинальные значения для всех остальных свойств
							return target[prop];
						},
					});

					console.log("Proxy для exports установлен.");
				}
			} else {
				// Выполняем оригинальный вызов, если это не наш объект
				result = originalCall.apply(this, args);
			}
		} catch (error) {
			console.error("Ошибка при перехвате вызова:", error);
		}

		// Возвращаем результат оригинального вызова
		return result;
	};

	console.log("Function.prototype.call успешно переопределён.");

	//*======================

	const originalOpen = XMLHttpRequest.prototype.open;
	const originalSend = XMLHttpRequest.prototype.send;

	XMLHttpRequest.prototype.open = function (method, url) {
		// Здесь можно проверить URL, например:
		console.log("[XHR Intercept] open:", method, url);
		if (url.includes("/map/")) {
			this._intercepted = true; // пометка, что этот запрос нам интересен
			this._customData = {
				url: url,
			};
		}
		if (url.includes("/subscriptions")) {
			this._intercepted = true; // пометка, что этот запрос нам интересен
			this._prebakedData = JSON.stringify(fakeSub);
		}
		return originalOpen.apply(this, arguments);
	};

	XMLHttpRequest.prototype.send = function (body) {
		const sendArgs = arguments;

		try {
			if (this._intercepted) {
				console.log("[XHR Intercept] send body:", body);

				if (!mapLinkContainer) {
					dataPromise.then((data) => {
						respondWithData(
							this,
							this._customData.url,
							body,
							sendArgs
						);
					});
					return;
				} else {
					respondWithData(this, this._customData.url, body, sendArgs);
					return;
				}
			} else {
				return originalSend.apply(this, arguments);
			}
		} catch (error) {
			console.error("Ошибка при перехвате вызова:", error);
		}
	};

	function respondWithData(xhr, url, body, sendArgs) {
		let responseText = null;
		if ("_prebakedData" in xhr) {
			responseText = xhr._prebakedData;
		} else {
			responseText = JSON.stringify(getFakeMapResponse(url, body));
		}

		if (responseText == "null") {
			return originalSend.apply(xhr, sendArgs);
		}

		//console.log("Возвращаем фейковые данные:", responseText);
		const fakeStatus = 200;
		const fakeStatusText = "OK";
		const fakeResponseURL = xhr._url;

		// Сохраним данные для геттеров
		xhr._fakeStatus = fakeStatus;
		xhr._fakeStatusText = fakeStatusText;
		xhr._fakeResponseText = responseText;
		xhr._fakeResponseURL = fakeResponseURL;
		xhr._fakeReadyState = 4;

		// Определяем геттеры, так как свойства XHR должны быть read-only
		Object.defineProperty(xhr, "status", {
			get: () => xhr._fakeStatus,
		});
		Object.defineProperty(xhr, "statusText", {
			get: () => xhr._fakeStatusText,
		});
		Object.defineProperty(xhr, "responseText", {
			get: () => xhr._fakeResponseText,
		});
		Object.defineProperty(xhr, "response", {
			get: () => xhr._fakeResponseText,
		});
		Object.defineProperty(xhr, "readyState", {
			get: () => xhr._fakeReadyState,
		});
		Object.defineProperty(xhr, "responseURL", {
			get: () => xhr._fakeResponseURL,
		});

		// Фейковые заголовки ответа, если приложение их запрашивает
		Object.defineProperty(xhr, "getAllResponseHeaders", {
			value: function () {
				return "Content-Type: application/json\r\n";
			},
			configurable: true,
			writable: true,
		});
		delete xhr.getResponseHeader;
		Object.defineProperty(xhr, "getResponseHeader", {
			configurable: true,
			writable: true,
			value: function (name) {
				if (name.toLowerCase() === "content-type") {
					return "application/json";
				}
				return null;
			},
		});

		// Асинхронно вызываем события, чтобы имитировать реальный XHR
		setTimeout(() => {
			// onreadystatechange
			if (typeof xhr.onreadystatechange === "function") {
				xhr.onreadystatechange();
			}
			xhr.dispatchEvent(new Event("readystatechange"));

			// onload
			if (typeof xhr.onload === "function") {
				xhr.onload();
			}
			xhr.dispatchEvent(new Event("load"));

			// onloadend
			if (typeof xhr.onloadend === "function") {
				xhr.onloadend();
			}
			xhr.dispatchEvent(new Event("loadend"));
		}, 0);
	}

	function getFakeMapResponse(url, body) {
		const mapNameMatch = url.match(/map\/([^\/]+)\//); // Регулярное выражение для поиска
		console.log(url);
		var mapName = mapNameMatch ? mapNameMatch[1] : null;

		const mapPart = url.split("/map/")[1];
		const typeMatch = mapPart.match(/\/([^\/\?]+)(?:[\/\?]|$)/); // Регулярное выражение для поиска
		var type = typeMatch ? typeMatch[1] : null;

		var originalMapName = mapLinkContainer[mapName];
		if (originalMapName == null) return null;
		var originalData = originalMapName[type];
		if (originalData == null) return null;

		console.log("сырые данные по урл: " + url, originalData);

		const response = JSON.parse(JSON.stringify(originalData));

		if (body !== null) {
			let parsedBody;
			try {
				parsedBody = JSON.parse(body); // Преобразуем строку JSON в объект
			} catch (e) {
				console.error("Ошибка при разборе JSON:", e);
			}

			if (
				parsedBody !== null &&
				parsedBody.limit !== undefined &&
				parsedBody.offset !== undefined
			) {
				response.data = response.data.slice(
					parsedBody.offset,
					parsedBody.offset + parsedBody.limit
				);
			}
		}

		return response;
	}
})();