GA Cheatnow (ManagerZone)

Analizador de Transferencias en curso (analizarTransferencias) para sub=cheatnow: motor de scoring ponderado (PESOS interno), coloreado por severidad, panel resumen (hubs, recíprocos, top de riesgo), filtro y botones [+]. Inyecta a Cruzar Usuarios vía GA.cargarEnCruce. Requiere ga-core.

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greasyfork.org/scripts/588167/1882654/GA%20Cheatnow%20%28ManagerZone%29.js

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         GA Cheatnow (ManagerZone)
// @namespace    https://control.managerzone.com/ga
// @version      1.0.0
// @description  Analizador de Transferencias en curso (analizarTransferencias) para sub=cheatnow: motor de scoring ponderado (PESOS interno), coloreado por severidad, panel resumen (hubs, recíprocos, top de riesgo), filtro y botones [+]. Inyecta a Cruzar Usuarios vía GA.cargarEnCruce. Requiere ga-core.
// ==/UserScript==

(function () {
    'use strict';
    window.GA = window.GA || {};
    const GA = window.GA;

    // ============================================================
    //  Analizador de Transferencias en curso  (sub=cheatnow)
    //
    //  Puntúa cada transferencia combinando señales. NINGUNA señal
    //  suelta alcanza: en una muestra real, UB=1 aparece en el 52% de
    //  las filas y X=0 en el 42%. Lo que discrimina es la CONJUNCIÓN.
    //
    //  Señales (pesos en PESOS, editables):
    //   · Reciprocidad  : PS>0 Y PP>0 (se venden en ambas direcciones)
    //   · Historial     : PS>0 o PP>0 (ya comerciaron)
    //   · Regalo        : precio irrisorio frente al valor del jugador
    //   · Sobrepago     : se pagó muy por encima del valor
    //   · Sin competencia: UB=1 (un único postor)
    //   · Hub comprador : el comprador recibe de muchos vendedores
    //   · Hub vendedor  : el vendedor reparte a muchos compradores
    //   · Par repetido  : el mismo par vendedor→comprador varias veces
    //
    //  Agrega: coloreado por severidad, panel resumen (hubs, recíprocos,
    //  top de riesgo), filtro "solo riesgo" y botones [+] con ID real.
    // ============================================================
    function analizarTransferencias() {

        // ---- Pesos (ajustá a tu criterio) ----
        const PESOS = {
            reciproco:      40,   // PS>0 y PP>0
            historial:      25,   // PS>0 o PP>0
            regalo:         25,   // bid/value < RATIO_REGALO
            precioBajo:     12,   // bid/value < RATIO_BAJO
            sobrepago:      20,   // bid/value > RATIO_ALTO
            sinCompetencia: 10,   // UB == 1
            hubComprador:   15,
            hubVendedor:    10,
            parRepetido:    20
        };
        const RATIO_REGALO = 0.01;   // < 1% del valor
        const RATIO_BAJO   = 0.10;   // < 10% del valor
        const RATIO_ALTO   = 3.0;    // paga 3x el valor
        const HUB_COMPRADOR = 3;     // compra a >= N vendedores distintos
        const HUB_VENDEDOR  = 4;     // vende a >= N compradores distintos
        // Calibrados sobre una muestra real de 558 transferencias:
        //   >=60 -> 36 filas (6.5%)   >=50 -> 88 filas (15.8%)
        // Por debajo de UMBRAL_MEDIO la fila NO se pinta (solo lleva el
        // badge con el score), para que el color siga significando algo.
        const UMBRAL_ALTO  = 60;
        const UMBRAL_MEDIO = 50;

        const COL_ALTO  = '#f8caca';
        const COL_MEDIO = '#ffe0b3';
        const COL_BAJO  = '#fff3b0';

        // ---- Ubicar la tabla (por sus cabeceras características) ----
        const tabla = [...document.querySelectorAll('table')].find(t => {
            const h = [...t.querySelectorAll('th')].map(x => x.textContent.trim());
            return h.includes('Seller') && h.includes('Buyer') && h.includes('PS') && h.includes('PP');
        });
        if (!tabla) return;

        const ths = [...tabla.querySelectorAll('thead th')];
        const idx = n => ths.map(t => t.textContent.trim()).indexOf(n);
        const C = {
            sc: idx('SC'), bc: idx('BC'), seller: idx('Seller'), buyer: idx('Buyer'),
            bid: idx('Current bid'), value: idx('Value'), x: idx('X'),
            tb: idx('TB'), ub: idx('UB'), ps: idx('PS'), pp: idx('PP'), end: idx('End date')
        };
        if (C.seller < 0 || C.buyer < 0) return;

        const filas = [...tabla.querySelectorAll('tr')].filter(tr => !tr.querySelector('th'));
        if (!filas.length) return;

        const txt = c => (c?.textContent || '').replace(/\s+/g, ' ').trim();
        const num = c => { const n = parseFloat(txt(c).replace(/\s/g, '').replace(',', '.')); return isNaN(n) ? 0 : n; };
        // El username viene en un <a>, y el user id en el href (searchstr=NNN)
        const userDe = c => {
            const a = c?.querySelector('a');
            if (!a) return { name: txt(c).split(' ')[0] || '', id: '' };
            const m = (a.getAttribute('href') || '').match(/searchstr=(\d+)/);
            return { name: a.textContent.trim(), id: m ? m[1] : '' };
        };

        // ---- 1er pase: leer filas ----
        const datos = filas.map(tr => {
            const c = tr.children;
            const s = userDe(c[C.seller]), b = userDe(c[C.buyer]);
            const bid = num(c[C.bid]), value = num(c[C.value]);
            return {
                tr, celdas: c,
                seller: s.name, sellerId: s.id, buyer: b.name, buyerId: b.id,
                bid, value, ratio: value > 0 ? bid / value : 0,
                ub: C.ub >= 0 ? num(c[C.ub]) : 0,
                ps: C.ps >= 0 ? num(c[C.ps]) : 0,
                pp: C.pp >= 0 ? num(c[C.pp]) : 0
            };
        }).filter(d => d.seller && d.buyer);

        // ---- Redes: hubs y pares repetidos ----
        const compraDe = {}, vendeA = {}, pares = {};
        datos.forEach(d => {
            (compraDe[d.buyer] = compraDe[d.buyer] || new Set()).add(d.seller);
            (vendeA[d.seller] = vendeA[d.seller] || new Set()).add(d.buyer);
            const k = d.seller + '>' + d.buyer;
            pares[k] = (pares[k] || 0) + 1;
        });

        // ---- 2do pase: puntuar ----
        datos.forEach(d => {
            let score = 0;
            const motivos = [];

            if (d.ps > 0 && d.pp > 0) {
                score += PESOS.reciproco;
                motivos.push(`recíproco (PS:${d.ps} PP:${d.pp})`);
            } else if (d.ps > 0 || d.pp > 0) {
                score += PESOS.historial;
                motivos.push(d.ps > 0 ? `${d.ps} venta(s) previa(s)` : `${d.pp} compra(s) previa(s)`);
            }

            if (d.value > 0) {
                if (d.ratio < RATIO_REGALO) {
                    score += PESOS.regalo;
                    motivos.push(`regalo (${d.bid.toLocaleString()} por un jugador de ${d.value.toLocaleString()})`);
                } else if (d.ratio < RATIO_BAJO) {
                    score += PESOS.precioBajo;
                    motivos.push(`precio bajo (${Math.round(d.ratio * 100)}% del valor)`);
                } else if (d.ratio > RATIO_ALTO) {
                    score += PESOS.sobrepago;
                    motivos.push(`sobrepago (${d.ratio.toFixed(1)}× el valor)`);
                }
            }

            if (d.ub === 1) { score += PESOS.sinCompetencia; motivos.push('sin competencia (UB=1)'); }

            const nComprAVend = (compraDe[d.buyer] || new Set()).size;
            if (nComprAVend >= HUB_COMPRADOR) {
                score += PESOS.hubComprador;
                motivos.push(`hub: ${d.buyer} compra a ${nComprAVend} vendedores`);
            }
            const nVendACompr = (vendeA[d.seller] || new Set()).size;
            if (nVendACompr >= HUB_VENDEDOR) {
                score += PESOS.hubVendedor;
                motivos.push(`hub: ${d.seller} vende a ${nVendACompr} compradores`);
            }

            const rep = pares[d.seller + '>' + d.buyer];
            if (rep > 1) { score += PESOS.parRepetido; motivos.push(`par repetido ×${rep}`); }

            d.score = score;
            d.motivos = motivos;
            d.nivel = score >= UMBRAL_ALTO ? 'alto' : score >= UMBRAL_MEDIO ? 'medio' : 'ok';
        });

        // ---- 3er pase: pintar ----
        const tint = (celdas, color) => { for (const td of celdas) td.style.background = color; };
        datos.forEach(d => {
            if (d.nivel === 'alto')  tint(d.celdas, COL_ALTO);
            else if (d.nivel === 'medio') tint(d.celdas, COL_MEDIO);
            // por debajo del umbral medio no se pinta la fila

            if (d.score > 0) {
                const b = document.createElement('span');
                b.textContent = d.score;
                b.title = d.motivos.join(' · ');
                b.style.cssText = 'display:inline-block; margin-left:6px; padding:0 5px; border-radius:3px; font-size:10px; font-weight:bold; color:#fff; background:' +
                    (d.nivel === 'alto' ? '#c0392b' : d.nivel === 'medio' ? '#e67e22' : '#9aa0a6') + ';';
                d.celdas[C.buyer].appendChild(b);
            }

            // [+] con el ID real, para Cruzar Usuarios
            [[C.seller, d.sellerId, d.seller], [C.buyer, d.buyerId, d.buyer]].forEach(([col, id, nom]) => {
                if (!nom) return;
                const btn = document.createElement('button');
                btn.type = 'button';
                btn.textContent = '[+]';
                btn.className = 'ga-plus-tr';
                btn.title = 'Añadir a Cruzar Usuarios';
                btn.style.cssText = 'margin-left:4px; cursor:pointer; background:#007bff; color:#fff; border:none; border-radius:3px; font-size:9px; font-weight:bold; padding:0 3px;';
                btn.addEventListener('click', e => {
                    e.preventDefault();
                    const input = document.getElementById('ga-simple-cross-input');
                    if (!input) return;
                    GA.cargarEnCruce(id || nom);
                    btn.textContent = '[✓]';
                    setTimeout(() => (btn.textContent = '[+]'), 800);
                });
                d.celdas[col].appendChild(btn);
            });
        });

        // ---- Panel resumen ----
        const altos  = datos.filter(d => d.nivel === 'alto').sort((a, b) => b.score - a.score);
        const medios = datos.filter(d => d.nivel === 'medio');
        const recip  = datos.filter(d => d.ps > 0 && d.pp > 0);

        const hubsC = Object.entries(compraDe).filter(([, s]) => s.size >= HUB_COMPRADOR)
            .sort((a, b) => b[1].size - a[1].size);
        const hubsV = Object.entries(vendeA).filter(([, s]) => s.size >= HUB_VENDEDOR)
            .sort((a, b) => b[1].size - a[1].size);

        const panel = document.createElement('div');
        panel.style.cssText = 'margin:12px 0; padding:12px 15px; border:1px solid #c9a0a0; background:#fff8f8; border-radius:5px; font-family:sans-serif; font-size:13px; color:#333;';

        const listaHubs = (arr, verbo) => arr.slice(0, 8).map(([n, s]) =>
            `<li><b>${n}</b> ${verbo} <b>${s.size}</b>: <span style="color:#666; font-size:11px;">${[...s].join(', ')}</span></li>`).join('');

        panel.innerHTML = `
            <div style="font-size:14px; margin-bottom:8px;">
                🚨 <b>Transferencias analizadas:</b> ${datos.length} &nbsp;·&nbsp;
                <span style="background:${COL_ALTO}; padding:1px 6px; border-radius:3px;">Alto: ${altos.length}</span>
                <span style="background:${COL_MEDIO}; padding:1px 6px; border-radius:3px; margin-left:4px;">Medio: ${medios.length}</span>
                <button id="ga-tr-filtro" type="button" style="margin-left:12px; padding:3px 10px; cursor:pointer; background:#c0392b; color:#fff; border:none; border-radius:3px; font-weight:bold;">Ver solo riesgo</button>
                <button id="ga-tr-addtop" type="button" style="margin-left:6px; padding:3px 10px; cursor:pointer; background:#28a745; color:#fff; border:none; border-radius:3px; font-weight:bold;">➕ Cruzar los de riesgo alto</button>
            </div>
            <div style="display:flex; gap:20px; flex-wrap:wrap;">
                <div style="flex:1; min-width:260px;">
                    <b>🔴 Top riesgo</b>
                    <ol style="margin:4px 0 0 18px; padding:0; font-size:12px;">
                        ${altos.slice(0, 10).map(d => `<li><b>${d.seller}</b> → <b>${d.buyer}</b> <span style="color:#c0392b; font-weight:bold;">(${d.score})</span><br><span style="color:#666; font-size:11px;">${d.motivos.join(' · ')}</span></li>`).join('') || '<li style="color:#666;">ninguna</li>'}
                    </ol>
                </div>
                <div style="flex:1; min-width:260px;">
                    <b>🕸 Hubs compradores</b> <span style="color:#777; font-size:11px;">(reciben de varios)</span>
                    <ul style="margin:4px 0 8px 18px; padding:0; font-size:12px;">${listaHubs(hubsC, 'compra a') || '<li style="color:#666;">ninguno</li>'}</ul>
                    <b>🕸 Hubs vendedores</b> <span style="color:#777; font-size:11px;">(reparten a varios)</span>
                    <ul style="margin:4px 0 8px 18px; padding:0; font-size:12px;">${listaHubs(hubsV, 'vende a') || '<li style="color:#666;">ninguno</li>'}</ul>
                    <b>🔄 Recíprocos</b> <span style="color:#777; font-size:11px;">(se venden en ambas direcciones)</span>
                    <ul style="margin:4px 0 0 18px; padding:0; font-size:12px;">
                        ${recip.slice(0, 6).map(d => `<li>${d.seller} ↔ ${d.buyer} <span style="color:#666;">(PS:${d.ps} PP:${d.pp})</span></li>`).join('') || '<li style="color:#666;">ninguno</li>'}
                    </ul>
                </div>
            </div>
        `;
        tabla.parentNode.insertBefore(panel, tabla);

        // Filtro: mostrar solo alto/medio
        let filtrado = false;
        document.getElementById('ga-tr-filtro').addEventListener('click', function () {
            filtrado = !filtrado;
            datos.forEach(d => {
                const ocultar = filtrado && d.nivel !== 'alto' && d.nivel !== 'medio';
                d.tr.style.display = ocultar ? 'none' : '';
            });
            this.textContent = filtrado ? 'Ver todas' : 'Ver solo riesgo';
        });

        // Cargar los de riesgo alto en Cruzar Usuarios
        document.getElementById('ga-tr-addtop').addEventListener('click', () => {
            const input = document.getElementById('ga-simple-cross-input');
            if (!input) { alert('El widget Cruzar Usuarios no está disponible en esta página.'); return; }
            const vals = [];
            altos.forEach(d => {
                [d.sellerId || d.seller, d.buyerId || d.buyer].forEach(v => { if (v) vals.push(v); });
            });
            const n = GA.cargarEnCruce(vals);
            alert(`Se agregaron ${n} usuario(s) de las transferencias de riesgo alto.`);
        });
    }
    GA.analizarTransferencias = analizarTransferencias;

})();