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