VK: Check Online

Checks the last online on page user and in dialog

  1. // ==UserScript==
  2. // @name VK: Check Online
  3. // @name:ru ВК: Проверка онлайна
  4. // @description Checks the last online on page user and in dialog
  5. // @description:ru Проверяет последний онлайн пользователя на странице и в диалогe
  6. // @namespace vk-check-online.user.js
  7. // @license MIT
  8. // @author askornot
  9. // @version 1.5.2
  10. // @match https://vk.com/*
  11. // @connect vk.com
  12. // @compatible chrome Violentmonkey 2.18.0
  13. // @compatible firefox Violentmonkey 2.13.3
  14. // @compatible firefox Tampermonkey 4.18.1
  15. // @homepageURL https://greasyfork.org/en/scripts/403717-vk-check-online
  16. // @supportURL https://greasyfork.org/en/scripts/403717-vk-check-online/feedback
  17. // @run-at document-start
  18. // @noframes
  19. // ==/UserScript==
  20.  
  21. (function (W) {
  22. 'use strict';
  23.  
  24. const IM_ID = 'payload,1,1,0,id';
  25. const IM_ONLINE = 'payload,1,0,last_seen';
  26. const PROFILE_ID = 'response,users.get,0,id';
  27. const PROFILE_ONLINE = 'response,users.get,0,online_info';
  28.  
  29. const IM_INFO = {
  30. can_see: 1,
  31. platform: 0,
  32. time: 0,
  33. mobile: 0,
  34. };
  35.  
  36. const PROFILE_INFO = {
  37. visible: true,
  38. last_seen: 0,
  39. app_id: 0,
  40. is_online: false,
  41. is_mobile: false,
  42. };
  43.  
  44. const last = (arr) => arr[arr.length - 1];
  45.  
  46. const match = (s) => /online_info|a_start/.test(s);
  47.  
  48. const getByPath = (data, dataPath) => {
  49. const path = dataPath.split(',');
  50. let object = data;
  51. for (const name of path) {
  52. const next = object[name];
  53. if (next === null || next === undefined) return next;
  54. object = next;
  55. }
  56. return object;
  57. };
  58.  
  59. const setByPath = (data, dataPath, value) => {
  60. const path = dataPath.split(',');
  61. const len = path.length;
  62. let obj = data;
  63. let i = 0;
  64. let next, prop;
  65. for (;;) {
  66. if (typeof obj !== 'object') return false;
  67. prop = path[i];
  68. if (i === len - 1) {
  69. obj[prop] = value;
  70. return true;
  71. }
  72. next = obj[prop];
  73. if (next === undefined || next === null) {
  74. next = {};
  75. obj[prop] = next;
  76. }
  77. obj = next;
  78. i++;
  79. }
  80. };
  81.  
  82. const parse = (text) => {
  83. const parser = new DOMParser();
  84. const body = parser.parseFromString(text, 'text/html');
  85. const [element] = body.getElementsByTagName('ya:lastloggedin');
  86. if (!element) return 0;
  87. const date = element.getAttribute('dc:date');
  88. return Date.parse(date) / 1000;
  89. };
  90.  
  91. const request = (url, callback) => {
  92. const xhr = new XMLHttpRequest();
  93. xhr.onreadystatechange = () => {
  94. if (xhr.readyState === xhr.DONE) {
  95. if (xhr.status === 0 || xhr.status === 200) {
  96. callback(xhr.responseText);
  97. }
  98. }
  99. };
  100. xhr.open('GET', url, true);
  101. xhr.send();
  102. };
  103.  
  104. const foaf = (id) => `https://vk.com/foaf.php?id=${id}`;
  105.  
  106. const profile = (data) => {
  107. const info = getByPath(data, PROFILE_ONLINE);
  108. if (info && info.visible === true) return Promise.resolve(data);
  109. const id = getByPath(data, PROFILE_ID);
  110. if (id === undefined) return Promise.resolve(data);
  111. return fetch(foaf(id))
  112. .then((res) => res.text())
  113. .then(parse)
  114. .then((time) => {
  115. setByPath(data, PROFILE_ONLINE, { ...PROFILE_INFO, last_seen: time });
  116. return data;
  117. });
  118. };
  119.  
  120. const im = (data, callback) => {
  121. const info = getByPath(data, IM_ONLINE);
  122. if (info && info.can_see === 1) return callback();
  123. const id = getByPath(data, IM_ID);
  124. if (id === undefined) return callback();
  125. request(foaf(id), (text) => {
  126. const time = parse(text);
  127. setByPath(data, IM_ONLINE, { ...IM_INFO, time });
  128. callback();
  129. });
  130. };
  131.  
  132. const queries = new WeakSet();
  133.  
  134. const xhr = W.XMLHttpRequest && W.XMLHttpRequest.prototype;
  135.  
  136. const onreadystatechange = (self) => {
  137. self.onreadystatechange = new Proxy(self.onreadystatechange, {
  138. apply(...args) {
  139. if (self.readyState === xhr.DONE) {
  140. const data = JSON.parse(self.responseText);
  141. im(data, () => {
  142. Object.defineProperty(self, 'responseText', {
  143. value: JSON.stringify(data),
  144. writable: true,
  145. });
  146. return Reflect.apply(...args);
  147. });
  148. }
  149. },
  150. });
  151. };
  152.  
  153. xhr.open = new Proxy(xhr.open, {
  154. apply(target, self, argumentsList) {
  155. const url = argumentsList[1];
  156. if (match(url)) queries.add(self);
  157. return Reflect.apply(target, self, argumentsList);
  158. },
  159. });
  160.  
  161. xhr.send = new Proxy(xhr.send, {
  162. apply(target, self, argumentsList) {
  163. if (queries.has(self)) onreadystatechange(self);
  164. return Reflect.apply(target, self, argumentsList);
  165. },
  166. });
  167.  
  168. W.fetch = new Proxy(W.fetch, {
  169. apply(...args) {
  170. const promise = Reflect.apply(...args);
  171. const argumentsList = last(args);
  172. const { body } = last(argumentsList);
  173. if (!match(body)) return promise;
  174. return promise
  175. .then((res) => res.clone())
  176. .then((res) => res.json())
  177. .then(profile)
  178. .then((patched) => new Response(JSON.stringify(patched), promise))
  179. .catch(() => promise);
  180. },
  181. });
  182.  
  183. const patch = () => {
  184. W.extend = new Proxy(W.extend, {
  185. apply(target, self, argumentsList) {
  186. const object = argumentsList[0];
  187. const { apiPrefetchCache } = last(argumentsList);
  188. if (!apiPrefetchCache) return Reflect.apply(target, self, argumentsList);
  189. const filtered = apiPrefetchCache.filter(
  190. ({ method }) => method !== 'users.get',
  191. );
  192. return Reflect.apply(target, self, [
  193. object,
  194. { apiPrefetchCache: filtered },
  195. ]);
  196. },
  197. });
  198. };
  199.  
  200. new MutationObserver((_, observer) => {
  201. try {
  202. patch();
  203. observer.disconnect();
  204. } catch {}
  205. }).observe(document.documentElement, {
  206. childList: true,
  207. subtree: true,
  208. });
  209. })(unsafeWindow || window);