Ограничение телеметрии мессенджера MAX
// ==UserScript==
// @name MAX Blinder
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Ограничение телеметрии мессенджера MAX
// @author Echo91
// @match https://*.max.ru/*
// @license MIT
// @run-at document-start
// @grant none
// ==/UserScript==
(function() {
'use strict';
const CONFIG = {
BLOCK_FETCH_XHR: true,
BLOCK_WEBSOCKET_OPCODE5: true,
BLOCK_WEBRTC: true,
BLOCK_BEACON: true,
PROTECT_CANVAS: true,
PROTECT_CANVAS_TO_DATA_URL: true,
PROTECT_AUDIO: true,
HIDE_WEBDRIVER: true,
HIDE_CONNECTION: true,
FAKE_PLUGINS: true,
FIXED_SCREEN: true,
LOG_SERVICE_WORKER: true
};
let blockedCount = 0;
let fullLogHistory = [];
const maxLogs = 20;
let pendingLogs = [];
let uiShadow = null;
const blackList = [
'api.ipify.org', 'ifconfig.me', 'ident.me', 'checkip.amazonaws.com',
'ip.mail.ru', '2ip.ru', 'ipinfo.io', 'ip-api.com', 'myexternalip.com',
'icanhazip.com', 'jsonip.com', 'httpbin.org/ip', 'wtfismyip.com',
'apptracer.ru', 'sdk-api', 'crash', 'metrics', 'telemetry', 'analytics',
'vigo', 'collector', 'log-api', 'error_report', 'notify-stat', 'event/send',
'tracker-api.vk-analytics.ru', 'my.tracker', 'data.mail.ru', 'fb.do',
'doubleclick.net', 'google-analytics.com', 'top-fwz1.mail.ru', 'counter.yadro.ru'
];
function shouldBlock(url) {
if (!url || !CONFIG.BLOCK_FETCH_XHR) return false;
const sUrl = String(url).toLowerCase();
return blackList.some(bad => sUrl.includes(bad));
}
const makeNative = (obj, prop) => {
const original = obj[prop];
if (typeof original === 'function') {
Object.defineProperty(original, 'toString', {
value: () => `function ${prop}() { [native code] }`,
configurable: true,
writable: true
});
}
};
// --- ЛОГИРОВАНИЕ (без времени в интерфейсе) ---
function addEntryToLog(container, data) {
const entry = document.createElement('div');
entry.style.cssText = 'border-bottom: 1px solid rgba(255,255,255,0.1); padding: 4px 0; color: #ffcc00; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 9px;';
// Время не выводим, только тип и URL
entry.innerHTML = `<span style="color: #ff4d4d;">[${data.type}]</span> ${data.url}`;
container.prepend(entry);
if (container.childNodes.length > maxLogs) container.removeChild(container.lastChild);
}
function logBlock(type, url) {
blockedCount++;
const time = new Date().toLocaleTimeString();
let displayUrl = String(url).split('?')[0];
try {
const urlObj = new URL(url);
displayUrl = urlObj.hostname + urlObj.pathname;
} catch(e) {}
const entryData = {
type: type,
url: displayUrl,
full: `[${time}] [${type}] ${url}` // время только здесь
};
fullLogHistory.push(entryData.full);
if (uiShadow) {
const counter = uiShadow.getElementById('pm-counter');
if (counter) counter.innerText = blockedCount;
const logContainer = uiShadow.getElementById('pm-log-list');
if (logContainer) {
addEntryToLog(logContainer, entryData);
return;
}
}
pendingLogs.push(entryData);
}
// --- СЕТЕВЫЕ ПЕРЕХВАТЫ (как в 15.6) ---
if (CONFIG.BLOCK_FETCH_XHR) {
window.fetch = new Proxy(window.fetch, {
apply(target, thisArg, args) {
const url = typeof args[0] === 'object' ? args[0].url : args[0];
if (shouldBlock(url)) {
logBlock('FETCH', url);
return Promise.resolve(new Response('{"status":"ok"}', { status: 200 }));
}
return Reflect.apply(target, thisArg, args);
}
});
makeNative(window, 'fetch');
const origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
this._isTracker = shouldBlock(url);
this._blockUrl = url;
return origOpen.apply(this, arguments);
};
makeNative(XMLHttpRequest.prototype, 'open');
const origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
if (this._isTracker) {
logBlock('XHR', this._blockUrl);
setTimeout(() => {
Object.defineProperty(this, 'readyState', { value: 4 });
Object.defineProperty(this, 'status', { value: 200 });
Object.defineProperty(this, 'responseText', { value: '{"status":"ok"}' });
this.dispatchEvent(new Event('load'));
this.dispatchEvent(new Event('readystatechange'));
}, 1);
return;
}
return origSend.apply(this, arguments);
};
makeNative(XMLHttpRequest.prototype, 'send');
}
if (CONFIG.BLOCK_WEBSOCKET_OPCODE5) {
const origWSSend = WebSocket.prototype.send;
WebSocket.prototype.send = function(data) {
if (typeof data === 'string' && data.includes('"opcode"')) {
try {
const msg = JSON.parse(data);
if (msg.opcode === 5) {
logBlock('WS-TELE', `opcode 5 blocked`);
return;
}
} catch (e) {}
}
return origWSSend.apply(this, arguments);
};
makeNative(WebSocket.prototype, 'send');
}
if (CONFIG.BLOCK_BEACON) {
const origBeacon = navigator.sendBeacon;
navigator.sendBeacon = function(url, data) {
if (shouldBlock(url)) {
logBlock('BEACON', url);
return true;
}
return origBeacon.call(this, url, data);
};
makeNative(navigator, 'sendBeacon');
}
// --- ЗАЩИТА ОТ ФИНГЕРПРИНТИНГА ---
try {
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
if (CONFIG.PROTECT_CANVAS) {
const orgGetImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
const imageData = orgGetImageData.call(this, x, y, w, h);
const data = imageData.data;
if (data.length > 0) {
data[0] = Math.min(255, Math.max(0, data[0] + (Math.random() > 0.5 ? 1 : -1)));
}
return imageData;
};
}
if (CONFIG.PROTECT_CANVAS_TO_DATA_URL && CONFIG.PROTECT_CANVAS) {
const orgToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type, quality) {
const canvas = this;
const ctx = canvas.getContext('2d');
if (ctx && canvas.width > 0 && canvas.height > 0) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
}
return orgToDataURL.call(this, type, quality);
};
makeNative(HTMLCanvasElement.prototype, 'toDataURL');
}
if (CONFIG.PROTECT_AUDIO && window.AudioContext) {
const originalGetChannelData = AudioBuffer.prototype.getChannelData;
AudioBuffer.prototype.getChannelData = function(channel) {
const originalData = originalGetChannelData.call(this, channel);
const noisyData = new Float32Array(originalData.length);
for (let i = 0; i < originalData.length; i++) {
noisyData[i] = originalData[i] + (Math.random() * 0.00001 - 0.000005);
}
return noisyData;
};
makeNative(AudioBuffer.prototype, 'getChannelData');
}
if (CONFIG.BLOCK_WEBRTC && window.RTCPeerConnection) {
const originalRTCPeerConnection = window.RTCPeerConnection;
window.RTCPeerConnection = function(config) {
config = config || {};
config.iceServers = [];
config.iceTransportPolicy = 'relay';
const pc = new originalRTCPeerConnection(config);
pc.addIceCandidate = function() {
return Promise.resolve();
};
return pc;
};
window.RTCPeerConnection.prototype = originalRTCPeerConnection.prototype;
}
if (CONFIG.HIDE_CONNECTION && 'connection' in navigator) {
const connection = navigator.connection;
if (connection) {
Object.defineProperty(connection, 'effectiveType', { get: () => '4g' });
Object.defineProperty(connection, 'downlink', { get: () => 10 });
Object.defineProperty(connection, 'rtt', { get: () => 50 });
}
}
if (CONFIG.HIDE_WEBDRIVER && navigator.webdriver !== undefined) {
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
}
if (CONFIG.FAKE_PLUGINS && navigator.plugins) {
const fakePlugins = [
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
];
const pluginArray = Object.create(PluginArray.prototype);
pluginArray.length = fakePlugins.length;
fakePlugins.forEach((p, i) => {
pluginArray[i] = p;
pluginArray[p.name] = p;
});
pluginArray.item = (index) => pluginArray[index];
pluginArray.namedItem = (name) => fakePlugins.find(p => p.name === name) || null;
Object.defineProperty(pluginArray, Symbol.toStringTag, { value: 'PluginArray', configurable: true, writable: false });
Object.defineProperty(navigator, 'plugins', { get: () => pluginArray, configurable: true });
const fakeMimes = [
{ type: 'application/pdf', suffixes: 'pdf', description: '' },
{ type: 'text/pdf', suffixes: 'pdf', description: '' }
];
const mimeArray = Object.create(MimeTypeArray.prototype);
mimeArray.length = fakeMimes.length;
fakeMimes.forEach((m, i) => {
mimeArray[i] = m;
mimeArray[m.type] = m;
});
mimeArray.item = (index) => mimeArray[index];
mimeArray.namedItem = (type) => fakeMimes.find(m => m.type === type) || null;
Object.defineProperty(mimeArray, Symbol.toStringTag, { value: 'MimeTypeArray', configurable: true, writable: false });
Object.defineProperty(navigator, 'mimeTypes', { get: () => mimeArray, configurable: true });
}
if (CONFIG.LOG_SERVICE_WORKER && navigator.serviceWorker && navigator.serviceWorker.register) {
const originalRegister = navigator.serviceWorker.register;
navigator.serviceWorker.register = function(scriptURL, options) {
console.log("📦 Service Worker registered:", scriptURL);
return originalRegister.call(this, scriptURL, options);
};
makeNative(navigator.serviceWorker, 'register');
}
} catch (e) {}
// --- ИНТЕРФЕЙС В SHADOW DOM С ТЁМНЫМ СКРОЛЛОМ ---
function createUI() {
if (document.getElementById('privacy-monitor-shadow-host')) return;
const host = document.createElement('div');
host.id = 'privacy-monitor-shadow-host';
host.style.cssText = 'all: initial; display: block;';
(document.body || document.documentElement).appendChild(host);
const shadow = host.attachShadow({ mode: 'open' });
uiShadow = shadow;
// Стили для тёмного скролла
const style = document.createElement('style');
style.textContent = `
#pm-log-list::-webkit-scrollbar {
width: 6px;
height: 6px;
}
#pm-log-list::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
}
#pm-log-list::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
#pm-log-list::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
`;
shadow.appendChild(style);
const main = document.createElement('div');
main.id = 'privacy-monitor';
Object.assign(main.style, {
position: 'fixed', bottom: '15px', right: '20px', zIndex: '2147483647',
background: 'rgba(15, 15, 15, 0.7)', color: '#00ff00', padding: '10px 14px',
borderRadius: '10px', fontSize: '11px', fontFamily: 'monospace',
boxShadow: '0 8px 32px rgba(0,0,0,0.5)', backdropFilter: 'blur(10px)', pointerEvents: 'auto'
});
main.innerHTML = `
<div id="pm-header" style="display: flex; justify-content: space-between; align-items: center; min-width: 130px; cursor: pointer;">
<span>🪬 BLINDER: <span id="pm-counter">${blockedCount}</span></span>
<span id="pm-arrow">▲</span>
</div>
<div id="pm-content" style="display: none; margin-top: 10px; border-top: 1px solid rgba(255,255,255,0.2); padding-top: 8px; width: 260px;">
<div id="pm-log-list" style="max-height: 150px; overflow-y: auto; margin-bottom: 8px;"></div>
<div style="display: flex; gap: 6px;">
<button id="pm-copy" style="flex: 1; background: rgba(50,50,50,0.8); color: #0f0; border: none; padding: 2px 4px; cursor: pointer; border-radius: 3px; font-size: 9px; height: 18px;">Copy log</button>
<button id="pm-clear" style="flex: 1; background: rgba(50,50,50,0.8); color: #f44; border: none; padding: 2px 4px; cursor: pointer; border-radius: 3px; font-size: 9px; height: 18px;">Clear log</button>
</div>
</div>
`;
main.querySelector('#pm-header').onclick = () => {
const content = main.querySelector('#pm-content');
const arrow = main.querySelector('#pm-arrow');
const isOpen = content.style.display === 'block';
content.style.display = isOpen ? 'none' : 'block';
arrow.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(180deg)';
};
// Кнопка копирования с обратной связью
main.querySelector('#pm-copy').onclick = async (e) => {
e.stopPropagation();
const btn = e.target;
const originalText = btn.innerText;
try {
await navigator.clipboard.writeText(fullLogHistory.join('\n'));
btn.innerText = 'Copied!';
setTimeout(() => btn.innerText = originalText, 1000);
} catch (err) {
console.warn('Clipboard API failed, trying fallback...', err);
try {
const textarea = document.createElement('textarea');
textarea.value = fullLogHistory.join('\n');
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
btn.innerText = 'Copied!';
setTimeout(() => btn.innerText = originalText, 1000);
} catch (fallbackErr) {
console.error('Fallback copy failed:', fallbackErr);
btn.innerText = 'Error!';
setTimeout(() => btn.innerText = originalText, 1500);
}
}
};
main.querySelector('#pm-clear').onclick = (e) => {
e.stopPropagation();
blockedCount = 0;
fullLogHistory = [];
pendingLogs = [];
main.querySelector('#pm-counter').innerText = '0';
main.querySelector('#pm-log-list').innerHTML = '';
};
shadow.appendChild(main);
// Добавляем накопленные записи
const logContainer = shadow.getElementById('pm-log-list');
while (pendingLogs.length > 0) {
addEntryToLog(logContainer, pendingLogs.shift());
}
shadow.getElementById('pm-counter').innerText = blockedCount;
}
setInterval(createUI, 1500);
})();