Загрузка данных
// ==UserScript==
// @name Intim Mirrors - Block Images
// @namespace https://a.intimcity.promo/
// @version 3.4.10
// @description Fast image/contact blocking and risky-word censoring for intim mirrors
// @author You
// @match *://*/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function () {
"use strict";
const STORAGE_KEY = "tm_intim_block_images_enabled";
const TOGGLE_ID = "tm-image-toggle-btn";
const STYLE_ID = "tm-block-images-style";
const CONTACT_STYLE_ID = "tm-hide-contacts-style";
const SEARCH_STYLE_ID = "tm-search-visibility-style";
const CONTACT_KEEP_SELECTOR = "#headerBg, #headerBg *, .main_link1, .main_link1 *, #search, #search *";
const USERINFO_KEEP_SELECTOR = ".userinfo-link, .userinfo, [itemprop='author']";
const COMMENT_TEXTAREA_SELECTOR = "textarea[readonly], textarea[name='comment_text'], textarea[id^='user_comment_']";
const IMAGE_KEEP_ROOT_SELECTOR = "#headerBg, .main_link1, .up-ocenki-basic, .up-ocenki-dop";
const COMPARE_TRIGGER_SELECTOR =
'[class*="sravn"], [id*="sravn"], [class*="compare"], [id*="compare"], [onclick*="sravn"], [onclick*="compare"]';
const HIDDEN_CONTACT_ATTR = "data-tm-contact-hidden";
const HOST_RE = /intim/i; // Works for changing mirrors with "intim" in host.
const CONTACT_LINK_SELECTOR = [
'a[href^="tel:"]',
'a[href^="callto:"]',
'a[href^="sms:"]',
'a[href*="wa.me/"]',
'a[href*="api.whatsapp.com"]',
'a[href*="whatsapp"]',
'a[href*="viber"]',
'a[href*="tg://"]',
'a[href*="t.me/"]',
'[class*="phone"]',
'[class*="whatsapp"]',
'[class*="viber"]',
'[class*="telegram"]',
'[id*="phone"]',
'[id*="whatsapp"]',
'[id*="viber"]',
'[id*="telegram"]',
"[data-phone]",
"[data-tel]",
"[data-whatsapp]",
"[data-viber]",
"[data-telegram]",
].join(", ");
const CONTACT_ATTR_SCAN_SELECTOR =
"[href], [onclick], [title], [aria-label], [data-phone], [data-tel], [data-whatsapp], [data-viber], [data-telegram]";
const CONTACT_ATTR_NAME_RE = /(?:^|[-_:.])(phone|tel|whatsapp|viber|telegram|tg)(?:[-_:.]|$)/i;
const CONTACT_VALUE_RE =
/(tel:|callto:|sms:|wa\.me|api\.whatsapp\.com|whatsapp|viber|tg:\/\/|t\.me\/|telegram|\bтелефон\b|\bномер\b|\bphone\b|\btel\b)/i;
const IMAGE_ATTR_FILTER = ["src", "srcset", "poster", "style", "data-src", "data-srcset"];
const CONTACT_ATTR_FILTER = [
"href",
"onclick",
"title",
"aria-label",
"data-phone",
"data-tel",
"data-whatsapp",
"data-viber",
"data-telegram",
];
// Max strict mode for short-video platforms.
// Edit lists only; censor regex is generated automatically.
// Use "*" at the end to match word forms by stem, e.g. "проститут*" => "проститутка/проститутки".
const FORBIDDEN_WORDS = {
adult: [
"18+",
"xxx",
"порн*",
"porn*",
"nsfw",
"эрот*",
"интим*",
"адалт*",
"adult",
"секс*",
"cекс*",
"cек*",
"sex*",
"sexy",
"nude*",
"nudity",
"голый",
"голая",
"голые",
"голыш*",
"обнажен*",
"обнаж*",
"фетиш*",
"bdsm",
"бдсм",
"минет*",
"куни*",
"аналь*",
"оральн*",
"oral*",
"оргия*",
"дроч*",
"мастурб*",
"онлифанс",
"onlyfans",
"only fans",
"вебкам*",
"webcam*",
"camgirl*",
"cam model*",
"секс-услуг*",
"sexwork*",
"sex work*",
],
escort: [
"эскорт*",
"эск##т",
"escort*",
"проститут*",
"проституц*",
"шлюх*",
"шалав*",
"девушка по вызову",
"девочки по вызову",
"по вызову",
"интим услуги",
],
intimacyServices: [
"классич* секс*",
"секс класс*",
"группов* секс*",
"аналь*",
"ана##*",
"минет*",
"ми##т*",
"горлов* минет*",
"кунилингус*",
"поцелу*",
"окончание в рот",
"окончание на лицо",
"окончание на грудь",
"член*",
"глот*",
"конч*",
"сквирт*",
"виртуальн* секс*",
"секс по телефону",
"телефонн* секс*",
"эротическ* массаж*",
"эро#######*",
"урологическ* массаж*",
"лингам*",
"карсай*",
"страпон*",
"анилингус*",
"золот* дожд*",
"копро*",
"фистинг*",
"фингеринг*",
"садо-мазо",
"садо мазо",
"бандаж*",
"госпожа*",
"игры*",
"легк* доминац*",
"порк*",
"рабын*",
"фетиш*",
"фе##ш*",
"футфетиш*",
"трамплинг*",
"клизм*",
"боллбастинг*",
"ballbusting*",
"феминизац*",
"размер имеет значение",
"услуги семейной паре",
],
violenceSelfHarm: [
"насили*",
"изнасил*",
"убий*",
"убить",
"расстрел*",
"зарез*",
"задуш*",
"пытк*",
"избиени*",
"жесток*",
"жесть",
"мясоруб*",
"кровав*",
"труп*",
"расчлен*",
"суицид*",
"самоубий*",
"самоповрежд*",
"селфхарм*",
"selfharm*",
"self-harm*",
"suicide*",
"kill*",
"murder*",
"dead body",
"corpse",
"повес*",
"вскрыть вены",
"резать вены",
"вены вскры*",
],
drugsAlcoholTobacco: [
"наркот*",
"закладк*",
"закладчик*",
"драг*",
"drug*",
"cocaine",
"кокаин*",
"heroin",
"героин*",
"meth",
"метамфет*",
"амфет*",
"мефедрон*",
"mdma",
"мдма",
"экстази",
"ecstasy",
"спайс*",
"соль*",
"марихуан*",
"конопл*",
"каннабис*",
"гашиш*",
"weed",
"420",
"алкогол*",
"бухл*",
"бухат*",
"пьян*",
"водк*",
"виски",
"whiskey",
"пиво",
"вино",
"самогон*",
"табак*",
"сигарет*",
"сигар*",
"вейп*",
"vape*",
"никотин*",
"smok*",
"курен*",
],
hateDiscrimination: [
"расист*",
"расизм*",
"нацист*",
"нацизм*",
"фаш*",
"ксенофоб*",
"дискриминац*",
"унижен*",
"оскорбл*",
"hatespeech",
"hate speech",
"slur*",
"негр*",
"ниггер*",
"nigg*",
"chink*",
"spic*",
"kike*",
"trann*",
"транн*",
"fagg*",
"пидор*",
"пидр*",
"гомик*",
"жидов*",
"жиден*",
"хач*",
"чурк*",
"blackface",
"white power",
"supremac*",
],
crimeScam: [
"мошенн*",
"скам*",
"scam*",
"обман*",
"развод*",
"краж*",
"грабеж*",
"угон*",
"взлом*",
"hack*",
"хак*",
"phishing*",
"фишинг*",
"кардинг*",
"carding*",
"даркнет*",
"darknet*",
"отмыв*",
"подделк*",
"фейк док*",
"подделка документов",
"вымогат*",
"шантаж*",
"террор*",
"экстрем*",
"пропаганд* террор",
"вербовк*",
"crypto pump",
"памп*",
"дамп*",
"фин пирами*",
"пирамид*",
"дропп*",
"обнал*",
],
weaponsExplosives: [
"оружи*",
"огнестрел*",
"пистолет*",
"револьвер*",
"автомат*",
"штурмовая винтовк*",
"shotgun*",
"rifle*",
"gun*",
"ammo*",
"патрон*",
"нож*",
"кастет*",
"гранат*",
"бомб*",
"взрыв*",
"взрывчат*",
"терракт*",
"теракт*",
"стрельб*",
"поджог*",
],
gamblingBetting: [
"казино*",
"casino*",
"ставк*",
"бетт*",
"betting*",
"букмекер*",
"лудоман*",
"gambl*",
"джекпот*",
"слот*",
"рулетк*",
"тотализатор*",
"покер*",
],
medicalMisinformation: [
"чудо-лекарств*",
"чудо средство",
"гарантированно лечит",
"вылечит рак",
"лечит рак",
"без врачей",
"без операций",
"анорекс*",
"булими*",
"быстрое похудение",
"похудеть за 3 дня",
"похудеть за неделю на 10",
"таблетки для похудения",
"fat burner",
],
minorsAndAbuse: [
"педоф*",
"несовершеннолетн*",
"малолетк*",
"child porn*",
"lolicon*",
"лоликон*",
"incest*",
"инцест*",
"grooming*",
"груминг*",
],
bloggerEuphemisms: [
"запрещенк*",
"деликатная тема",
"контент 18",
"клубничк*",
"клубника",
"темки 18",
"серый контент",
"gray content",
"грязные деньги",
"быстрый заработок",
"легкие деньги",
"гарантированный доход",
"100% прибыль",
],
profanityRu: [
"бля*",
"бляд*",
"еб*",
"ёб*",
"пизд*",
"хуй*",
"хер*",
"наху*",
"нахер*",
"сук*",
"мраз*",
"твар*",
"гандон*",
"мудак*",
"долбоеб*",
"долбаеб*",
"ублюд*",
"уеб*",
"шмар*",
"сран*",
"срать*",
"сру*",
"засра*",
"говн*",
"петух*",
"чмо*",
],
profanityEn: [
"fuck*",
"shit*",
"bitch*",
"asshole*",
"motherf*",
"cunt*",
"whore*",
"slut*",
"dick*",
"cock*",
"pussy*",
"bastard*",
"retard*",
"wtf",
"stfu",
],
};
const FORBIDDEN_WORD_TOKENS = Object.values(FORBIDDEN_WORDS).flat();
const RISKY_WORD_REGEX = compileForbiddenWordRegex(FORBIDDEN_WORD_TOKENS);
const TEXT_SANITIZE_HINT_RE = compileSanitizeHintRegex(FORBIDDEN_WORD_TOKENS);
const OBSERVER_ATTR_FILTER = Array.from(new Set([...IMAGE_ATTR_FILTER, ...CONTACT_ATTR_FILTER]));
if (!HOST_RE.test(location.hostname)) return;
const blockingEnabled = localStorage.getItem(STORAGE_KEY) !== "0";
const CSS_BLOCK = `
img,
picture,
source,
svg image,
video[poster],
[style*="background-image"]:not(#headerBg):not(#headerBg *):not(.main_link1):not(.main_link1 *):not(.up-ocenki-basic):not(.up-ocenki-basic *):not(.up-ocenki-dop):not(.up-ocenki-dop *) {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
*:not(#headerBg):not(#headerBg *):not(.main_link1):not(.main_link1 *):not(.up-ocenki-basic):not(.up-ocenki-basic *):not(.up-ocenki-dop):not(.up-ocenki-dop *) {
background-image: none !important;
}
.up-ocenki-basic img,
.up-ocenki-dop img {
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
}
`;
const CSS_HIDE_CONTACTS = `
a[href^="tel:"],
a[href^="callto:"],
a[href^="sms:"],
a[href*="wa.me/"],
a[href*="api.whatsapp.com"],
a[href*="whatsapp"],
a[href*="viber"],
a[href*="tg://"],
a[href*="t.me/"],
[data-phone],
[data-tel],
[data-whatsapp],
[data-viber],
[data-telegram] {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
`;
const CSS_SEARCH_VISIBILITY = `
#search,
#search .searchphone,
#search form {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
#search #searchStr {
background: #ffffff !important;
color: #111111 !important;
border: 1px solid #777777 !important;
border-radius: 4px !important;
padding: 4px 8px !important;
}
#search #searchPS {
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
min-width: 74px !important;
min-height: 28px !important;
padding: 4px 10px !important;
border: 1px solid #666666 !important;
border-radius: 4px !important;
background: #f2f2f2 !important;
color: #111111 !important;
cursor: pointer !important;
text-indent: 0 !important;
line-height: 1.2 !important;
}
#search #menuRight2 {
color: #f2f2f2 !important;
}
#search #menuRight2 a.menu {
color: #f2f2f2 !important;
text-decoration: underline !important;
}
#search #menuRight2 a.menu[style*="#CC0000"],
#search #menuRight2 a.menu[style*="#cc0000"] {
color: #ff5959 !important;
}
`;
function appendNode(node) {
const root = document.documentElement || document.head || document.body;
if (root) {
root.appendChild(node);
return;
}
document.addEventListener(
"readystatechange",
() => {
const nextRoot = document.documentElement || document.head || document.body;
if (nextRoot && !node.isConnected) nextRoot.appendChild(node);
},
{ once: true }
);
}
function setBlockingState(enabled) {
localStorage.setItem(STORAGE_KEY, enabled ? "1" : "0");
}
function injectToggleButton() {
if (document.getElementById(TOGGLE_ID)) return;
const style = document.createElement("style");
style.textContent = `
#${TOGGLE_ID} {
position: fixed !important;
right: 12px !important;
bottom: 12px !important;
z-index: 2147483647 !important;
padding: 8px 12px !important;
border: 1px solid #333 !important;
border-radius: 8px !important;
font: 12px/1.2 Arial, sans-serif !important;
cursor: pointer !important;
color: #fff !important;
background: ${blockingEnabled ? "#b00020" : "#0a7a2f"} !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35) !important;
}
#${TOGGLE_ID}:hover {
filter: brightness(1.08) !important;
}
`;
appendNode(style);
const btn = document.createElement("button");
btn.id = TOGGLE_ID;
btn.type = "button";
btn.textContent = blockingEnabled ? "Изображения: OFF" : "Изображения: ON";
btn.title = "Переключить и перезагрузить";
btn.addEventListener("click", () => {
setBlockingState(!blockingEnabled);
location.reload();
});
const mount = () => {
if (!document.body) return;
document.body.appendChild(btn);
};
if (document.body) {
mount();
} else {
document.addEventListener("DOMContentLoaded", mount, { once: true });
}
}
function injectBlockCss() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = CSS_BLOCK;
appendNode(style);
}
function injectContactCss() {
if (document.getElementById(CONTACT_STYLE_ID)) return;
const style = document.createElement("style");
style.id = CONTACT_STYLE_ID;
style.textContent = CSS_HIDE_CONTACTS;
appendNode(style);
}
function injectSearchCss() {
if (document.getElementById(SEARCH_STYLE_ID)) return;
const style = document.createElement("style");
style.id = SEARCH_STYLE_ID;
style.textContent = CSS_SEARCH_VISIBILITY;
appendNode(style);
}
function repairSearchUi(root) {
if (!(root instanceof Document || root instanceof Element)) return;
const btn = root.querySelector("#searchPS");
if (btn instanceof HTMLInputElement && btn.type === "submit") {
if (!btn.value || !btn.value.trim()) {
btn.value = "Найти";
}
}
}
function escapeForRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function compileForbiddenWordRegex(tokens) {
const uniq = Array.from(new Set(tokens.map((v) => (v || "").trim().toLowerCase()).filter(Boolean)));
const variants = [];
for (const token of uniq) {
const isStem = token.endsWith("*");
const base = isStem ? token.slice(0, -1) : token;
if (!base) continue;
const escaped = escapeForRegex(base);
const variant = isStem ? `${escaped}[\\p{L}\\p{N}_-]*` : escaped;
variants.push(variant);
}
if (!variants.length) return null;
return new RegExp(`(?<![\\p{L}\\p{N}_])(?:${variants.join("|")})(?![\\p{L}\\p{N}_])`, "giu");
}
function compileSanitizeHintRegex(tokens) {
const chunks = Array.from(
new Set(
tokens
.map((v) => (v || "").trim().toLowerCase().replace(/\*+$/g, ""))
.filter(Boolean)
.map(escapeForRegex)
)
);
if (!chunks.length) return /\d/;
return new RegExp(`\\d|${chunks.join("|")}`, "i");
}
function hasPhoneLikePattern(value) {
if (!value || !/\d/.test(value)) return false;
const hasPhoneCue = /(тел|телефон|phone|tel|номер|call|wa\.me|whatsapp|viber|telegram|tg)/i.test(value);
const matches = value.match(/(?:\+?\d[\d\s().-]{8,}\d)/g);
if (!matches) return false;
return matches.some((chunk) => {
const digits = chunk.replace(/\D/g, "");
if (digits.length < 10 || digits.length > 15) return false;
const hasDelimiter = /[\s().-]/.test(chunk) || chunk.startsWith("+");
return hasDelimiter || hasPhoneCue;
});
}
function maskPhoneText(value) {
return value.replace(/(?:\+?\d[\d\s().-]{8,}\d)/g, (chunk) => {
const digits = chunk.replace(/\D/g, "");
if (digits.length < 10 || digits.length > 15) return chunk;
return "номер скрыт";
});
}
function maskTokenWithHashes(token) {
const chars = Array.from(token);
const letterLikeIndexes = [];
for (let i = 0; i < chars.length; i += 1) {
if (/[\p{L}\p{N}]/u.test(chars[i])) {
letterLikeIndexes.push(i);
}
}
if (letterLikeIndexes.length <= 2) {
if (letterLikeIndexes.length === 0) return token;
if (letterLikeIndexes.length === 1) return "#";
chars[letterLikeIndexes[1]] = "#";
return chars.join("");
}
// Keep readability for "конч*" forms (e.g. "кончил" -> "к#нчил").
if (/^конч/iu.test(token) && letterLikeIndexes.length >= 2) {
const secondLetterIndex = letterLikeIndexes[1];
chars[secondLetterIndex] = "#";
return chars.join("");
}
// Keep readability for "член*" forms (e.g. "член" -> "чл#н").
if (/^член/iu.test(token) && letterLikeIndexes.length >= 3) {
const thirdLetterIndex = letterLikeIndexes[2];
chars[thirdLetterIndex] = "#";
return chars.join("");
}
// Soft mode: keep words understandable by masking only 1 char (2 for very long tokens).
const logicalMaskIndexes = [1];
if (letterLikeIndexes.length >= 10) {
logicalMaskIndexes.push(Math.floor(letterLikeIndexes.length / 2));
}
for (const logicalIndex of logicalMaskIndexes) {
const sourceIndex = letterLikeIndexes[logicalIndex];
if (sourceIndex === undefined) continue;
chars[sourceIndex] = "#";
}
return chars.join("");
}
function maskWordWithHashes(value) {
const tokenMatches = Array.from(value.matchAll(/[\p{L}\p{N}]+/gu));
if (!tokenMatches.length) return value;
if (tokenMatches.length === 1) {
return value.replace(/[\p{L}\p{N}]+/u, (token) => maskTokenWithHashes(token));
}
// For multi-word phrases, mask only the first word to keep text understandable.
const first = tokenMatches[0];
const start = first.index || 0;
const token = first[0];
const masked = maskTokenWithHashes(token);
return `${value.slice(0, start)}${masked}${value.slice(start + token.length)}`;
}
function maskRiskyWords(value) {
if (!RISKY_WORD_REGEX) return value;
RISKY_WORD_REGEX.lastIndex = 0;
return value.replace(RISKY_WORD_REGEX, (word) => maskWordWithHashes(word));
}
function sanitizeText(value) {
if (!value || !TEXT_SANITIZE_HINT_RE.test(value)) return value;
let next = maskPhoneText(value);
next = maskRiskyWords(next);
return next;
}
function isInsideImageKeepArea(el) {
if (!(el instanceof Element)) return false;
if (el.matches(IMAGE_KEEP_ROOT_SELECTOR)) return true;
return Boolean(el.closest(IMAGE_KEEP_ROOT_SELECTOR));
}
function isInsideContactKeepArea(el) {
if (!(el instanceof Element)) return false;
if (el.matches(CONTACT_KEEP_SELECTOR)) return true;
return Boolean(el.closest("#headerBg, .main_link1, #search"));
}
function isUserInfoElement(el) {
if (!(el instanceof Element)) return false;
if (el.matches(USERINFO_KEEP_SELECTOR)) return true;
return Boolean(el.closest(USERINFO_KEEP_SELECTOR));
}
function hideContactElement(el) {
if (!(el instanceof Element)) return;
if (isInsideContactKeepArea(el) || isUserInfoElement(el)) {
if (el.hasAttribute(HIDDEN_CONTACT_ATTR)) {
el.removeAttribute(HIDDEN_CONTACT_ATTR);
el.style.removeProperty("display");
el.style.removeProperty("visibility");
el.style.removeProperty("opacity");
}
return;
}
if (el.hasAttribute(HIDDEN_CONTACT_ATTR)) return;
el.setAttribute(HIDDEN_CONTACT_ATTR, "1");
el.style.setProperty("display", "none", "important");
el.style.setProperty("visibility", "hidden", "important");
el.style.setProperty("opacity", "0", "important");
if (el.tagName === "A") {
el.removeAttribute("href");
}
el.removeAttribute("onclick");
}
function shouldHideByAttributes(el) {
if (!(el instanceof Element)) return false;
if (isUserInfoElement(el)) return false;
const attrNames = el.getAttributeNames();
for (const attrName of attrNames) {
const value = (el.getAttribute(attrName) || "").trim();
const name = attrName.toLowerCase();
const allowPhonePattern =
name === "href" ||
name === "title" ||
name === "aria-label" ||
name === "value" ||
name.startsWith("data-");
if (CONTACT_ATTR_NAME_RE.test(name) && value) return true;
if (value && CONTACT_VALUE_RE.test(value)) return true;
if (value && allowPhonePattern && hasPhoneLikePattern(value)) return true;
}
return false;
}
function sanitizeTextNode(node) {
if (!(node instanceof Text)) return;
if (!node.nodeValue) return;
const parent = node.parentElement;
if (!parent) return;
const tag = parent.tagName;
if (tag === "SCRIPT" || tag === "STYLE" || tag === "NOSCRIPT" || tag === "TEXTAREA") return;
const next = sanitizeText(node.nodeValue);
if (next !== node.nodeValue) {
node.nodeValue = next;
}
}
function scanSanitizedText(root) {
if (!(root instanceof Document || root instanceof Element)) return;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
let node = walker.nextNode();
while (node) {
sanitizeTextNode(node);
node = walker.nextNode();
}
}
function sanitizeCommentTextarea(el) {
if (!(el instanceof HTMLTextAreaElement)) return;
const source = el.value || el.textContent || "";
if (!source) return;
const next = sanitizeText(source);
if (next === source) return;
el.value = next;
el.textContent = next;
}
function scanSanitizedCommentFields(root) {
if (!(root instanceof Document || root instanceof Element)) return;
if (root instanceof HTMLTextAreaElement && root.matches(COMMENT_TEXTAREA_SELECTOR)) {
sanitizeCommentTextarea(root);
}
root.querySelectorAll(COMMENT_TEXTAREA_SELECTOR).forEach((el) => {
sanitizeCommentTextarea(el);
});
}
function forEachMatch(root, selector, cb) {
if (!(root instanceof Document || root instanceof Element)) return;
if (root instanceof Element && root.matches(selector)) cb(root);
root.querySelectorAll(selector).forEach(cb);
}
function scanContacts(root) {
if (!(root instanceof Document || root instanceof Element)) return;
forEachMatch(root, CONTACT_LINK_SELECTOR, hideContactElement);
forEachMatch(root, CONTACT_ATTR_SCAN_SELECTOR, (el) => {
if (shouldHideByAttributes(el)) hideContactElement(el);
});
scanSanitizedText(root);
scanSanitizedCommentFields(root);
}
function neutralize(el) {
if (!(el instanceof Element)) return;
if (!isInsideContactKeepArea(el)) {
if (el.matches(CONTACT_LINK_SELECTOR) || shouldHideByAttributes(el)) {
hideContactElement(el);
}
}
if (!blockingEnabled) return;
if (isInsideImageKeepArea(el)) return;
if (el.matches("img")) {
el.removeAttribute("src");
el.removeAttribute("srcset");
el.removeAttribute("data-src");
el.removeAttribute("data-srcset");
el.removeAttribute("data-original");
el.removeAttribute("data-lazy");
el.removeAttribute("data-lazy-src");
return;
}
if (el.matches("source")) {
el.removeAttribute("src");
el.removeAttribute("srcset");
return;
}
if (el.matches("video[poster]")) {
el.removeAttribute("poster");
}
if (el.hasAttribute("style")) {
const style = el.getAttribute("style") || "";
if (/background-image\s*:\s*url\(/i.test(style)) {
el.setAttribute(
"style",
style.replace(/background-image\s*:[^;]+;?/gi, "background-image:none !important;")
);
}
}
}
function scanImages(root) {
if (!(root instanceof Document || root instanceof Element)) return;
const list = root.querySelectorAll(
"img, source, picture, svg image, video[poster], [style*='background-image']:not(#headerBg):not(#headerBg *):not(.main_link1):not(.main_link1 *):not(.up-ocenki-basic):not(.up-ocenki-basic *):not(.up-ocenki-dop):not(.up-ocenki-dop *)"
);
list.forEach(neutralize);
}
function scan(root) {
if (!(root instanceof Document || root instanceof Element)) return;
scanContacts(root);
repairSearchUi(root);
if (blockingEnabled) scanImages(root);
}
function triggerCompareRescan() {
setTimeout(() => scan(document), 0);
setTimeout(() => scan(document), 150);
setTimeout(() => scan(document), 450);
}
function observeCompareTriggers() {
document.addEventListener(
"click",
(event) => {
if (!(event.target instanceof Element)) return;
if (!event.target.closest(COMPARE_TRIGGER_SELECTOR)) return;
triggerCompareRescan();
},
true
);
}
function observe() {
const root = document.documentElement;
if (!root) {
document.addEventListener("DOMContentLoaded", observe, { once: true });
return;
}
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.type === "characterData") {
sanitizeTextNode(m.target);
continue;
}
if (m.type === "attributes" && m.target instanceof Element) {
neutralize(m.target);
continue;
}
if (m.type === "childList") {
m.addedNodes.forEach((n) => {
if (n instanceof Element) {
neutralize(n);
scan(n);
return;
}
if (n instanceof Text) {
sanitizeTextNode(n);
}
});
}
}
});
obs.observe(root, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeFilter: OBSERVER_ATTR_FILTER,
});
}
injectToggleButton();
injectContactCss();
injectSearchCss();
scanContacts(document);
repairSearchUi(document);
observeCompareTriggers();
observe();
if (blockingEnabled) {
injectBlockCss();
scanImages(document);
}
document.addEventListener("DOMContentLoaded", () => {
scan(document);
});
})();