Nomad's Quick Revive

The fastest way to request a revive from Nomad Medical: with a button on your main page, you're just a click away from being in the hands of our great revivers. Never hesitate to use the Nomad Medical revive service!

// ==UserScript==
// @name         Nomad's Quick Revive
// @namespace    http://tampermonkey.net/
// @version      1.2.2.1
// @description  The fastest way to request a revive from Nomad Medical: with a button on your main page, you're just a click away from being in the hands of our great revivers. Never hesitate to use the Nomad Medical revive service!
// @author       LilyWaterbug [2608747]
// @match        https://www.torn.com/*
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    let currentUrl = ''; // To detect URL changes
    let profileButtonAdded = false; // To track if the profile button is added

    // Initialize the script
    function initialize() {
        currentUrl = window.location.href;
        handlePageLoad();

        // Set up a MutationObserver to monitor dynamic changes
        const observer = new MutationObserver(() => {
            if (currentUrl !== window.location.href) {
                // URL has changed
                currentUrl = window.location.href;
                profileButtonAdded = false; // Reset the flag on URL change
            }
            handlePageLoad();
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Handle page load or dynamic DOM changes
    function handlePageLoad() {
        const currentUrl = window.location.href;

        // Check for profile pages
        if (currentUrl.includes('/profiles.php?XID=')) {
            handleProfilePage();
        }

        // Check for the main page
        let mainPageContainer;
        if (isMobileView()) {
            mainPageContainer = document.querySelector('.header-buttons-wrapper'); // Sidebar móvil
        } else {
            mainPageContainer = document.querySelector('.toggle-content___BJ9Q9 .content___GVtZ_'); // Desktop
        }

        if (mainPageContainer && !document.getElementById('quick-revive-button')) {
            addMainPageButton(mainPageContainer);
        }
    }

    // Handle logic for profile pages
    function handleProfilePage() {
        const buttonsWrap = document.querySelector('.buttons-wrap .buttons-list');
        const userId = window.location.href.match(/XID=(\d+)/)?.[1];

        // Ensure the button is only added once
        if (buttonsWrap && !document.getElementById('profile-revive-button') && userId) {
            checkIfUserCanBeRevived(userId, (canBeRevived) => {
                if (canBeRevived) {
                    addProfilePageButton(buttonsWrap, userId);
                    profileButtonAdded = true;
                }
            });
        }
    }

    // Función para agregar botón en la página principal (adaptada para mobile)
    function addMainPageButton(container) {
        const button = document.createElement('button');
        button.id = 'quick-revive-button';

        // Cambiar el texto según la vista
        button.innerText = isMobileView() ? 'Nurse' : 'Nurse Nomad';

        // Aplicar los estilos directamente (como los que ajustaste manualmente)
        button.style.margin = isMobileView() ? '6.5px 0px' : '10px auto';
        button.style.padding = isMobileView() ? '3px 10px' : '2.5px 15px';
        button.style.display = isMobileView() ? 'inline-block' : 'block';
        button.style.fontSize = isMobileView() ? '12px' : '14px';
        button.style.backgroundColor = '#D32F2F';
        button.style.color = '#FFFFFF';
        button.style.border = 'none';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        button.style.fontWeight = 'bold';
        button.style.textAlign = 'center';
        button.style.webkitTextStroke = '0.2px #FFFFFF';
        button.style.boxShadow = `
            0 0 0 2px #f2f2f2,
            0 0 0 4px #D32F2F
        `;

        if (isMobileView()) {
            button.style.position = 'absolute';

            // Encuentra el botón del menú principal
            const menuButton = document.querySelector('.top_header_button.header-menu-icon');
            if (menuButton) {
                const menuButtonRect = menuButton.getBoundingClientRect();
                button.style.top = `${menuButtonRect.top}px`;
                button.style.left = `${menuButtonRect.right + 10}px`; // Posicionar justo a la derecha con un margen de 10px
            } else {
                button.style.top = '10px'; // Posición predeterminada si no se encuentra el botón
                button.style.right = '20px'; // Mover a la derecha
            }
        }

        button.addEventListener('click', () => {
            const sidebarData = getSidebar();
            const userName = sidebarData?.user?.name || 'Unknown';
            const userId = sidebarData?.user?.userID || 'Unknown';

            if (userName && userId) {
                handleReviveRequest(userName, userId);
            } else {
                alert('Error: Unable to extract user information.');
            }
        });

        // Renderizar el botón en mobile o desktop
        let targetContainer;
        if (isMobileView()) {
            const menuContainer = document.querySelector('.header-menu.left.leftMenu___md3Ch.dropdown-menu');
            if (menuContainer) {
                targetContainer = menuContainer.parentNode; // Adjuntar como hermano del menú
            }
        } else {
            targetContainer = container; // Desktop
        }

        if (targetContainer) {
            targetContainer.appendChild(button);
        }
    }

    // Función para detectar si el dispositivo es mobile
    function isMobileView() {
        return window.innerWidth <= 768; // Puedes ajustar este valor según el diseño responsivo
    }

    // Obtener datos del sidebar (para mobile y desktop)
    function getSidebar() {
        let key = Object.keys(sessionStorage).find(key => /sidebarData\d+/.test(key));
        let sidebarData = JSON.parse(sessionStorage.getItem(key));
        return sidebarData;
    }

    // Detectar si la página está en dark mode
    function isDarkMode() {
        const bodyElement = document.getElementById('body');
        return bodyElement.classList.contains('dark-mode');
    }

    function makeRequestToServer(endpoint, params = {}, callback, errorCallback) {
        GM_xmlhttpRequest({
            method: 'POST',
            url: 'http://lilywaterbug.art:6224/torn-api',
            headers: { 'Content-Type': 'application/json' },
            data: JSON.stringify({
                endpoint: endpoint,
                params: params,
            }),
            onload: function (response) {
                if (response.status === 200) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (callback) callback(data);
                    } catch (e) {
                        console.error('Failed to parse response:', e);
                        if (errorCallback) errorCallback('Failed to parse response.');
                    }
                } else {
                    console.error('Request failed with status:', response.status);
                    if (errorCallback) errorCallback('Request failed.');
                }
            },
            onerror: function () {
                console.error('An error occurred while making the request.');
                if (errorCallback) errorCallback('An error occurred while making the request.');
            },
        });
    }


    // Función para agregar botón en la página de perfil
    function addProfilePageButton(container, targetUserId) {
        if (document.getElementById('profile-revive-button')) return;

        const profileButton = document.createElement('a');
        profileButton.id = 'profile-revive-button';
        profileButton.href = '#';

        profileButton.className = 'profile-button profile-button-revive active';
        profileButton.setAttribute('aria-label', 'Request Revive');
        profileButton.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" class="default___XXAGt profileButtonIcon" filter="" fill="url(#linear-gradient)" stroke="#d4d4d4" stroke-width="0" viewBox="0 0 512 512" style="width: 36px; height: 36px; margin: 5px;">
            <path d="M249.84 479.52c-6.048 0-12.024 0-18.144-.216-.504-.576-1.008-1.08-1.512-1.44-3.96-2.952-8.424-5.472-11.952-8.928-16.92-16.632-33.624-33.48-50.472-50.256-.864-.936-1.872-1.8-2.88-2.664-3.888 2.736-7.272 5.976-11.304 7.992-14.544 7.272-29.736 7.848-45.072 2.664-12.168-4.104-20.376-12.888-25.992-24.192-4.32-8.784-4.536-18.36-4.68-27.864-.288-14.04-.144-28.08 0-42.12 0-2.664-.72-4.608-2.664-6.624-20.736-20.52-41.256-41.184-61.92-61.704-4.68-4.608-8.784-9.432-11.088-15.624-.144-.432-.936-.576-1.44-.864 0-4.608 0-9.144.216-13.896.576-.576 1.08-1.008 1.224-1.512 1.872-5.4 4.896-10.008 9-14.04 21.456-21.384 42.912-42.912 64.296-64.368.936-.936 1.944-2.304 2.016-3.528.144-9.144-.072-18.36.144-27.576.288-15.264.36-30.6 1.224-45.864.72-11.232 6.192-20.736 14.256-28.296 11.664-10.872 25.848-13.824 41.4-12.384 10.8 1.008 19.8 5.4 27.432 13.032 2.88 2.952 6.048 5.688 9.288 8.784.576-.576 1.44-1.296 2.304-2.16 15.264-15.264 30.456-30.528 45.72-45.72C222.912 6.48 226.8 3.168 232.056 2.088c.288 0 .36-.864.504-1.368 5.328 0 10.584 0 15.984.216 8.208 3.168 13.608 9.648 19.44 15.48 15.408 15.408 30.888 30.888 46.368 46.296 1.8 1.8 3.816 3.528 5.904 5.544.36-.864.576-1.224.72-1.656 4.968-12.888 13.896-22.176 26.712-27.216 9.504-3.816 19.584-4.176 29.736-2.808 9.288 1.296 17.28 5.184 24.192 11.16 12.24 10.512 15.984 24.768 16.2 40.104.36 23.832.36 47.736 0 71.568 0 5.04 1.584 8.496 4.968 11.88 16.488 16.272 32.832 32.616 49.104 49.176 2.376 2.448 3.816 5.76 5.832 8.64.72 1.008 1.656 1.8 2.52 2.736 0 5.328 0 10.584-.216 15.984-.576.36-1.152.576-1.224.936-1.224 4.752-3.744 8.568-7.416 11.808-3.744 3.312-7.128 6.912-10.584 10.512-4.176 4.32-8.208 8.784-12.456 13.032-4.32 4.32-8.856 8.352-13.248 12.672-5.184 5.112-10.368 10.224-15.408 15.48-.936 1.008-1.656 2.664-1.728 4.032-.144 8.568 0 17.136-.072 25.704 0 11.016.216 22.032-.288 32.976-.36 7.704-.72 15.696-2.952 22.968-4.464 13.896-13.536 24.408-28.008 28.872-20.52 6.336-39.528 4.392-55.44-11.736-2.304-2.376-4.824-4.536-7.56-7.056-1.728 1.944-3.528 4.176-5.472 6.192-3.888 3.96-7.776 7.92-11.736 11.808-4.824 4.68-9.792 9.216-14.544 13.968-4.032 4.032-7.704 8.28-11.664 12.312-4.824 4.68-9.936 9.072-14.544 13.968-4.392 4.752-8.928 9-14.832 11.808-.432.216-.648.936-1.008 1.44M401.832 335.16c0-81 0-162-.072-243 0-4.392-.072-8.784-.792-13.104-1.584-10.08-6.696-18-15.984-22.68-8.208-4.248-17.064-3.672-25.776-2.664-3.384.36-6.84 1.728-9.864 3.384-11.448 6.264-15.84 16.92-15.912 29.16-.216 61.2-.072 122.4-.072 183.6 0 1.368 0 2.808 0 4.392-3.744 0-7.128.072-10.44-.072-1.08-.072-2.376-.576-3.024-1.296-2.52-2.88-4.752-5.904-7.056-8.928-10.44-13.464-20.736-27-31.248-40.32-10.296-12.96-20.88-25.704-31.176-38.664-10.152-12.672-20.016-25.632-30.024-38.448-6.912-8.712-13.896-17.352-20.808-26.064-8.064-10.08-15.912-20.232-23.976-30.312-6.192-7.776-12.312-15.624-19.008-22.968-3.816-4.248-8.424-8.064-13.176-11.16-5.76-3.672-12.6-2.736-19.08-2.808-14.256-.288-26.784 9.936-29.16 23.688-1.152 6.624-.864 13.536-.864 20.304-.072 96.696-.072 193.392.072 290.088 0 6.696 2.592 12.744 7.056 17.64 10.8 11.88 24.984 13.32 38.88 9.144 14.976-4.536 21.456-14.76 21.528-30.24.432-61.56.144-123.12.144-184.68 0-1.296 0-2.52 0-4.032 3.744 0 7.056-.144 10.44.072 1.296.144 3.024.792 3.816 1.872 7.704 9.36 15.264 18.864 22.824 28.44 10.224 13.104 20.376 26.28 30.672 39.312 11.304 14.328 22.68 28.512 33.984 42.696s22.68 28.296 33.912 42.48c10.008 12.672 19.8 25.56 29.736 38.232 3.384 4.32 6.84 8.64 10.584 12.744 5.76 6.264 11.808 12.528 20.664 14.04 4.968.864 10.224.504 15.336.36 2.304-.072 4.68-.576 6.84-1.368 13.896-5.328 20.736-16.56 20.952-31.32.144-15.624.072-31.176.072-47.52m-179.28-54.792C208.08 262.08 193.608 243.792 178.56 224.712c0 2.088 0 3.24 0 4.392 0 49.896 0 99.792-.072 149.688 0 2.088-.504 4.248-.648 6.336-.216 2.376.072 4.392 2.088 6.408 10.8 10.512 21.384 21.312 32.04 32.04 9.576 9.576 19.08 19.152 28.512 28.656 22.104-22.176 44.064-44.136 66.024-66.096-6.48-8.064-13.176-16.344-19.872-24.552-2.664-3.384-5.256-6.912-7.992-10.368-5.976-7.56-11.952-15.12-18-22.68-3.312-4.248-6.696-8.424-10.008-12.672-5.4-6.84-10.728-13.752-16.128-20.592-3.888-4.824-7.704-9.648-11.952-14.904M235.44 33.48c-15.408 15.408-30.816 30.816-46.296 46.296 42.48 53.712 84.744 107.208 127.656 161.496 0-1.584 0-2.016 0-2.448 0-44.208.072-88.344-.072-132.48 0-1.368-.72-3.024-1.656-3.96-4.608-4.824-9.432-9.504-14.112-14.184-12.816-12.816-25.632-25.704-38.448-38.52-7.272-7.2-14.472-14.4-21.6-21.6-1.872 1.872-3.528 3.456-5.472 5.4m-158.328 190.08c0-10.368 0-20.736 0-31.464-16.272 16.344-32.184 32.256-48.24 48.312 15.48 16.128 31.104 32.4 46.656 48.672.504-.504 1.08-1.008 1.584-1.512 0-21.096 0-42.192 0-64.008m341.136 6.48c0 14.688 0 29.304 0 45 11.52-11.592 22.248-22.464 33.048-33.264 1.944-1.944.36-2.88-.792-4.032-10.152-10.152-20.304-20.304-30.528-30.528-.432-.432-.936-.72-1.584-1.296-.072.648-.072.864-.072 1.08 0 7.488 0 14.904-.072 23.04Z" />
        </svg>
        `;

        const svg = profileButton.querySelector('svg');
        svg.setAttribute('width', '36');
        svg.setAttribute('height', '36');
        svg.style.margin = '5px';
        svg.style.stroke = '#d4d4d4';

        // Detectar si está en dark mode
        if (isDarkMode()) {
            svg.style.fill = 'url(#linear-gradient-dark-mode)';
        } else {
            svg.style.fill = 'url(#linear-gradient)';
        }

        profileButton.addEventListener('click', (e) => {
            e.preventDefault();

            let requesterName, requesterId;
            if (isMobileView()) {
                const sidebarData = getSidebar();
                requesterName = sidebarData?.user?.name || 'Unknown';
                requesterId = sidebarData?.user?.userID || 'Unknown';
            } else {
                const requesterNameElement = document.querySelector('.menu-info-row___YG31c .menu-value___gLaLR');
                const requesterLink = requesterNameElement?.getAttribute('href');
                requesterName = requesterNameElement?.textContent.trim();
                const requesterIdMatch = requesterLink?.match(/XID=(\d+)/);
                requesterId = requesterIdMatch ? requesterIdMatch[1] : null;
            }

            if (requesterName && requesterId) {
                fetchRequesterFaction(requesterId, (requesterFaction) => {
                    fetchTargetInfo(targetUserId, (targetInfo) => {
                        handleReviveRequestProfile(requesterName, requesterId, requesterFaction, targetInfo);
                    });
                });
            } else {
                alert('Error: Unable to extract requester information.');
            }
        });

        container.appendChild(profileButton);
    }

    // Obtener información de la facción del solicitante
    function fetchRequesterFaction(requesterId, callback) {
        makeRequestToServer(
            `user/${requesterId}`,
            { selections: 'faction' },
            (data) => {
                const factionName = data.faction?.faction_name || 'No Faction';
                const factionUrl = data.faction?.faction_id
                ? `https://www.torn.com/factions.php?step=profile&ID=${data.faction.faction_id}#/`
                : 'No Faction URL';
                callback({ name: factionName, url: factionUrl });
            },
            (error) => {
                alert(`Failed to fetch requester faction: ${error}`);
            }
        );
    }

    // Obtener información del usuario objetivo
    function fetchTargetInfo(targetUserId, callback) {
        makeRequestToServer(
            `user/${targetUserId}`,
            { selections: '' },
            (data) => {
                callback({
                    name: data.name || 'Unknown',
                    id: targetUserId,
                    status: data.status?.description || 'N/A',
                    faction: data.faction?.faction_name || 'No Faction',
                    faction_url: data.faction?.faction_id ? `https://www.torn.com/factions.php?step=profile&ID=${data.faction.faction_id}#/`
                    : 'No Faction URL',
                    details: data.status?.details || 'No Details',
                });
            },
            (error) => {
                alert(`Failed to fetch target info: ${error}`);
            }
        );
    }


    // Solicitud personalizada para el botón de perfil
    function handleReviveRequestProfile(requesterName, requesterId, requesterFaction, targetInfo) {
        const formattedRequester = `${requesterName} [${requesterId}]`;
        const formattedTarget = `${targetInfo.name} [${targetInfo.id}]`;

        const data = {
            function: 'profile-revive',
            requester: formattedRequester,
            requester_faction: requesterFaction.name,
            requester_faction_url: requesterFaction.url,
            target: formattedTarget,
            target_status: targetInfo.status,
            target_faction: targetInfo.faction,
            target_faction_url: targetInfo.faction_url,
            details: targetInfo.details,
        };

        GM_xmlhttpRequest({
            method: 'POST',
            url: 'http://lilywaterbug.art/webhook',
            headers: { 'Content-Type': 'application/json' },
            data: JSON.stringify(data),
            onload: function (response) {
                if (response.status === 200) {
                    alert('Profile revive request sent successfully!');
                    alert('Disclaimer: Remember to pay 1M or a Xanax to the person reviving your target.');
                } else {
                    alert('Failed to send profile revive request.');
                }
            },
            onerror: function () {
                alert('An error occurred while sending the profile revive request.');
            },
        });
    }

    function handleReviveRequest(userName = 'Unknown', userId = 'Unknown') {
        makeRequestToServer(
            `user/${userId}`,
            { selections: '' },
            (data) => {
                const statusState = data.status?.state || 'N/A';
                const revivable = data.revivable || 0;

                if (statusState !== 'Hospital') {
                    alert('This user is not hospitalized right now.');
                    return;
                }

                if (revivable === 0) {
                    alert('Turn on your revives!');
                    return;
                }

                const requestData = {
                    function: 'revive',
                    usuario: `${userName} [${userId}]`,
                    status: data.status?.description || 'N/A',
                    faction: data.faction?.faction_name || 'No Faction',
                    faction_url: data.faction?.faction_id ? `https://www.torn.com/factions.php?step=profile&ID=${data.faction.faction_id}#/`
                    : 'No Faction URL',
                    details: data.status?.details || 'No Details',
                };

                GM_xmlhttpRequest({
                    method: 'POST',
                    url: 'http://lilywaterbug.art/webhook',
                    headers: { 'Content-Type': 'application/json' },
                    data: JSON.stringify(requestData),
                    onload: function (response) {
                        if (response.status === 200) {
                            alert('Revive request sent successfully!');
                        } else {
                            alert('Failed to send revive request.');
                        }
                    },
                    onerror: function () {
                        alert('An error occurred while sending the revive request.');
                    },
                });
            },
            (error) => {
                alert(`Failed to fetch user data: ${error}`);
            }
        );
    }


    // Función para verificar si el usuario puede ser revivido
    function checkIfUserCanBeRevived(userId, callback) {
        makeRequestToServer(
            `user/${userId}`,
            { selections: '' },
            (data) => {
                const statusState = data.status?.state || 'N/A';
                const revivable = data.revivable || 0;
                callback(statusState === 'Hospital' && revivable === 1);
            },
            (error) => {
                callback(false);
                console.error(`Failed to check if user can be revived: ${error}`);
            }
        );
    }
    initialize();
})();