Company Enhancement Suite

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