🏠 Home 

xVKTT

Removing Ads, additional information about the page and many other improvements!


Install this script?
// ==UserScript==
// @name         xVKTT
// @name:en xVKTT
// @license MIT
// @description  Удаление Рекламы, доп. информация о странице и многие другие улучшения!
// @description:en Removing Ads, additional information about the page and many other improvements!
// @author       xZeNice
// @version      29.04.2024
// @match        https://vk.com/*
// @match        https://id.vk.com/*
// @namespace    http://tampermonkey.net/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=vk.com
// @exclude      *://vk.com/notifier.php*
// @exclude      *://vk.com/*widget*.php*
// @run-at       document-start
// @grant        none
// ==/UserScript==
// xVKTT by [ Роман Афанасьев | xZeNice ]
// Скрипт делался по большей части под себя, испытывался в Yandex | Edge | Chrome
//Поддержка автора: https://www.donationalerts.com/r/xzenice
// xVKTT by [ Roman Afanasyev | xZeNice ]
// The script was made mostly for itself, tested in Yandex | Edge | Chrome
//Author's support: https://www.donationalerts.com/r/xzenice
// Создаем объект MutationObserver, который будет следить за изменениями в DOM-дереве
const observer = new MutationObserver(() => {
// Ищем элемент с классом OwnerPageName, который соответствует имени пользователя на странице профиля
const profilePage = document.querySelector('.OwnerPageName');
// Если элемент не найден, прекращаем работу скрипта
if (!profilePage) return;
// Если элемент уже имеет класс display_additional_information_in_vk_profile, значит информация уже добавлена, прекращаем работу
if (profilePage.classList.contains('display_additional_information_in_vk_profile')) return;
// Создаем объект XMLHttpRequest для отправки запроса на текущую страницу
const xhr = new XMLHttpRequest();
// Открываем соединение с сервером, используя метод GET и текущий URL
xhr.open('GET', window.location.href, false);
// Отправляем запрос на сервер
xhr.send();
// Извлекаем ID профиля из HTML-кода страницы, если статус ответа 200 (успешный)
const profileId = xhr.status === 200 ? xhr.responseText.match(/"ownerId":(\d+),"/i)?.[1] : null;
// Если ID профиля не найден, прекращаем работу
if (!profileId) return;
// Добавляем класс display_additional_information_in_vk_profile к элементу OwnerPageName, чтобы предотвратить повторное добавление информации
profilePage.classList.add('display_additional_information_in_vk_profile');
// Создаем объект XMLHttpRequest для отправки запроса на foaf.php с ID профиля
const requestVkFoaf = new XMLHttpRequest();
// Определяем обработчик события загрузки данных
requestVkFoaf.onload = () => {
// Если статус ответа 200 (успешный), обрабатываем данные VK Foaf
if (requestVkFoaf.status === 200) {
// Парсим полученные данные VK Foaf с помощью функции parseVkFoaf
const data = parseVkFoaf(requestVkFoaf.responseText);
// Рендерим дополнительную информацию на странице с помощью функции renderAdditionalInfo
renderAdditionalInfo(profilePage, data, profileId);
}
};
// Открываем соединение с сервером, используя метод GET и URL foaf.php с ID профиля
requestVkFoaf.open('GET', `/foaf.php?id=${profileId}`, true);
// Отправляем запрос на сервер
requestVkFoaf.send();
});
// Функция для парсинга данных VK Foaf и извлечения даты регистрации, последнего редактирования и последнего посещения
const parseVkFoaf = (foafString) => {
return {
vkRegDate: foafString.match(/ya:created dc:date="(.+)"/i)?.[1],
vkLastProfileEditDate: foafString.match(/ya:modified dc:date="(.+)"/i)?.[1],
vkLastSeenDate: foafString.match(/ya:lastLoggedIn dc:date="(.+)"/i)?.[1],
};
};
// Функция для форматирования даты в удобный для пользователя формат
const formatProfileDate = (profileDate) => {
// Если дата не задана, возвращаем пустую строку
if (!profileDate) {
return '';
}
// Создаем объект Date из строки даты
const date = new Date(profileDate);
// Получаем названия месяцев на английском и русском языках
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const russianMonthNames = ['Января', 'Февраля', 'Марта', 'Апреля', 'Мая', 'Июня', 'Июля', 'Августа', 'Сентября', 'Октября', 'Ноября', 'Декабря'];
// Определяем язык страницы и выбираем соответствующее название месяца
const monthName = language === 'ru' ? russianMonthNames[date.getMonth()] : monthNames[date.getMonth()];
// Форматируем дату в соответствии с выбранным языком
return `${date.getDate()} ${monthName} ${date.getFullYear()} ${('0' + date.getHours()).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}:${('0' + date.getSeconds()).slice(-2)}`;
};
// Функция для рендеринга дополнительной информации на странице профиля
const renderAdditionalInfo = (profilePage, data, profileId) => {
// Создаем контейнер для дополнительной информации
const container = document.createElement('div');
// Устанавливаем класс контейнера, чтобы его можно было стилизовать
container.className = 'display_additional_information_in_vk_profile';
// Если дата регистрации найдена, добавляем элемент с датой регистрации в контейнер
if (data.vkRegDate) { container.appendChild(createInfoElement(labels.registration, formatProfileDate(new Date(data.vkRegDate)))); }
// Если дата последнего редактирования найдена, добавляем элемент с датой последнего редактирования в контейнер
if (data.vkLastProfileEditDate) { container.appendChild(createInfoElement(labels.lastEdit, formatProfileDate(new Date(data.vkLastProfileEditDate)))); }
// Если дата последнего посещения найдена, добавляем элемент с датой последнего посещения в контейнер
if (data.vkLastSeenDate) { container.appendChild(createInfoElement(labels.lastSeen, formatProfileDate(new Date(data.vkLastSeenDate)))); }
// Добавляем элемент с ID страницы в контейнер
container.appendChild(createInfoElement(labels.pageId, profileId));
// Добавляем контейнер с информацией на страницу профиля
profilePage.appendChild(container);
};
// Функция для создания элемента с информацией о профиле
const createInfoElement = (label, value) => {
// Создаем новый элемент div
const element = document.createElement('div');
// Устанавливаем класс для стилизации
element.className = 'clear_fix profile_info_row';
// Устанавливаем размер шрифта
element.style.fontSize = '0.75rem';
// Устанавливаем HTML-код элемента
element.innerHTML = `${label} ${value}`;
// Возвращаем созданный элемент
return element;
};
// Создаем объект с подписями для элементов
const labels = {
registration: 'Registration:',
lastEdit: 'Last Edit:',
lastSeen: 'Last Seen:',
pageId: 'Page ID:'
};
// Получаем язык страницы из атрибута lang элемента html
const language = document.documentElement.lang;
// Если язык страницы русский, меняем подписи на русский язык
if (language === 'ru') {
labels.registration = 'Регистрация:';
labels.lastEdit = 'Ред. стр.:';
labels.lastSeen = 'Посл. вход:';
labels.pageId = 'ID Страницы:';
}
// Начинаем наблюдение за изменениями в DOM-дереве
observer.observe(document.body, { childList: true, subtree: true }); // Информация о странице под именем
// (Регистрация, Последнее редактирование страницы, Последний заход и ID)
// Функция для поддержания постоянного онлайн-статуса в VK
function stayOnline() {
fetch('https://vk.com/al_im.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'act=a_enter&al=1',
});
}
// Запускаем функцию stayOnline каждые 5 минут (350000 мс)
setInterval(stayOnline, 350000); // Вечный онлайн
// (Работает до закрытия всех вкладок ВКонтакте)
// Объект PROXY для перехвата запросов, связанных с "печатает..."
const PROXY = {
'vk.com': () => {
// Переопределяем метод send объекта XMLHttpRequest
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = new Proxy(originalSend, {
// Перехватываем вызов метода send
apply(target, thisArg, args) {
const [data] = args;
// Проверяем, содержит ли данные "typing" или "audiomessage"
if (/typing|audiomessage/.test(data)) return null;
// Выполняем оригинальный метод send, если нет "typing" или "audiomessage"
return Reflect.apply(target, thisArg, args);
},
});
},
'm.vk.com': () => {
// Переопределяем функцию fetch
const originalFetch = window.fetch;
window.fetch = async (input, init = {}) => {
// Клонируем объект input
const clonedInput = input.clone();
// Получаем данные из формы
const formData = await clonedInput.formData();
// Проверяем, есть ли в данных "typing"
const isTyping = [...formData.values()].includes('typing');
// Возвращаем пустое Promise, если есть "typing", иначе выполняем оригинальный fetch
return isTyping ? new Promise(() => null) : originalFetch(input, init);
};
},
};
// Получаем хост текущей страницы
const { host } = location;
// Создаем новый скрипт
const script = document.createElement('script');
// Устанавливаем текст скрипта, который выполняет функцию из PROXY для текущего хоста
script.textContent = `(${PROXY[host].toString()})()`;
// Добавляем скрипт в заголовок страницы
document.head.appendChild(script);
// Скрытие "***** Печатает..." у пользователей
// (Работает если перейти в мессенджер, через список чатов который справа снизу — не работает)
// Регулярные выражения для исключения некоторых ссылок
const r0 = /(\/(share|intent\/tweet|submitlink|submit)([^?]*)\?|api\.addthis\.com\/oexchange|cms\/\?url=|downloads\.sourceforge\.net|translate\.google\.)/i; // exclude
const r1 = /[?&](url|r|p|z|to|u|go|goto|q|st\.link|link|href|redirect_url)=([^&]+)(&|$)/i;
const r2 = /(\/leech_out\.php\?.:|\/phpBB2\/goto\/|\/go\/\?)(.+)/i; // Dude Smart Leech (DLE), phpBB2
const r3 = /outgoing\.prod\.mozaws\.net\/v1\/([^/]+)\/(.+)/i; // addons.mozilla.org
const r4 = /([^:]+):([^:]+)(|$)/; // Disqus FIX
// Коды для декодирования
const impCodes = '%3B%2C%2F%3F%3A%40%26%3D%2B%24%23';
// Регулярное выражение для поиска кодов
const impRegex = new RegExp((impCodes.replace(/%/g,'|%').replace('|','')), 'gi');
// Декодированные коды
const impDecoded = decodeURIComponent(impCodes);
// Функция для замены кодов на декодированные значения
const impReplacer = (ch) => impDecoded[impCodes.indexOf(ch.toUpperCase())/3];
// Функция для декодирования важных символов
const decodeImportant = (text) => text.replace(impRegex, impReplacer);
// Обработчик события mouseenter для ссылок
const Handler = (e) => {
// Получаем ссылку
const link = e.target;
// Получаем URL ссылки
let url = link.href;
// Ищем URL, если его нет в текущем элементе
while (!url && link !== this) {
link = link.parentNode;
url = link.href;
}
// Удаляем обработчик события
link.removeEventListener('mouseenter', Handler);
// Проверяем URL
if (!url || url.length < 5 || r0.test(url)) {
return;
}
// Извлекаем целевой URL из ссылки
let tourl = ((url.match(r1) || url.match(r2) || url.match(r3) || [])[2]);
// Проверяем целевой URL
if (!tourl) {
return;
}
try {
// Декодируем целевой URL
tourl = decodeURIComponent(tourl);
tourl = window.atob(tourl);
tourl = decodeURIComponent(tourl);
tourl = escape(tourl);
} catch (err) {}
// Декодируем важные символы
tourl = decodeImportant(tourl);
// Извлекаем протокол и хост из целевого URL
tourl = ((tourl.match(r4)||[])[0]);
// Проверяем, является ли целевой URL ссылкой HTTP или HTTPS
if (tourl && tourl.match(/^http(|s):\/\//i)) {
// Удаляем обработчик onclick ссылки
link.removeAttribute('onclick');
// Устанавливаем атрибут rel ссылки на noreferrer
link.rel = 'noreferrer';
// Устанавливаем новый URL ссылки
link.href = tourl;
}
};
// Функция для добавления обработчиков событий к ссылкам
const attachEvent = (e) => {
// Ищем все ссылки, содержащие "/ " или "?" в href
for (const link of e.querySelectorAll('a[href*="/"], a[href*="?"]')) {
// Добавляем обработчик события mouseenter
link.addEventListener('mouseenter', Handler);
}
};
// Добавляем обработчики событий к ссылкам в текущем документе
attachEvent(document.body);
// Создаем наблюдателя за изменениями DOM
new MutationObserver((ms) => {
// Перебираем изменения DOM
for (const m of ms) {
// Перебираем добавленные узлы
for (const n of m.addedNodes) {
// Проверяем, является ли узел элементом
if (n.nodeType === Node.ELEMENT_NODE) {
// Если узел - ссылка, добавляем обработчик события mouseenter
if (n.href) {
n.addEventListener('mouseenter', Handler);
} else {
// Иначе добавляем обработчики событий к ссылкам в узле
attachEvent(n);
}
}
}
}
}).observe(document, {childList: true, subtree: true});
// Прямые ссылки, отключение "Подозрительная ссылка и т.п"
// Запускаем функцию каждые 5 мс для удаления ненужных элементов
setInterval(function () {
// Селекторы для удаления элементов
const selectorToRemove = [
'#profile_redesigned > div > div > div > div.vkuiPopoutRoot.vkuiSplitLayout > div > div.ScrollStickyWrapper > div > section:nth-child(2)',
'.page_block.apps_feedRightAppsBlock.apps_feedRightAppsBlock_single_app.apps_feedRightAppsBlock_single_app--',
'#react_rootEcosystemAccountMenuEntry > div > div > div.EcosystemAccountButton_row__iVqJo',
'#index_rcolumn > div:nth-child(2) > div > div > div.JoinForm__connectAgreement',
'#top_profile_menu > a.top_profile_mrow.TopProfileItem--appearance',
'#side_bar_inner > div.LegalRecommendationsLinkLeftMenuAuthorized',
'#index_rcolumn > div:nth-child(2) > div > div > div > div',
'#index_rcolumn > div.LegalRecommendationsLink',
'#side_bar_inner > div.left_menu_nav_wrap',
'.page_block.feed_friends_recomm',
'.feed_groups_recomm_friends',
'#friends_right_blocks_root',
'#groups_invites_wrap',
'._ads_block_data_w',
'#feed_row-23_0_0',
'#ads_left',
];
// Находим элементы, соответствующие селекторам
const elementsToRemove = document.querySelectorAll(selectorToRemove);
// Удаляем найденные элементы
elementsToRemove.forEach(element => element.remove());
}, 5);
// Удаление Рекламы / Всяких кнопок и т.п
// Селектор для элемента, который нужно размыть
const blurSelector = '#top_audio_player';
// Селектор для элемента, при наведении на который нужно снять размытие
const hoverSelector = blurSelector;
// Функция для включения/выключения размытия
function toggleBlur(add) {
document.querySelector(blurSelector).style.filter = add ? 'blur(500px)' : 'none';
}
// Обработчик события mouseover
document.addEventListener('mouseover', (e) => {
// Получаем целевой элемент
const target = e.target;
// Проверяем, является ли целевой элемент или его родительский элемент элементом, на который нужно наводить курсор
const isHovered = target.matches(hoverSelector) || target.closest(hoverSelector);
// Включаем/выключаем размытие в зависимости от того, наведен ли курсор на целевой элемент
toggleBlur(!isHovered);
}); // Блюр музыки
// Находим элемент, который нужно переместить
const elementToMove = document.querySelector('#index_rcolumn');
// Если элемент найден, перемещаем его
if (elementToMove) {
// Координаты и размеры элемента
const [pixelsToMoveX, pixelsToMoveY, elementSizeX] = [0, 33, 350];
// Устанавливаем позиционирование элемента
elementToMove.style.position = 'relative';
// Устанавливаем левое смещение элемента
elementToMove.style.left = `${pixelsToMoveX}px`;
// Устанавливаем верхнее смещение элемента
elementToMove.style.top = `${pixelsToMoveY}px`;
// Устанавливаем ширину элемента
elementToMove.style.width = `${elementSizeX}px`;
} // Изменение местоположения окна Авторизации
// Находим элемент "Сохранить вход"
const element = document.querySelector('#index_login > div > form > div.VkIdCheckbox.VkIdCheckbox--save label .VkIdCheckbox__name');
// Если элемент найден, нажимаем на него
element?.click();
// Удаляем элемент "Сохранить вход" через 25 мс
setTimeout(() => {
const elementToRemove = document.querySelector('#index_login > div > form > div.VkIdCheckbox.VkIdCheckbox--save');
elementToRemove?.remove();
}, 25); // Нажатие на "Сохранить вход" и удаление этого элемента
// (Чтобы не предлагало сохранить вход при Авторизации)
// Функция для получения объекта VK
function getVK(){
// Проверяем, доступен ли объект VK
if (!!window.vk){
// Устанавливаем свойство audioAdsConfig объекта VK в null, чтобы отключить аудиорекламу
window.vk.audioAdsConfig = null;
} else {
// Если объект VK недоступен, запускаем функцию getVK через 100 мс
window.setTimeout(getVK, 100);
}
}
// Запускаем функцию getVK после загрузки страницы
(function() {
window.addEventListener('load', function() {
window.setTimeout(getVK, 100);
});
})();
// Отключение аудиорекламы