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