Checks the last online on page user and in dialog
// ==UserScript== // @name VK: Check Online // @name:ru ВК: Проверка онлайна // @description Checks the last online on page user and in dialog // @description:ru Проверяет последний онлайн пользователя на странице и в диалогe // @namespace vk-check-online.user.js // @license MIT // @author askornot // @version 1.5.2 // @match https://vk.com/* // @connect vk.com // @compatible chrome Violentmonkey 2.18.0 // @compatible firefox Violentmonkey 2.13.3 // @compatible firefox Tampermonkey 4.18.1 // @homepageURL https://greasyfork.org/en/scripts/403717-vk-check-online // @supportURL https://greasyfork.org/en/scripts/403717-vk-check-online/feedback // @run-at document-start // @noframes // ==/UserScript== (function (W) { 'use strict'; const IM_ID = 'payload,1,1,0,id'; const IM_ONLINE = 'payload,1,0,last_seen'; const PROFILE_ID = 'response,users.get,0,id'; const PROFILE_ONLINE = 'response,users.get,0,online_info'; const IM_INFO = { can_see: 1, platform: 0, time: 0, mobile: 0, }; const PROFILE_INFO = { visible: true, last_seen: 0, app_id: 0, is_online: false, is_mobile: false, }; const last = (arr) => arr[arr.length - 1]; const match = (s) => /online_info|a_start/.test(s); const getByPath = (data, dataPath) => { const path = dataPath.split(','); let object = data; for (const name of path) { const next = object[name]; if (next === null || next === undefined) return next; object = next; } return object; }; const setByPath = (data, dataPath, value) => { const path = dataPath.split(','); const len = path.length; let obj = data; let i = 0; let next, prop; for (;;) { if (typeof obj !== 'object') return false; prop = path[i]; if (i === len - 1) { obj[prop] = value; return true; } next = obj[prop]; if (next === undefined || next === null) { next = {}; obj[prop] = next; } obj = next; i++; } }; const parse = (text) => { const parser = new DOMParser(); const body = parser.parseFromString(text, 'text/html'); const [element] = body.getElementsByTagName('ya:lastloggedin'); if (!element) return 0; const date = element.getAttribute('dc:date'); return Date.parse(date) / 1000; }; const request = (url, callback) => { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState === xhr.DONE) { if (xhr.status === 0 || xhr.status === 200) { callback(xhr.responseText); } } }; xhr.open('GET', url, true); xhr.send(); }; const foaf = (id) => `https://vk.com/foaf.php?id=${id}`; const profile = (data) => { const info = getByPath(data, PROFILE_ONLINE); if (info && info.visible === true) return Promise.resolve(data); const id = getByPath(data, PROFILE_ID); if (id === undefined) return Promise.resolve(data); return fetch(foaf(id)) .then((res) => res.text()) .then(parse) .then((time) => { setByPath(data, PROFILE_ONLINE, { ...PROFILE_INFO, last_seen: time }); return data; }); }; const im = (data, callback) => { const info = getByPath(data, IM_ONLINE); if (info && info.can_see === 1) return callback(); const id = getByPath(data, IM_ID); if (id === undefined) return callback(); request(foaf(id), (text) => { const time = parse(text); setByPath(data, IM_ONLINE, { ...IM_INFO, time }); callback(); }); }; const queries = new WeakSet(); const xhr = W.XMLHttpRequest && W.XMLHttpRequest.prototype; const onreadystatechange = (self) => { self.onreadystatechange = new Proxy(self.onreadystatechange, { apply(...args) { if (self.readyState === xhr.DONE) { const data = JSON.parse(self.responseText); im(data, () => { Object.defineProperty(self, 'responseText', { value: JSON.stringify(data), writable: true, }); return Reflect.apply(...args); }); } }, }); }; xhr.open = new Proxy(xhr.open, { apply(target, self, argumentsList) { const url = argumentsList[1]; if (match(url)) queries.add(self); return Reflect.apply(target, self, argumentsList); }, }); xhr.send = new Proxy(xhr.send, { apply(target, self, argumentsList) { if (queries.has(self)) onreadystatechange(self); return Reflect.apply(target, self, argumentsList); }, }); W.fetch = new Proxy(W.fetch, { apply(...args) { const promise = Reflect.apply(...args); const argumentsList = last(args); const { body } = last(argumentsList); if (!match(body)) return promise; return promise .then((res) => res.clone()) .then((res) => res.json()) .then(profile) .then((patched) => new Response(JSON.stringify(patched), promise)) .catch(() => promise); }, }); const patch = () => { W.extend = new Proxy(W.extend, { apply(target, self, argumentsList) { const object = argumentsList[0]; const { apiPrefetchCache } = last(argumentsList); if (!apiPrefetchCache) return Reflect.apply(target, self, argumentsList); const filtered = apiPrefetchCache.filter( ({ method }) => method !== 'users.get', ); return Reflect.apply(target, self, [ object, { apiPrefetchCache: filtered }, ]); }, }); }; new MutationObserver((_, observer) => { try { patch(); observer.disconnect(); } catch {} }).observe(document.documentElement, { childList: true, subtree: true, }); })(unsafeWindow || window);