Company Enhancement Suite

Your one stop solution to managing your company.

当前为 2019-05-26 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Company Enhancement Suite
// @namespace    LordBusiness.CES
// @version      3.4
// @description  Your one stop solution to managing your company.
// @author       LordBusiness
// @match        *.torn.com/companies.php
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
    .last_action_icon {
        cursor: pointer;
        vertical-align: middle;
        display: inline-block;
        background-image: url(/images/v2/sidebar_icons_desktop_2017.png);
        background-repeat: no-repeat;
        background-position-y: -783px;
        width: 34px;
        height: 30px;
    }

    .stock-list li:not(.total) .delivery {
        background-color: white;
    }

    .order.hidden ~ .fill-custom,
    .order.hidden ~ .fill-daily {
        display: none;
    }`);

    const APIkeyName = 'TornApiKey';
    let APIkey = localStorage.getItem(APIkeyName),
        itemsStock = JSON.parse(localStorage.getItem("TornCustomStock") || '{}'),
        keyUpEvent = new KeyboardEvent('keyup');

    /* Credits to Mafia [610357] for the original snippet of code from his excellent library, tornlib.
       Rewritten in Vanilla JS cuz .. who uses jQuery nowadays? */
    const getAPIkey = () => {
        localStorage.removeItem(APIkeyName);
        document.querySelector('div.content-title').insertAdjacentHTML('afterend',
        `<div id='apibox' class="m-top10">
            <div class="title-gray top-round" role="heading" aria-level="5">
            <i class="issue-attention-icon"></i>
            <span id="title">API Key required</span>
            </div>
            <div class="bottom-round cont-gray p10" id="api-input-wrapper">
                <fieldset class="submit-wrap">
                    <p>You are currently using <strong>${GM.info.script.name}</strong> that requires your API key. Please fill in your API key to use it.</p><br/>
                    <div class="cont-quantity left">
                        <div class="input-money-group"><span title="Fill with your correct API key" class="input-money-symbol">KEY</span><input id="api-input" class="quantity price input-money" type="text" value=""></div>
                    </div>
                    <div class="cont-button left" id="apiSignIn" style="margin-left: 10px;">
                        <span class="btn-wrap silver">
                            <span class="btn c-pointer bold" style="padding: 0 15px 0 10px;"><span>SIGN IN</span></span>
                        </span>
                    </div>
                    <div class="clear"></div>
                </fieldset>
            </div>
            <!--div class="clear"></div-->
            <hr class="page-head-delimiter m-top10">
        </div>`);
        const apiWrapper = document.getElementById("api-input-wrapper");
        document.getElementById("apiSignIn").addEventListener('click', () => {
            APIkey = document.getElementById("api-input").value.trim();
            fetch(`https://api.torn.com/user/?selections=profile&key=${APIkey}`)
                .then(response => response.json())
                .then(data => {
                if(data.error) {
                    alert(data.error.error);
                    apiWrapper.querySelector(".input-money-group").classList.add("error");
                } else {
                    localStorage.setItem(APIkeyName, APIkey);
                    apiWrapper.innerHTML = `Hi ${data.name}, you have successfully signed in with your API key. This page will be refreshed in a moment.`;
                    setTimeout(() => location.reload(), 3000);
                }
            });
        });
    };

    // Add the t-red class to parent element
    const makeRed = theElement => theElement.parentElement.classList.add('t-red');

    // Check trains to make sure you don't lose a few tomorrow
    const checkTrains = () => {
        const trains = document.querySelector(".trains"),
              stars = document.querySelector(".company-rating").querySelectorAll("li.active").length;
        if(parseInt(trains.innerText) + stars > 20) {
            makeRed(trains);
        }
    }

    // Check if employee capacity is filled
    const checkEmployees = () => {
        const currentEmployees = document.querySelector(".total-employees"),
              maxEmployees = document.querySelector(".limit-employees");
        if(parseInt(maxEmployees.innerText) - parseInt(currentEmployees.innerText)) {
            makeRed(maxEmployees);
        }
    }

    /* Employee last action is a refined (AKA semi-colonified) version of tos's Faction Last Action script.
       See https://www.torn.com/forums.php#/p=threads&f=67&t=16046522&b=0&a=0&to=18638360
       I'm starting to wonder if my script is just a compilation of everybody else's scripts. */

    const generateTimeColor = timeInterval => {
        if (timeInterval.includes('minute')) return 'ftGreen';
        else if (timeInterval.includes('hour')) return (parseInt(timeInterval.split(' ')[0]) <= 12) ? 't-green' : 'ftDarkGold';
        return 't-red';
    }

    const toggleLastAction = (columnTitle, memberList) => {
        if (columnTitle.innerText === 'Rank') {
            columnTitle.childNodes[0].nodeValue = 'Last Action';
            for (const li of memberList.children) {
                const lastActionDIV = li.querySelector('.last-action');
                const memberID = lastActionDIV.getAttribute('data-member-ID');
                fetch(`https://api.torn.com/user/${memberID}?selections=profile&key=${APIkey}`)
                .then(response => response.json())
                .then(response => {
                    const lastAction = response.last_action.relative;
                    li.querySelector('.rank .employee-rank-drop-list').classList.toggle('hide');
                    lastActionDIV.innerText = lastAction;
                    lastActionDIV.classList.add(generateTimeColor(lastAction));
                    lastActionDIV.classList.toggle('hide');
                });
            }
        }
        else {
            columnTitle.childNodes[0].nodeValue = 'Rank';
            for (const li of memberList.children) {
                li.querySelector('.rank .employee-rank-drop-list').classList.toggle('hide');
                li.querySelector('.last-action').classList.toggle('hide');
            }
        }
    }

    const employeeCallback = node => {
        const columnTitle = node.querySelector('.employee-list-title .rank');
        const memberList = node.querySelector('.employee-list');
        columnTitle.insertAdjacentHTML('beforeend', `<i class="last_action_icon right"></i>`);
        node.querySelector('.last_action_icon').addEventListener('click', () => toggleLastAction(columnTitle, memberList));
        for (const li of memberList.children) {
            const memberID = li.getAttribute('data-user');
            li.querySelector('.rank .employee-rank-drop-list').insertAdjacentHTML('afterend', `<div class="last-action hide" data-member-id="${memberID}"></div>`);
        }
    }

    const giveMeNumber = numberString => parseInt(numberString.replace(/[^0-9]/g, ''));

    const fillInput = (element, value) => {
        if(value <= 0) return;
        element.value = value;
        element.dispatchEvent(keyUpEvent); // Send keyboard event so that Torn correctly registers input.
    }

    const updateItemStocks = (item, itemName) => {
        itemsStock[itemName] = giveMeNumber(item.innerText);
        localStorage.setItem("TornCustomStock", JSON.stringify(itemsStock));
    }

    const stockCallback = node => {
        let itemList = node.querySelectorAll('.stock-list li:not(.total)'),
            items = new Map();

        for(let item of itemList) {
            let itemName = item.querySelector('.name').innerText;
            items.set(itemName, {
                input: item.querySelector(".quantity input[type=text]"),
                stock: giveMeNumber(item.querySelector('.stock').innerText),
                soldDaily: giveMeNumber(item.querySelector('.sold-daily').innerText)
            });

            let itemStock = item.querySelector('.delivery');
            itemStock.innerHTML = itemsStock[itemName] || 0;
            itemStock.contentEditable = 'true';
            itemStock.addEventListener('blur', () => updateItemStocks(itemStock, itemName));
        }

        // Add "Fill daily" button to stock form
        node.querySelector('span.order.btn-wrap.silver').insertAdjacentHTML('afterend',
                                                                            `<span class="fill-custom btn-wrap silver"><span class="btn disable" role="button">FILL CUSTOM</span></span>
                                                                             <span class="fill-daily btn-wrap silver"><span class="btn disable" role="button">FILL DAILY</span></span>`);
        // Click Handler for fill-custom
        node.querySelector('span.fill-custom').addEventListener('click', () => {
            items.forEach((item, itemName, items) => {
                let remainingStock = (itemsStock[itemName] || 0) - item.stock;
                fillInput(item.input, remainingStock);
            });
        });

        // Click Handler for fill-daily
        node.querySelector('span.fill-daily').addEventListener('click', () => {
            items.forEach((item, itemName, items) => {
                fillInput(item.input, item.soldDaily);
            });
        });

        // Hide delivered orders
        let orders = document.querySelectorAll('#stock .order-list li');
        for(let order of orders) {
            if(order.innerText.includes('Delivered')) {
                order.style.display = 'none';
            } else {
                // And add incoming orders to items map
                let orderName = order.querySelector('.name').innerText;
                let orderAmount = giveMeNumber(order.querySelector('.amount').innerText);
                items.get(orderName).stock += orderAmount;
            }
        }

        node.querySelector('.stock-list-title .delivery').innerHTML = `Total<div class="t-delimiter"></div>`;
    }

    const observe = (elementId, callback) => {
        (new MutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.tagName === 'FORM') {
                        callback(node);
                        return;
                    }
                }
            }
        }))
        .observe(document.getElementById(elementId), {
            childList: true
        });
    }

    checkTrains();
    checkEmployees();
    if(APIkey === null) {
        getAPIkey();
    } else {
        observe('employees', employeeCallback);
    }
    observe('stock', stockCallback);
})();