您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Your one stop solution to managing your company.
// ==UserScript== // @name Company Enhancement Suite // @namespace LordBusiness.CES // @version 3.4.8 // @description Your one stop solution to managing your company. // @author LordBusiness // @match *.torn.com/companies.php // @run-at document-end // @license MIT // @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 to stay safe from the jQuerypocalypse. */ 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"> <a href="/preferences.php#tab=api" target="_blank" title="Fill with your correct API key" class="input-money-symbol">KEY</a><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> <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.`; // This reload is allowed. IceBlueFire: That's fine (Wed, 12:47 - TCT 05/06/19) 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 [1976582]'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 => { if(response.error) { console.log(response.error.error) if(response.error.code == 2) getAPIkey() } 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" title="Toggle Last Action"></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>`); } } /* So why did I decide to rewrite this old, obselete script? Good question. When I saw Mathiaas [1918010]'s Stock Order script, I thought to myself, "Hey, I've made a script just like this! Wonder what happened to that." And thus, I started working on this one. You can start crying now */ 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); })();