VK: Check Online

Checks the last online on page user and in dialog

目前为 2020-05-19 提交的版本。查看 最新版本

/* globals vk, cur, getDateText, GM, GM_xmlhttpRequest */
/* eslint padded-blocks: ["error", { "blocks": "always" }]
object-curly-spacing: ["error", "always", { "objectsInObjects": false }]
curly: ["error", "multi"] */

// ==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.0.5
// @match           https://vk.com/*
// @connect         vk.com
// @compatible      chrome     Violentmonkey 2.12.7
// @compatible      firefox    Violentmonkey 2.12.7
// @homepageURL     https://greasyfork.org/en/scripts/403717-vk-check-online
// @supportURL      https://greasyfork.org/en/scripts/403717-vk-check-online/feedback
// @grant           GM_xmlhttpRequest
// @grant           GM.xmlHttpRequest
// @run-at          document-end
// @noframes
// ==/UserScript==

'use strict';

const USERS = new Map();

const request = (id, data = {}) => new Promise((resolve, reject) => {

	data.method = 'GET';
	data.url = `/foaf.php?id=${id}`;
	data.responseType = 'document';
	/* eslint-disable unicorn/prefer-add-event-listener, no-unused-expressions, no-constant-condition, operator-linebreak, new-cap */
	data.onload = resolve;
	data.onerror = reject;

	typeof !GM && typeof !GM.xmlHttpRequest
		? GM.xmlHttpRequest(data)
		: GM_xmlhttpRequest(data);
	/* eslint-enable */

});

const floor = time => Math.floor(time);

const now = () => (floor(Date.now()));

const render = uts => {

	const online = document.querySelector('.mail_box_label_info') ||
		document.querySelector('.profile_online_lv') ||
		document.querySelector('._im_page_peer_online');

	const { lang } = vk;

	const text = lang === 3 ? 'last seen' : 'заходил(а)';

	online.textContent = `${text} ${getDateText(uts, null)}`;

};

const getTs = rdf => {

	// eslint-disable-next-line unicorn/prefer-query-selector
	const [element] = rdf.getElementsByTagName('ya:lastloggedin');

	if (!element) return 0;

	const date = element.getAttribute('dc:date');

	const uts = floor(Date.parse(date) / 1000); // A getDateText requires unixtime

	return uts;

};

const getUser = user => USERS.has(user) ? USERS.get(user) : {};

const start = async () => {

	const { options = {}, peer } = cur; // VK object

	// eslint-disable-next-line no-prototype-builtins
	const userId = (peer ? peer : (options.hasOwnProperty('user_id') ? options.user_id : 0));

	if (userId === 0 || Math.sign(userId) === -1) return;

	let { expires = 0, uts = 0 } = getUser(userId);

	if (expires < now()) { // eslint-disable-line curly

		try {

			const { response } = await request(userId);
			uts = getTs(response);

			USERS.set(userId, {
				uts,
				expires: now() + 60000 // Every one minute
			});

		} catch (error) {

			console.error(error.stack);

		}

	}

	if (uts === 0) return;

	render(uts);

};

start().catch(console.error);

const filterMutsByClassName = muts => {

	return muts.filter((array, i, self) => self.findIndex(_array => (array.target.className === _array.target.className)) === i);

};

new MutationObserver(muts => {

	muts = filterMutsByClassName(muts);

	for (const { target, addedNodes } of muts) {

		const { tagName } = target;

		if (tagName === 'DIV' || tagName === 'SPAN') {

			const { classList } = target;

			if ( // eslint-disable-line curly
				(classList.contains('wide_column') && addedNodes.length >= 2) ||
				classList.contains('box_body') ||
				classList.contains('_im_page_peer_online') ||
				classList.contains('im-page-chat-contain')
			) {

				setTimeout(start, 1000);

			}

		}

	}

}).observe(document.body || document, {
	childList: true,
	subtree: true
});