YATA Level Filter

Level filter for YATA target list

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         YATA Level Filter
// @namespace    YATA Level Filter
// @description  Level filter for YATA target list
// @author       tasozz
// @match        https://yata.yt/*/target/*
// @match        https://yata.yt/target/*
// @version      1.2
// @icon         https://yata.yt/static/favicon.ico
// @grant        none
// ==/UserScript==


/* global $, jQuery */

(function() {
    'use strict';
    // Take page jQuery into the userscript scope
    const $ = window.jQuery;

    // NEW: helper to get CSRF cookie (name matches csrftoken cookie)
    function getCookie(name) {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) return parts.pop().split(";").shift();
    }

    function insertFilterUI() {
        // Prevent duplicates
        if (document.getElementById("yata-level-filter-box")) return;

        const table = document.querySelector("#target-targets");
        if (!table) return;

        const wrapper = document.createElement("div");
        wrapper.id = "yata-level-filter-box";
        wrapper.style.padding = "10px";
        wrapper.style.margin = "10px 0";
        wrapper.style.border = "1px solid #ccc";
        wrapper.style.borderRadius = "6px";
        wrapper.style.background = "#f7f7f7";

        wrapper.innerHTML = `
            <b>Level Filter:</b>
            <br>
            Min: <input type="number" id="level-filter-min" style="width:70px; margin-right:10px;" value="1">
            Max: <input type="number" id="level-filter-max" style="width:70px; margin-right:10px;" value="100">
            <button id="apply-level-filter" class="btn btn-sm btn-primary">Apply</button>
            <button id="clear-level-filter" class="btn btn-sm btn-secondary">Clear</button>

            <!-- NEW: Sort Button -->
            <button id="sort-status-btn" class="btn btn-sm btn-success" style="margin-left:10px;">
                Sort by Status
            </button>

            <!-- NEW: AUTO-OPEN BUTTON -->
            <button id="open-first-ok" class="btn btn-sm btn-danger" style="margin-left:10px;">
                Attack First Okay Target
            </button>

            <!-- NEW: REFRESH BUTTON -->
            <button id="refresh-visible" class="btn btn-sm btn-warning" style="margin-left:10px;">
                Refresh Visible Only
            </button>

        `;

        table.parentNode.insertBefore(wrapper, table);

        document.getElementById("apply-level-filter").onclick = filterRows;
        document.getElementById("clear-level-filter").onclick = clearFilter;

        // Sorting button
        document.getElementById("sort-status-btn").onclick = sortByStatus;

        // NEW button action
        document.getElementById("open-first-ok").onclick = openFirstOK;

        // NEW button refresh
        document.getElementById("refresh-visible").onclick = refreshVisibleOnly;

    }

    function clearFilter() {
        document.getElementById("level-filter-min").value = 1;
        document.getElementById("level-filter-max").value = 100;
        filterRows();
    }

    function filterRows() {
        const min = parseInt(document.getElementById("level-filter-min").value) || 1;
        const max = parseInt(document.getElementById("level-filter-max").value) || 100;

        const rows = document.querySelectorAll("#target-targets tbody tr");
        rows.forEach(row => {
            const levelCell = row.querySelector("td:nth-child(4)");
            if (!levelCell) return;

            const level = parseInt(levelCell.textContent.trim());
            row.style.display = (level >= min && level <= max) ? "" : "none";
        });
    }

    // ✅ NEW: Sort by Status → Okays first → hospital time remaining
    function sortByStatus() {
        const tbody = document.querySelector("#target-targets tbody");
        const rows = Array.from(tbody.querySelectorAll("tr"));

        rows.sort((a, b) => {
            const statusCellA = a.querySelector("td:nth-child(10)");
            const statusCellB = b.querySelector("td:nth-child(10)");

            const textA = statusCellA.textContent.trim();
            const textB = statusCellB.textContent.trim();

            const isOkayA = textA === "Okay";
            const isOkayB = textB === "Okay";

            // ✔ OKAY targets go first
            if (isOkayA && !isOkayB) return -1;
            if (isOkayB && !isOkayA) return 1;

            // ✔ If both Okay, keep original order
            if (isOkayA && isOkayB) return 0;

            // ✔ Both are non-Okay (hospital, travel, etc.)
            // Sort by hospital remaining time (data-val)
            const timeA = parseInt(statusCellA.dataset.val || "99999999");
            const timeB = parseInt(statusCellB.dataset.val || "99999999");

            return timeA - timeB; // shortest → longest
        });

        rows.forEach(r => tbody.appendChild(r));
    }

    // Tracks which targets have already been opened
    let openedTargets = new Set();

    function openFirstOK() {
        const rows = Array.from(document.querySelectorAll("#target-targets tbody tr"))
            .filter(r => r.style.display !== "none");

        for (const row of rows) {

            // Get the target ID from row id="target-list-refresh-XXXX"
            const idMatch = row.id.match(/(\d+)/);
            if (!idMatch) continue;
            const targetID = idMatch[1];

            // Skip if already opened before
            if (openedTargets.has(targetID)) continue;

            // Attack button cell is 13th TD
            const attackCell = row.querySelector("td:nth-child(13)");
            if (!attackCell) continue;

            // OK targets have data-val="1"
            if (attackCell.dataset.val !== "1") continue;

            // Get the actual <a> element
            const attackBtn = attackCell.querySelector("a");
            if (!attackBtn) continue;

            // Icon must contain class "error"
            const icon = attackBtn.querySelector("i");
            if (!icon) continue;
            if (!icon.classList.contains("error")) continue;

            // OPEN IT!
            window.open(attackBtn.href, "_blank");

            // Remember it so it is NOT opened again
            openedTargets.add(targetID);

            return; // Stop after opening next available OK target
        }

        console.log("No new OK targets to attack.");
    }

    // CHANGED: use correct POST /target/target/ with CSRF, one request per visible row
function refreshVisibleOnly() {
    const visibleRows = Array.from(document.querySelectorAll("#target-targets tbody tr"))
        .filter(r => r.style.display !== "none" && r.id.startsWith("target-list-refresh-"));

    if (!visibleRows.length) {
        console.log("No visible rows to refresh");
        return;
    }

    const csrfToken = getCookie("csrftoken");
    if (!csrfToken) {
        console.warn("CSRF token not found");
        return;
    }

    visibleRows.forEach((row, index) => {
        const rowID = row.id.replace("target-list-refresh-", "");

        setTimeout(() => {
            $.ajax({
                url: "https://yata.yt/target/target/",
                method: "POST",
                data: {
                    targetId: rowID,
                    type: "update",
                    csrfmiddlewaretoken: csrfToken
                },
                success: function (html) {
                    console.log("Refreshed target", rowID);

                    const newRow = $("<tr id='target-list-refresh-" + rowID + "'>" + html + "</tr>");
                    $("#target-list-refresh-" + rowID).replaceWith(newRow);
                },
                error: function (xhr) {
                    console.warn("Refresh failed for", rowID, "status", xhr.status);
                }
            });
        }, index * 1000); // 0ms, 500ms, 1000ms, ...
    });
}



    // Wait for YATA dynamic loading
    const watcher = setInterval(() => {
        if (document.querySelector("#target-targets tbody tr")) {
            clearInterval(watcher);
            insertFilterUI();
        }
    }, 300);
})();