Torn OC ItemEye

Shows current faction members without their required OC item on the CRIMES page. Includes links to IM, and includes custom message with easy copy paste for sending!

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Torn OC ItemEye
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Shows current faction members without their required OC item on the CRIMES page. Includes links to IM, and includes custom message with easy copy paste for sending!
// @author       HeyItzWerty [3626448]
// @match        https://www.torn.com/factions.php*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @connect      api.torn.com
// ==/UserScript==

(function() {
    'use strict';

    // --- Native Torn UI Styling ---
    GM_addStyle(`
        #oc-item-tool {
            background-color: var(--default-bg-panel-color, #f2f2f2);
            color: var(--default-color, #333);
            border-radius: 5px;
            margin-bottom: 10px;
            font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
            border: 1px solid var(--default-panel-border-color, #cccccc);
            overflow: hidden;
        }

        .oc-header {
            background: var(--default-panel-header-background, linear-gradient(to bottom, #eeeeee 0%, #cccccc 100%));
            color: var(--default-panel-header-color, #333);
            padding: 6px 10px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-bottom: 1px solid var(--default-panel-border-color, #ccc);
            font-weight: 700;
            text-shadow: var(--default-text-shadow, none);
        }

        .oc-header-title { display: flex; align-items: center; }
        
        .oc-header-title img { 
            height: 24px; 
            width: auto; 
            max-width: 150px;
            object-fit: contain; 
        }

        .oc-body { padding: 10px; background: var(--default-bg-panel-color, #fff); }

        .oc-input {
            padding: 4px 8px;
            border-radius: 3px;
            border: 1px solid var(--default-panel-border-color, #ccc);
            background: var(--default-bg-panel-active-color, #fff);
            color: var(--default-color, #333);
            font-size: 11px;
            outline: none;
        }

        .oc-btn { 
            cursor: pointer; 
            padding: 4px 10px; 
            border: 1px solid var(--default-panel-border-color, #ccc); 
            border-radius: 3px; 
            font-weight: bold; 
            font-size: 11px; 
            background: var(--default-bg-panel-active-color, #e0e0e0); 
            color: var(--default-color, #333); 
            transition: all 0.2s ease; 
            text-decoration: none;
            display: inline-flex;
            align-items: center;
            gap: 4px;
        }
        .oc-btn:hover { filter: brightness(0.9); }
        .btn-primary { border-color: #1976D2; color: #fff; background: #2196F3; }
        .btn-secondary { border-color: #388E3C; color: #fff; background: #4CAF50; }

        .icon-btn { cursor: pointer; background: transparent; border: none; color: var(--default-color, #666); padding: 2px; display: flex; align-items: center; opacity: 0.7; transition: opacity 0.2s; }
        .icon-btn:hover { opacity: 1; color: var(--default-blue-color, #005eb8); }

        .oc-table { width: 100%; border-collapse: collapse; font-size: 12px; }
        
        .oc-table th { 
            background: var(--default-table-header-bg, #f2f2f2); 
            padding: 8px 10px; 
            text-align: left; 
            font-weight: bold; 
            border-bottom: 1px solid var(--default-panel-border-color, #ccc); 
            color: #85b200;
            text-shadow: 1px 1px 0px rgba(0,0,0,0.05);
        }
        .dark-mode .oc-table th { color: #85b200; }
        
        .oc-table td { 
            padding: 8px 10px; 
            border-bottom: 1px solid var(--default-panel-border-color, #eee); 
            vertical-align: middle; 
        }
        
        .oc-table tr:nth-child(even) td { background: var(--default-bg-panel-active-color, #f9f9f9); }
        .dark-mode .oc-table tr:nth-child(even) td { background: var(--default-bg-panel-active-color, #2a2a2a); }
        
        .highlight-text { font-weight: 600; color: var(--default-color, #333); }
        .oc-link { color: var(--default-blue-color, #005eb8); text-decoration: none; font-weight: 600; }
        .oc-link:hover { text-decoration: underline; }
    `);

    // SVG Icons
    const ICONS = {
        copy: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`,
        msg: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>`,
        refresh: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>`
    };

    const LOGO_URL = "https://i.ibb.co/Zpy216QB/dawdawd.png"; 

    // --- API Handlers ---
    async function getTornData(endpoint, version = "v1") {
        const key = GM_getValue("oc_api_key", "");
        if (!key) throw new Error("API Key required.");
        let baseUrl = version === "v2" ? "https://api.torn.com/v2" : "https://api.torn.com/v1";
        
        // ADDED CACHE BUSTER (_t) to prevent mobile browsers from recycling old data
        let url = `${baseUrl}/${endpoint}${endpoint.includes('?') ? '&' : '?'}key=${key}&_t=${Date.now()}`;
        
        let res = await fetch(url);
        let data = await res.json();
        if (data.error) throw new Error(data.error.error || "API Error");
        return data;
    }

    async function runCheck() {
        const tbody = document.querySelector("#oc-tool-tbody");
        const status = document.querySelector("#oc-status");
        
        const apiKey = GM_getValue("oc_api_key", "");
        
        if (!apiKey) {
            status.innerHTML = "<em>Awaiting API Key...</em>";
            tbody.innerHTML = `
                <tr>
                    <td colspan='4' style='text-align:center; padding:30px;'>
                        <div style="margin-bottom:10px; font-weight:bold; color:var(--default-color, #333);">
                            Please enter a <span style="color:#1976D2;">Limited Access</span> API Key to use Torn OC ItemEye.
                        </div>
                        <input type="password" id="oc-inline-api-input" class="oc-input" placeholder="Paste API Key here..." style="width:250px; margin-right:8px;">
                        <button id="oc-inline-api-save" class="oc-btn btn-primary">Save Key</button>
                    </td>
                </tr>
            `;
            
            document.getElementById('oc-inline-api-save').addEventListener('click', () => {
                const newKey = document.getElementById('oc-inline-api-input').value.trim();
                if(newKey) {
                    GM_setValue("oc_api_key", newKey);
                    runCheck();
                }
            });
            return;
        }

        status.innerHTML = "<em>Scanning planned OCs...</em>";
        tbody.innerHTML = "<tr><td colspan='4' style='text-align:center; padding:20px;'>Pulling live faction data...</td></tr>";

        try {
            const itemsData = await getTornData("torn/?selections=items");
            const items = itemsData.items;
            const factionData = await getTornData("faction/?selections=basic");
            const members = factionData.members || {};
            const crimesData = await getTornData("faction/crimes?cat=planning", "v2");
            
            // Check to ensure they didn't input a Public key (which strips out crime slots)
            if (crimesData.crimes === undefined) {
                throw new Error("Cannot view Faction Crimes. Are you sure you are using a Limited Access key?");
            }

            const crimes = crimesData.crimes || [];

            let missingList = [];
            for (let crime of crimes) {
                if (!crime.slots) continue; // Safety check
                for (let slot of crime.slots) {
                    if (slot.user && slot.item_requirement && slot.item_requirement.is_available === false) {
                        missingList.push({
                            crimeName: crime.name,
                            userId: slot.user.id,
                            userName: members[slot.user.id] ? members[slot.user.id].name : `User`,
                            itemId: slot.item_requirement.id,
                            itemName: items[slot.item_requirement.id]?.name || "Unknown Item"
                        });
                    }
                }
            }

            tbody.innerHTML = "";
            if (missingList.length === 0) {
                tbody.innerHTML = "<tr><td colspan='4' style='text-align:center; padding: 20px; font-weight:bold; color: var(--default-green-color, #4CAF50);'>All assigned members are fully equipped!</td></tr>";
                status.innerText = "Scan complete. No missing items found.";
                return;
            }

            let msgTemplate = GM_getValue("oc_custom_msg", "Hey {user}, noticed you're missing a {item} for the upcoming {crime} OC. Let me know if you need me to send one over!");

            for (let item of missingList) {
                let tr = document.createElement("tr");
                
                let prefillMsg = msgTemplate
                    .replace(/{user}/g, item.userName)
                    .replace(/{item}/g, item.itemName)
                    .replace(/{crime}/g, item.crimeName);

                tr.innerHTML = `
                    <td class="highlight-text">${item.crimeName}</td>
                    <td>
                        <div style="display:flex; align-items:center; gap:8px;">
                            <a class="oc-link" href="https://www.torn.com/profiles.php?XID=${item.userId}" target="_blank">${item.userName} [${item.userId}]</a>
                            <button class="icon-btn copy-btn" title="Copy Name" data-copy="${item.userName} [${item.userId}]">${ICONS.copy}</button>
                            <button class="icon-btn copy-msg-btn" title="Copy message" data-msg="${prefillMsg}">${ICONS.msg}</button>
                        </div>
                    </td>
                    <td class="highlight-text">${item.itemName}</td>
                    <td>
                        <div style="display:flex; gap:6px;">
                            <button class="oc-btn btn-primary btn-buy" data-itemid="${item.itemId}">Market</button>
                            <button class="oc-btn btn-secondary btn-items" title="Go to Items Page">Send Item</button>
                        </div>
                    </td>
                `;
                tbody.appendChild(tr);
            }
            status.innerHTML = `<em>Scan complete. Found ${missingList.length} missing item(s).</em>`;
            attachListeners();
        } catch (e) {
            status.innerHTML = `<span style="color:red; font-weight:bold;">Error: ${e.message}</span>`;
            tbody.innerHTML = `<tr><td colspan='4' style='text-align:center; padding:20px; color:red;'>${e.message}</td></tr>`;
        }
    }

    function attachListeners() {
        document.querySelectorAll('.copy-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                GM_setClipboard(this.getAttribute('data-copy'));
                this.innerHTML = `<span style="color:green; font-weight:bold;">✓</span>`;
                setTimeout(() => this.innerHTML = ICONS.copy, 1000);
            });
        });

        document.querySelectorAll('.copy-msg-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                GM_setClipboard(this.getAttribute('data-msg'));
                this.innerHTML = `<span style="color:green; font-weight:bold;">✓</span>`;
                setTimeout(() => this.innerHTML = ICONS.msg, 1000);
            });
        });

        document.querySelectorAll('.btn-buy').forEach(btn => {
            btn.addEventListener('click', function() {
                window.open(`https://www.torn.com/page.php?sid=ItemMarket#/market/view=search&itemID=${this.dataset.itemid}`, '_blank');
            });
        });

        document.querySelectorAll('.btn-items').forEach(btn => {
            btn.addEventListener('click', () => window.open('https://www.torn.com/item.php', '_blank'));
        });
    }

    function injectUI() {
        if (document.getElementById('oc-item-tool')) return;
        const target = document.querySelector('.factions-crimes-wrap') || document.querySelector('#factions');
        if (!target) return;

        const container = document.createElement('div');
        container.id = 'oc-item-tool';
        container.innerHTML = `
            <div class="oc-header">
                <div class="oc-header-title">
                    <img src="${LOGO_URL}" alt="ItemEye Logo" onerror="this.outerHTML=''">
                    <span style="color: #85b200; font-weight: bold; margin-left: 8px; font-size: 13px; letter-spacing: 0.5px;">Torn OC ItemEye</span>
                </div>
                <button id="oc-refresh-btn" class="oc-btn">${ICONS.refresh} Refresh</button>
            </div>
            <div class="oc-body">
                <div style="display:flex; justify-content: space-between; align-items:flex-end; margin-bottom: 8px;">
                    <span id="oc-status" style="font-size:11px; color: var(--default-color, #666);">Initializing...</span>
                    
                    <div style="display:flex; gap: 12px;">
                        <button id="oc-edit-msg" style="background:none; border:none; color:var(--default-blue-color, #005eb8); font-size:10px; cursor:pointer; text-decoration:underline;">Edit Copy Message</button>
                        <button id="oc-api-reset" style="background:none; border:none; color:var(--default-blue-color, #005eb8); font-size:10px; cursor:pointer; text-decoration:underline;">Reset API Key</button>
                    </div>
                </div>
                <table class="oc-table">
                    <thead><tr><th>Crime</th><th>Member</th><th>Item Needed</th><th>Actions</th></tr></thead>
                    <tbody id="oc-tool-tbody"></tbody>
                </table>
            </div>
        `;
        target.parentNode.insertBefore(container, target);
        
        document.getElementById('oc-refresh-btn').addEventListener('click', runCheck);
        
        document.getElementById('oc-api-reset').addEventListener('click', () => {
            GM_setValue("oc_api_key", "");
            runCheck(); // Will instantly show the inline input box again
        });

        document.getElementById('oc-edit-msg').addEventListener('click', () => {
            let currentMsg = GM_getValue("oc_custom_msg", "Hey {user}, noticed you're missing a {item} for the upcoming {crime} OC. Let me know if you need me to send one over!");
            
            let promptText = "Customize your copy message button for easy pasting when sending an item!\\n" +
                             "{user}  -> Member's name\\n" +
                             "{item}  -> Missing item name\\n" +
                             "{crime}  -> Current OC";

            let newMsg = prompt(promptText, currentMsg);
            
            if (newMsg !== null && newMsg.trim() !== "") {
                GM_setValue("oc_custom_msg", newMsg.trim());
                runCheck(); 
            }
        });
        
        runCheck();
    }

    setInterval(() => {
        if (location.href.includes('tab=crimes') && !document.getElementById('oc-item-tool')) injectUI();
    }, 1500);

})();