Raw Source
SuhEugene / DevChecker

// ==UserScript==
// @name         DevChecker
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  Check Developers and testers in vk.com!
// @copyright 2019, King (https://openuserjs.org/users/King)
// @updateURL https://openuserjs.org/meta/King/DevChecker.meta.js
// @license MIT
// @author       King (not SuhEugene)
// @match        https://*.vk.com/*
// @resource     apiLib https://ban.su/~ex/api.js
// @grant        GM_getResourceText
// ==/UserScript==

// delete localStorage.devUsersCache2

function DevUsers() {

  var cache = {}; // Кэш
  var groups = { // Настройки
    "9713780": { // id групп
      title: "Разработчик", // Подсказки
      href: "https://vk.com/devclub", // Ссылки * - id юзера
      background: "url(https://pp.userapi.com/c619526/v619526550/1ab9e/21AneyV7dPc.jpg) center/cover", // иконка
    },
    "150825328": {
      title: "Special Forces",
      href: "https://vk.com/specialtesters",
      background: "url(https://pp.userapi.com/c637621/v637621394/59591/XWk69t0P1Iw.jpg) center/cover"
    },
    "134304772": {
      title: "Тестер | *",
      href: "https://vk.com/bugtracker?act=reporter&id=*",
      background: "url(https://pp.userapi.com/c639625/v639625391/42408/zj0kpTaIKiI.jpg) center/cover"
    }
  };

  function insertStyles() { // Фукция иниацилизации стилей
    var style = document.createElement("style"); // Создаем элемент стилей
    style.innerHTML = // css стили// css стили
      '.user_checker_icon {' +
      '   width: 12px; height: 12px; border-radius: 12px;' +
      '   display: inline-block;  margin: 0px 0px -1px 2px;' +
      '   transition: transform .2s; position: relative;' +
      '}' +
      '.user_checker_icon:hover {' +
      '    transform: scale(1.2);' +
      '}';
    document.head.appendChild(style); // Добавляем в залоговок
  }

  function checkLinks(el) { // Функция поиска в элементе ссылок
    var links = el.querySelectorAll('.im-mess-stack--lnk, .author, .friends_field a, .im-member-item--name a,.ListItem.ListItem--can-be-hovered a');
    if (!links) return; // Если в элементе нет ссылок, то пропускаем
    Array.from(links).map(function (link) { // Если есть, то перебираем
      if (link.checked) return; // Если ссылка проверена, то пропускаем
      checkUser(link); // Если есть, то отдаем на проверку
      link.checked = 1; // Отмечаем прочитанной
    });
  }

  function drawIcons(link, info) { // Функция отрисовки иконок
    if (!info.types.length || !info.user_id) return; // Если у юзера его нет или если это не юзер, то выходим
    info.types.map(function (type) { // Перебираем группы
      var icon = document.createElement("a"); // Создаем ссылку
      icon.className = "user_checker_icon"; // назначаем ей класс
      icon.target = "_blank"; // Открывать в новой вкладке
      icon.href = groups[type].href.replace("*", info.user_id); // Ссылка на карточку тестировщика
      icon.title = groups[type].title; // Подсказка
      icon.style.background = groups[type].background; // Иконка
      icon.onmouseover = function () {
        if (!showTooltip) return;
        showTooltip(icon, {
          force: 1,
          black: 1,
          content: '<div class="tt_text wrapped">' + icon.title + '</div>'
        });
      };
      link.appendChild(icon); // Добавляем ссылку в ссылку
    });
    return info; // Отдаем результат для ссылок ждущих кеша
  }

  var executeCode = function () { // Функция передаваемая в execute для получение исформации о пользователе
    var types = []; // Типы
    var groups = Args.groups.split(","); // id групп
    var ui = API.utils.resolveScreenName(Args); // Получаем id пользователя
    if (ui.type != "user") return {
      types: [],
      user_id: 0
    }; // Если не юзер, то выходим
    // Далее проверяем на наличие юзера в группах, если есть, то складываем в типы
    var group = 0; // Доя записи текущей группы;
    var isMember = 0; // Переменная для проверки подписки
    while (groups.length) { // Перебираем группы
      group = groups.shift(); // Первую в списке
      isMember = API.groups.isMember({ // Проверяем подписку
        group_id: group,
        user_id: ui.object_id
      });
      if (isMember) types.push(group); // Если подписан, то записываем это
    }
    // Выводим user_id и подписки
    return {
      types: types,
      user_id: ui.object_id
    };
  };

  // Преобразуем функцию в строку, для дальнейшего считывания execute
  executeCode = executeCode.toString().replace(/.+?\{([^]+)\}$/, "$1");

  function checkUser(link) { // Проверка пользователя на группы
    var screen_name = link.href.replace(/.+\//, ""); // Убираем из ссылки vk.com и прочее
    if (cache[screen_name] && cache[screen_name].then) // Если в кэше Promise
      return cache[screen_name].then(drawIcons.bind(this, link)); // то ждем ее результат и выводим иконки
    // Если в кэше результат и он не старее суток, то выводим иконки
    if (cache[screen_name] && cache[screen_name].updated > Date.now()) return drawIcons(link, cache[screen_name]);
    cache[screen_name] = API("execute", { // Если нет в кэше, то проверяем ее
      screen_name: screen_name, // Передаем ссылку в execute
      groups: groups.ids, // id групп
      code: executeCode // и код из функции выше
    }).then(function (r) { // Ждем результат
      cache[screen_name] = r.response; // Записываем результат в кэш
      cache[screen_name].updated = Date.now() + 864e5; // Записываем время через которое нужно повторить запрос
      if (r.response.types.length || !r.response.user_id) // Если юзер есть в группах или это не юзер,
        localStorage.devUsersCache2 = JSON.stringify(cache); // то записываем кэш в localStorage
      drawIcons(link, r.response); // Рисуем иконки
      return r.response; // Отдаем остальным
    }).catch(function (e) { // При ошибках
      console.error(e); // Выводим в консоль
    });
  }

  // Создаем обработчик мутаций элемента
  var observer = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) { // Перебираем обновленя в элементах
      if (mutation.target.nodeType !== 1) return; // Если элемент не блок, то выходим
      checkLinks(mutation.target); // Отдаем элемент на проверку ссылок
    });
  });

  window.addEventListener("load", function () { // Вешаем обработчик на загрузку страницы
    insertStyles(); // Вставляем стили

    if (localStorage.devUserGroups) // Есть ли сохраненный кэш
      groups = JSON.parse(localStorage.devUserGroups); // Загружаем и парсим
    if (localStorage.devUsersCache2) // Есть ли сохраненный кэш
      cache = JSON.parse(localStorage.devUsersCache2); // Загружаем и парсим

    groups.ids = Object.keys(groups).join(","); // id групп для передачи в execute

    checkLinks(document.body); // Отправляем body на проверку ссылок

    observer.observe(document.body, { // Запускаем обработчик мутаций
      childList: true, // Проведять детей элемента
      subtree: true // по всему дереву
    });
  });

}

(function injectScript() {
  var script = document.createElement('script');
  var code = '(' + DevUsers + ')();';
  code += '(function(){' + (GM_getResourceText('apiLib')) + '})();';
  script.appendChild(document.createTextNode(code));
  (document.body || document.head || document.documentElement).appendChild(script);
})();