- // ==UserScript==
- // @name HWM_show_info
- // @author Мифист
- // @namespace Мифист
- // @version 1.0.0
- // @description Подробная инфа артов/существ/навыков
- // @match https://www.heroeswm.ru/*
- // @match https://*.lordswm.com/*
- // @exclude */war.php*
- // @exclude */roulette.php*
- // @run-at document-end
- // @grant none
- // @license MIT
- // @noframes
- // ==/UserScript==
-
- (function initModule(view) {
- 'use strict';
-
- if (document.visibilityState === 'hidden') {
- const handler = () => initModule(view);
- document.addEventListener('visibilitychange', handler, { once: true });
- return;
- }
-
- if (document.readyState === 'loading') {
- const handler = () => initModule(view);
- document.addEventListener('DOMContentLoaded', handler, { once: true });
- return;
- }
-
- // ==========================
-
- const MODULE_NAME = 'HWM_show_info';
- const MODULE_VERSION = '1.0.0';
-
- const modules = (function(symbol) {
- return view[symbol] || (view[symbol] = {
- stack: new Map,
- has(key) { return this.stack.has(key); },
- delete(key) { return this.stack.delete(key); },
- get(key) { return this.stack.get(key); },
- add(key, version, exports) {
- if (this.stack.has(key)) return;
- this.stack.set(key, { version, exports });
- }
- });
- })(Symbol.for('__5781303__'));
-
- if (modules.has('HWM_auction_upd')) return;
-
- // ==========================
-
- let hideFrame = Function.prototype;
- const outerStyleSheet = document.createElement('style');
-
- const $ = (selector, ctx = document) => ctx.querySelector(selector);
-
- function fetch(url) {
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest();
- xhr.open('GET', url);
- xhr.responseType = 'document';
-
- xhr.onload = () => {
- if (xhr.status === 200) return resolve(xhr.response);
- reject(new Error(`Error status: ${xhr.status}`));
- };
-
- xhr.onerror = () => reject(new Error('ERR_INTERNET_DISCONNECTED'));
-
- xhr.send(null);
- });
- }
-
- // ==========================
-
- async function handleTarget(e) {
- const trg = e.target;
- const anchor = getElemAnchor(trg);
-
- if (!anchor) return;
-
- e.preventDefault();
- hideFrame();
-
- const self = InfoFrame.instances[anchor.flag];
- let data = self.cache.get(anchor.id);
-
- if (!document.contains(self.frame)) {
- if (!navigator.onLine) return onInternetDisconnect();
- await self.onConnect(self.getURL(anchor.id));
- }
-
- if (data == null) {
- data = await pullRequest(self, anchor.id);
- if (data == null) return;
- }
-
- self.setHide(trg);
- self.render(data);
- self.shell.classList.add('__shown');
- centerFrame(self, getClosestBlockElem(trg));
-
- if (self === ArmyInfo) self.initHints();
- }
-
- function onInternetDisconnect(err) {
- const msg = err ? err.message : 'ERR_INTERNET_DISCONNECTED';
- alert(`${msg}\nНет подключения к Интернету`);
- }
-
- function pullRequest(self, id) {
- return self.request(id)
- .then(data => self.cache.set(id, data).get(id))
- .catch(onInternetDisconnect);
- }
-
- // ==========================
-
- class InfoFrame {
- static __init__() {
- if (this !== InfoFrame) return;
-
- const instances = this.instances = [PerkInfo, ArtInfo, ArmyInfo];
-
- instances.forEach((self, ind) => {
- self.flag = ind;
-
- if (!$(self.selector)) return;
-
- self.cache = new Map;
- self.shell = document.createElement('div');
- self.frame = document.createElement('iframe');
- self.shell.classList.add(`${MODULE_NAME}-shell`);
- self.frame.classList.add(`${MODULE_NAME}-frame`);
- });
-
- if (instances.every(self => !self.hasOwnProperty('cache'))) return;
-
- outerStyleSheet.append(this.outerCSS);
- document.addEventListener('contextmenu', handleTarget);
-
- const destroyType = MODULE_NAME + '__destroy';
- const destroyHandler = this.__destroy__.bind(this);
- document.addEventListener(destroyType, destroyHandler, { once: true });
-
- modules.add(MODULE_NAME, MODULE_VERSION, this);
- }
-
- static __destroy__() {
- hideFrame();
-
- this.instances.splice(0).forEach(self => {
- if (!self.hasOwnProperty('cache')) return;
- self.cache.clear();
- self.shell.remove();
- });
-
- outerStyleSheet.remove();
- document.removeEventListener('contextmenu', handleTarget);
- modules.delete(MODULE_NAME);
- }
-
- static get outerCSS() {
- return /*css*/`
- .${MODULE_NAME}-shell {
- --w: 0;
- --h: 0;
- --x: 0;
- --y: 0;
- width: var(--w);
- height: var(--h);
- display: none;
- position: absolute;
- left: var(--x);
- top: var(--y);
- z-index: 100;
- }
- .${MODULE_NAME}-shell.__shown {
- display: block;
- }
- .${MODULE_NAME}-shell::before,
- .${MODULE_NAME}-shell::after {
- content: "";
- height: 5px;
- position: absolute;
- left: 0;
- right: 0;
- top: -5px;
- }
- .${MODULE_NAME}-shell::after {
- top: auto;
- bottom: -5px;
- }
- .${MODULE_NAME}-frame {
- width: 100%;
- height: 100%;
- display: block;
- border: none;
- outline: 2px solid #72787c;
- resize: none;
- overflow: hidden;
- user-select: none;
- }
- .hwm_hint_css,
- div[style^="z-index:1;top:0;right:0;"] {
- pointer-events: none;
- }
- `.replace(/^ +/gm, '');
- }
-
- static get frameView() {
- return this.frame.contentWindow;
- }
-
- static get frameDoc() {
- return this.frame.contentDocument;
- }
-
- static onConnect(url) {
- const {frame, shell} = this;
-
- if (!document.contains(outerStyleSheet)) {
- document.head.append(outerStyleSheet);
- }
-
- frame.src = url;
- shell.append(frame);
- document.body.prepend(shell);
-
- return new Promise(resolve => {
- frame.onload = () => {
- this.onFrameLoad();
- resolve();
- };
- });
- }
-
- static onFrameLoad() {
- const {frame, frameView, frameDoc} = this;
- const target = this.target = frameDoc.createElement('div');
- target.id = 'cont';
-
- clearAsyncQueue(frameView);
- frameView.setTimeout(() => clearAsyncQueue(frameView), 200);
-
- frameView.addEventListener('error', (e) => {
- console.log(e);
- alert(`@${MODULE_NAME} => ${this.name}:\n${e.message}`);
- if (this === ArmyInfo) this.shell.remove();
- });
-
- frameDoc.head.innerHTML = /*html*/`
- <base target="_parent">
- <style>${this.innerCCS}</style>
- `;
- frameDoc.body.replaceChildren(target);
- }
-
- static onFrameHide() {}
-
- static setHide(trg) {
- const {frame, shell, frameDoc} = this;
-
- const onKeyUp = (e) => void (e.key === 'Escape' && hide());
-
- const hide = hideFrame = () => {
- toggleHandlers(false);
- shell.classList.remove('__shown');
- shell.removeAttribute('style');
- hideFrame = Function.prototype;
- this.onFrameHide();
- };
-
- toggleHandlers(true);
-
- function toggleHandlers(force) {
- const method = (force ? 'add' : 'remove') + 'EventListener';
- document[method]('click', hide);
- document[method]('keyup', onKeyUp);
- frameDoc[method]('keyup', onKeyUp);
- shell[method]('mouseleave', hide);
- trg[method]('mouseleave', leave);
- trg[method]('click', preventClick, true);
- }
-
- function leave({type, relatedTarget}) {
- this.removeEventListener(type, leave);
- if (relatedTarget && !relatedTarget.contains(frame)) hide();
- }
-
- function preventClick(e) {
- e.preventDefault();
- e.stopPropagation();
- hide();
- }
- }
-
- static render(html) {
- this.target.innerHTML = html;
- }
- }
-
- // ==========================
-
- class PerkInfo extends InfoFrame {
- static get innerCCS() {
- return /*css*/`
- * {
- font-family: inherit;
- font-size: inherit;
- box-sizing: border-box;
- }
- :root {
- font-size: 10px;
- }
- body {
- font-family: Verdana, Arial, sans-serif;
- font-size: 1.3rem;
- line-height: 1.3;
- margin: 0;
- background-image: linear-gradient(45deg, #cdc9c0, #fff);
- overflow: hidden;
- user-select: none;
- }
- #cont {
- width: 60rem;
- display: flex;
- flex-wrap: wrap;
- align-items: flex-start;
- justify-content: flex-start;
- padding: 1rem;
- }
- #cont > h1 {
- font-size: 1.1em;
- width: 100%;
- margin: 0 0 1rem;
- color: #435970;
- text-transform: uppercase;
- }
- #cont > div {
- flex: 1;
- padding-left: 1rem;
- }
- `.replace(/^ +/gm, '');
- }
-
- static get selector() {
- return 'a[href*="showperkinfo."]';
- }
-
- static getURL(id) {
- return `/showperkinfo.php?name=${id}`;
- }
-
- static getItemId(el) {
- return new URLSearchParams(el.search).get('name');
- }
-
- static async request(id) {
- const responseDoc = await fetch(this.getURL(id));
- const img = $('img[src*="/perks/"]', responseDoc);
- let elem = img && img.closest('td');
- elem = elem && elem.nextElementSibling;
-
- if (!elem) return '';
-
- return /*html*/`
- <h1>${img.alt.slice(7)}</h1>
- <img src="${img.src}">
- <div>${elem.innerHTML.slice(20)}</div>
- `;
- }
- }
-
- // ==========================
-
- class ArtInfo extends InfoFrame {
- static get innerCCS() {
- return /*css*/`
- * {
- font-family: inherit;
- font-size: inherit;
- box-sizing: border-box;
- }
- :root {
- font-size: 10px;
- }
- body {
- font-family: Verdana, Arial, sans-serif;
- font-size: 1.3rem;
- line-height: 1.3;
- margin: 0;
- background-image: linear-gradient(45deg, #cdc9c0, #fff);
- overflow: hidden;
- user-select: none;
- }
- #cont {
- width: 74rem;
- max-height: 42rem;
- display: flex;
- position: relative;
- overflow-y: auto;
- scrollbar-width: thin;
- }
- .global_container_block_header {
- font-size: 1.1em;
- position: absolute;
- right: 2rem;
- top: 1rem;
- }
- .global_container_block_header h1 {
- line-height: normal !important;
- text-transform: uppercase;
- margin: 0;
- }
- .global_container_block_header b {
- color: #435970;
- }
- .art_info_left_block {
- padding: 2rem 1rem;
- }
- .s_art_prop_amount_icon {
- min-height: 2.8rem;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #0a2b4b;
- background-image: linear-gradient(#eee, #bbc4b1);
- border: 1px solid #78878d;
- }
- .s_art_prop_amount_icon:hover {
- filter: saturate(1.5);
- }
- .s_art_prop_amount_icon img {
- width: 2rem;
- margin-right: .5rem;
- }
- .cre_mon_image1 {
- display: none;
- }
- .art_info_desc {
- padding: 3rem 1rem 1rem;
- background: transparent !important;
- }
- .rs {
- margin: 0 2px;
- }
- b {
- color: #332f2f;
- }
- i {
- color: #315473;
- }
- a[href*="=40#"] {
- font-weight: bold;
- font-style: normal;
- text-decoration: none;
- }
- `.replace(/^ +/gm, '');
- }
-
- static get selector() {
- return location.pathname === '/inventory.php'
- ? '.inv_art_outside'
- : 'a[href*="art_info."]';
- }
-
- static getURL(id) {
- return `/art_info.php?id=${id}`;
- }
-
- static getItemId(el) {
- if (el.tagName === 'A') return new URLSearchParams(el.search).get('id');
-
- const imgPathReg = /\/artifacts\/([^.]+)/;
- const getArtName = (html) => html.match(imgPathReg)[1];
- const artName = getArtName(el.outerHTML);
-
- const {arts = []} = view;
- const art = arts.find(art => getArtName(art.html) === artName) || {};
- return art.art_id;
- }
-
- static async request(id) {
- const responseDoc = await fetch(this.getURL(id));
- const elem = $('#set_mobile_max_width', responseDoc);
- return elem ? elem.innerHTML : '';
- }
- }
-
- // ==========================
-
- class ArmyInfo extends InfoFrame {
- static get innerCCS() {
- return /*css*/`
- * {
- box-sizing: border-box;
- }
- :root {
- font-size: 10px;
- }
- body {
- font-family: Verdana, Arial, sans-serif;
- font-size: 1.2rem;
- margin: 0;
- background-image: linear-gradient(45deg, #dad1be, #fff);
- overflow: hidden;
- user-select: none;
- }
- .hwm_hint_css {
- font-size: inherit !important;
- max-width: 34rem !important;
- position: fixed;
- display: none;
- padding: .4em .7em;
- color: #ddd;
- background-color: #3a3a3a;
- border: 2px solid #888;
- z-index: 2;
- }
- #cont {
- width: 70rem;
- display: flex;
- flex-wrap: wrap;
- }
- .info_header_content {
- width: 100%;
- height: 3.5em;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #afc2d747;
- border-bottom: 1px solid #757575;
- }
- a:first-child {
- font-size: 1.6em;
- color: #506263;
- text-decoration: none;
- }
- .info_text_content {
- width: calc(100% - 20rem);
- display: flex;
- flex-wrap: wrap;
- border-right: 1px solid #757575;
- }
- .info_text_content > div {
- font-size: 1.1em;
- width: 50%;
- display: flex;
- align-items: center;
- column-gap: .5rem;
- padding: 0 1.4rem;
- }
- .info_text_content img {
- width: 2.4rem;
- height: auto;
- }
- .info_text_content div:last-child {
- margin-left: auto;
- }
- canvas,
- #show_army,
- .konvajs-content {
- width: 20rem !important;
- height: 20rem !important;
- }
- .army_info_skills {
- font-size: 1.1em;
- width: 100%;
- display: flex;
- flex-wrap: wrap;
- column-gap: 0.4em;
- padding: 1rem 1.4rem;
- border-top: 1px solid #757575;
- }
- .army_info_skills > div {
- font-weight: bold;
- margin-right: -0.5em;
- }
- .army_info_skills > span:hover {
- color: brown;
- cursor: help;
- }
- `.replace(/^ +/gm, '');
- }
-
- static get selector() {
- return 'a[href*="army_info."]';
- }
-
- static getURL(id) {
- return `/army_info.php?name=${id}`;
- }
-
- static getItemId(el) {
- return new URLSearchParams(el.search).get('name');
- }
-
- static async request(id) {
- const responseDoc = await fetch(this.getURL(id));
- const linkHTML = `<a href="${this.getURL(id)}">$1</a>`;
- const html = $('.army_info', responseDoc).innerHTML.trim()
- .replaceAll('\n', '')
- .replace(/\s{2,}/g, ' ')
- .replace(' style="display: show;"', '')
- .replaceAll('> ', '>')
- .replaceAll(' width="48" height="48" alt="" title=""', '')
- .replace(/<div><h1 [^>]+>(.+?)<\/h1><\/div>/, linkHTML)
- .replace(/<div(?:><img| class="corner).+?div>/g, '')
- .replaceAll(' class="scroll_content_half"', '');
-
- const reg = /info\((.+?)\);/;
- const script = [...responseDoc.scripts].pop();
- const paramsStr = (script.text.match(reg) || ['', ''])[1];
-
- return [html, paramsStr];
- }
-
- static render([html, paramsStr]) {
- super.render(html);
-
- const {frameView} = this;
- const params = new Function(`return [${paramsStr}]`)();
- frameView.setTimeout(() => frameView.init_army_info(...params));
- }
-
- static onFrameLoad() {
- super.onFrameLoad();
-
- const {frameView, target} = this;
- const hwmHint = frameView.hwm_hint;
-
- if (!(hwmHint instanceof frameView.HTMLElement)) return;
-
- target.after(hwmHint);
- }
-
- static onFrameHide() {
- const {frameView: ctx} = this;
- const stages = ctx.Konva && ctx.Konva.stages || [];
- stages.splice(0).forEach(stage => ctx.clearInterval(stage.interval));
- }
-
- static initHints() {
- const initHwmHints = this.frameView.hwm_hints_init;
- if (typeof initHwmHints === 'function') initHwmHints();
- }
- }
-
- // ==========================
-
- InfoFrame.__init__();
-
- // ==========================
-
- function centerFrame({target, shell}, elem) {
- const {offsetHeight, offsetWidth} = target;
- const {left, right, top, bottom} = elem.getBoundingClientRect();
- const offset = 5;
- const halfw = offsetWidth / 2;
- const height = offsetHeight + offset;
- const maxX = document.documentElement.clientWidth - offset;
- const centerX = left + (right - left) / 2;
-
- const x = Math.max(offset, Math.min(centerX - halfw, maxX - offsetWidth));
- const y = (bottom + height < view.innerHeight || top - height <= 0)
- ? bottom + offset
- : top - height;
-
- const setCSS = shell.style.setProperty.bind(shell.style);
- setCSS('--w', `${offsetWidth >> 0}px`);
- setCSS('--h', `${offsetHeight >> 0}px`);
- setCSS('--x', `${x + view.scrollX >> 0}px`);
- setCSS('--y', `${y + view.scrollY >> 0}px`);
- }
-
- function getElemAnchor(el) {
- const propName = `__cachedInfoFrameAnchor`;
- if (el.hasOwnProperty(propName)) return el[propName];
-
- const set = (val = null) => (el[propName] = val);
- const artElem = el.closest(ArtInfo.selector);
- const perkElem = artElem ? null : el.closest(PerkInfo.selector);
- const elem = artElem || perkElem || el.closest(ArmyInfo.selector);
-
- if (!elem) return set();
-
- const self = perkElem ? PerkInfo : artElem ? ArtInfo : ArmyInfo;
- const id = self.getItemId(elem);
-
- return !id ? set() : set({ flag: self.flag, id });
- }
-
- function getClosestBlockElem(elem) {
- if (elem.offsetWidth && elem.offsetHeight) return elem;
- return getClosestBlockElem(elem.parentNode);
- }
-
- function clearAsyncQueue(ctx) {
- let i = ctx.setTimeout(0);
- while (i) ctx.clearTimeout(i--);
- }
- })(document.defaultView);