Company Enhancement Suite

Your one stop solution to managing your company.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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);
})();