Mudanças entre as edições de "Widget:Item"

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
Linha 1: Linha 1:
<includeonly>
<includeonly>
     <!--
     <!--
       Tooltip: o HTML das passivas/descrição vem do Módulo:Item (Scribunto), função renderOne.
       Tooltip lazy: passivas/descrição/valor NÃO vão no HTML da página.
       Este widget só aplica estilos (.item-tooltip-passive-*) e o script de posição no hover.
      No hover (mouse real), busca {{#invoke:Item|tooltip|key=...}} e monta na hora.
       Se as passivas não aparecem, confira Módulo:ItemDB (dados) e Módulo:Item.lua na wiki.
 
      Defesa (Widget:Item): rate limit, eventos não confiáveis (console/automação),
       tamper em fetch/XHR. Se disparar: apaga cache, remove data-item-key e tooltips
       do DOM — ícones ficam. Staff/editor (wgUserGroups) não é afetado.
      Tooltip normal continua para quem usa mouse de verdade.
     -->
     -->
     <style>
     <style>
Linha 265: Linha 269:
         .item-tooltip.tooltip-shift-left .item-tooltip-arrow {
         .item-tooltip.tooltip-shift-left .item-tooltip-arrow {
             margin-right: var(--arrow-offset, 12px);
             margin-right: var(--arrow-offset, 12px);
        }
        .item-wrapper.item-tooltip-loading {
            opacity: 0.85;
         }
         }
     </style>
     </style>
Linha 275: Linha 283:
             var activeTip = null;
             var activeTip = null;
             var activeWrapper = null;
             var activeWrapper = null;
            var tooltipGen = 0;
            var tooltipHtmlCache = Object.create(null);
            /* ===== Defesa anti-scrape (tooltip continua normal pro usuário) ===== */
            var defenseTripped = false;
            var untrustedStrikes = 0;
            var tooltipFetchLog = [];
            var RATE_WINDOW_MS = 120000;
            var RATE_MAX = 45;
            var BURST_WINDOW_MS = 15000;
            var BURST_MAX = 18;
            var UNTRUSTED_MAX = 2;
            var nativeFetch = window.fetch ? window.fetch.bind(window) : null;
            var nativeXHROpen = XMLHttpRequest.prototype.open;
            function isStaffUser() {
                try {
                    if (!window.mw || !mw.config) return false;
                    var groups = mw.config.get("wgUserGroups") || [];
                    for (var i = 0; i < groups.length; i++) {
                        var g = groups[i];
                        if (g === "sysop" || g === "bureaucrat" || g === "editor" || g === "staff") {
                            return true;
                        }
                    }
                } catch (e) { /* ignore */ }
                return false;
            }
            function clearTooltipCache() {
                Object.keys(tooltipHtmlCache).forEach(function (k) {
                    delete tooltipHtmlCache[k];
                });
            }


             function showTooltip(wrapper) {
             function purgeTooltipData() {
                 hideTooltip();
                 hideTooltip();
                clearTooltipCache();
                var tips = document.querySelectorAll(".item-tooltip");
                for (var i = 0; i < tips.length; i++) {
                    tips[i].remove();
                }
                var wrappers = document.querySelectorAll(".item-has-tooltip");
                for (var j = 0; j < wrappers.length; j++) {
                    wrappers[j].removeAttribute("data-item-key");
                    wrappers[j].removeAttribute("data-item-lang");
                    wrappers[j].classList.remove("item-has-tooltip", "item-tooltip-loading");
                }
            }


                 var tip = wrapper.querySelector('.item-tooltip');
            function triggerDefense() {
                 if (!tip) return;
                 if (defenseTripped || isStaffUser()) return;
                 defenseTripped = true;
                purgeTooltipData();
            }


                 activeWrapper = wrapper;
            function registerTooltipFetch() {
                 activeTip = tip;
                if (defenseTripped || isStaffUser()) return true;
                var now = Date.now();
                tooltipFetchLog.push(now);
                var i = 0;
                while (i < tooltipFetchLog.length) {
                    if (now - tooltipFetchLog[i] > RATE_WINDOW_MS) {
                        tooltipFetchLog.splice(i, 1);
                    } else {
                        i++;
                    }
                }
                if (tooltipFetchLog.length > RATE_MAX) {
                    triggerDefense();
                    return false;
                }
                var recent = 0;
                for (var j = tooltipFetchLog.length - 1; j >= 0; j--) {
                    if (now - tooltipFetchLog[j] <= BURST_WINDOW_MS) recent++;
                }
                if (recent > BURST_MAX) {
                    triggerDefense();
                    return false;
                }
                return true;
            }
 
            function noteUntrustedEvent() {
                if (defenseTripped || isStaffUser()) return;
                untrustedStrikes++;
                if (untrustedStrikes >= UNTRUSTED_MAX) {
                    triggerDefense();
                }
            }
 
            function checkIntegrity() {
                if (defenseTripped || isStaffUser()) return;
                if (nativeFetch && window.fetch !== nativeFetch) {
                    triggerDefense();
                    return;
                }
                if (XMLHttpRequest.prototype.open !== nativeXHROpen) {
                    triggerDefense();
                }
            }
 
            setInterval(checkIntegrity, 2500);
 
            /* Console aberto: não desliga tooltip sozinho — só se combinar com abuso */
            (function watchConsoleAccess() {
                if (isStaffUser()) return;
                var bait = {};
                var tripped = false;
                try {
                    Object.defineProperty(bait, "id", {
                        get: function () {
                            if (!tripped && !isStaffUser()) {
                                tripped = true;
                                if (tooltipFetchLog.length >= 8 || untrustedStrikes > 0) {
                                    triggerDefense();
                                }
                            }
                            return "";
                        }
                    });
                } catch (e) { return; }
                setInterval(function () {
                    if (defenseTripped || isStaffUser()) return;
                    try { console.debug(bait); } catch (e2) { /* ignore */ }
                }, 4000);
            })();
 
            function getApiUrl() {
                if (window.mw && mw.util && mw.util.wikiScript) {
                    return mw.util.wikiScript("api");
                }
                var path = window.location.pathname || "";
                if (path.indexOf("/index.php") !== -1) {
                    return path.split("/index.php")[0] + "/api.php";
                }
                return "/api.php";
            }
 
            function buildTooltipInvoke(key, lang) {
                var safeKey = String(key).replace(/\|/g, "").replace(/\}/g, "").replace(/\{/g, "");
                var safeLang = String(lang || "pt-br").replace(/\|/g, "");
                return "{{#invoke:Item|tooltip|key=" + safeKey + "|lang=" + safeLang + "}}";
            }
 
            function fetchParse(wikitext) {
                if (window.mw && mw.Api) {
                    return new mw.Api().post({
                        action: "parse",
                        text: wikitext,
                        contentmodel: "wikitext",
                        disablelimitreport: true
                    }).then(function (data) {
                        return data.parse.text["*"];
                    });
                }
                var body = new URLSearchParams();
                body.set("action", "parse");
                body.set("format", "json");
                body.set("text", wikitext);
                body.set("contentmodel", "wikitext");
                body.set("disablelimitreport", "1");
                return fetch(getApiUrl(), {
                    method: "POST",
                    credentials: "same-origin",
                    headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
                    body: body.toString()
                })
                    .then(function (res) { return res.json(); })
                    .then(function (data) {
                        if (data.error) throw new Error(data.error.info || "API parse failed");
                        return data.parse.text["*"];
                    });
            }
 
            function fetchTooltipHtml(key, lang) {
                if (defenseTripped) return Promise.reject(new Error("defense"));
                 var cacheKey = key + "\0" + (lang || "pt-br");
                if (tooltipHtmlCache[cacheKey]) {
                    return Promise.resolve(tooltipHtmlCache[cacheKey]);
                 }
                if (!registerTooltipFetch()) {
                    return Promise.reject(new Error("rate"));
                }
                return fetchParse(buildTooltipInvoke(key, lang)).then(function (html) {
                    if (defenseTripped) return "";
                    tooltipHtmlCache[cacheKey] = html || "";
                    return tooltipHtmlCache[cacheKey];
                });
            }


                document.body.appendChild(tip);
            function positionTooltip(tip, wrapper) {
                 tip.classList.add('tooltip-visible');
                 tip.classList.add("tooltip-visible");
                 tip.classList.remove('tooltip-below', 'tooltip-shift-right', 'tooltip-shift-left');
                 tip.classList.remove("tooltip-below", "tooltip-shift-right", "tooltip-shift-left");
                 tip.style.removeProperty('--arrow-offset');
                 tip.style.removeProperty("--arrow-offset");


                 var rect = wrapper.getBoundingClientRect();
                 var rect = wrapper.getBoundingClientRect();
Linha 303: Linha 492:
                 } else if (below + tipH <= window.innerHeight - MARGIN) {
                 } else if (below + tipH <= window.innerHeight - MARGIN) {
                     top = below;
                     top = below;
                     tip.classList.add('tooltip-below');
                     tip.classList.add("tooltip-below");
                 } else {
                 } else {
                     top = Math.max(MARGIN, Math.min(above, window.innerHeight - MARGIN - tipH));
                     top = Math.max(MARGIN, Math.min(above, window.innerHeight - MARGIN - tipH));
Linha 312: Linha 501:
                 if (left < MARGIN) {
                 if (left < MARGIN) {
                     left = Math.max(MARGIN, rect.left);
                     left = Math.max(MARGIN, rect.left);
                     tip.classList.add('tooltip-shift-right');
                     tip.classList.add("tooltip-shift-right");
                     tip.style.setProperty('--arrow-offset', Math.max(12, centerX - left) + 'px');
                     tip.style.setProperty("--arrow-offset", Math.max(12, centerX - left) + "px");
                 } else if (left + tipW > window.innerWidth - MARGIN) {
                 } else if (left + tipW > window.innerWidth - MARGIN) {
                     left = Math.min(window.innerWidth - MARGIN - tipW, rect.right - tipW);
                     left = Math.min(window.innerWidth - MARGIN - tipW, rect.right - tipW);
                     tip.classList.add('tooltip-shift-left');
                     tip.classList.add("tooltip-shift-left");
                     tip.style.setProperty('--arrow-offset', Math.max(12, (left + tipW) - centerX) + 'px');
                     tip.style.setProperty("--arrow-offset", Math.max(12, (left + tipW) - centerX) + "px");
                 }
                 }


                 tip.style.top = top + 'px';
                 tip.style.top = top + "px";
                 tip.style.left = left + 'px';
                 tip.style.left = left + "px";
             }
             }


             function hideTooltip() {
             function destroyTooltip() {
                 if (activeTip && activeWrapper) {
                 if (activeTip) {
                     activeTip.classList.remove('tooltip-visible', 'tooltip-below', 'tooltip-shift-right', 'tooltip-shift-left');
                     activeTip.remove();
                    activeTip.style.top = '';
                    activeTip.style.left = '';
                    activeTip.style.removeProperty('--arrow-offset');
                    activeWrapper.appendChild(activeTip);
                     activeTip = null;
                     activeTip = null;
                }
                if (activeWrapper) {
                    activeWrapper.classList.remove("item-tooltip-loading");
                     activeWrapper = null;
                     activeWrapper = null;
                 }
                 }
             }
             }


             document.addEventListener('mouseover', function (e) {
            function hideTooltip() {
                 var el = e.target;
                tooltipGen++;
                 var wrapper = el.closest ? el.closest('.item-wrapper') : null;
                destroyTooltip();
            }
 
            function showTooltip(wrapper) {
                if (defenseTripped) return;
                if (wrapper === activeWrapper && activeTip) return;
 
                hideTooltip();
 
                var key = wrapper.getAttribute("data-item-key");
                if (!key) return;
 
                var lang = wrapper.getAttribute("data-item-lang") || "pt-br";
                var gen = ++tooltipGen;
 
                activeWrapper = wrapper;
                wrapper.classList.add("item-tooltip-loading");
 
                fetchTooltipHtml(key, lang).then(function (html) {
                    if (defenseTripped || gen !== tooltipGen || activeWrapper !== wrapper) return;
 
                    wrapper.classList.remove("item-tooltip-loading");
                    if (!html || !html.trim()) return;
 
                    var tmp = document.createElement("div");
                    tmp.innerHTML = html.trim();
                    var tip = tmp.querySelector(".item-tooltip") || tmp.firstElementChild;
                    if (!tip) return;
 
                    activeTip = tip;
                    document.body.appendChild(tip);
                    positionTooltip(tip, wrapper);
                }).catch(function () {
                    if (gen === tooltipGen && activeWrapper === wrapper) {
                        wrapper.classList.remove("item-tooltip-loading");
                        activeWrapper = null;
                    }
                });
            }
 
             document.addEventListener("mouseover", function (e) {
                 if (e.isTrusted === false) {
                    noteUntrustedEvent();
                    return;
                }
                 var wrapper = e.target.closest ? e.target.closest(".item-wrapper.item-has-tooltip") : null;
                 if (wrapper && wrapper !== activeWrapper) {
                 if (wrapper && wrapper !== activeWrapper) {
                     showTooltip(wrapper);
                     showTooltip(wrapper);
Linha 344: Linha 577:
             });
             });


             document.addEventListener('mouseout', function (e) {
             document.addEventListener("mouseout", function (e) {
                if (e.isTrusted === false) return;
                 if (!activeWrapper) return;
                 if (!activeWrapper) return;
                 var wrapper = e.target.closest ? e.target.closest('.item-wrapper') : null;
                 var wrapper = e.target.closest ? e.target.closest(".item-wrapper") : null;
                 if (wrapper === activeWrapper && !activeWrapper.contains(e.relatedTarget)) {
                 if (wrapper === activeWrapper && !activeWrapper.contains(e.relatedTarget)) {
                     hideTooltip();
                     hideTooltip();
                 }
                 }
             });
             });
            document.addEventListener("scroll", hideTooltip, true);
         })();
         })();
     </script>
     </script>
</includeonly>
</includeonly>

Edição das 19h20min de 28 de maio de 2026