diff --git a/.gitignore b/.gitignore index 1250672..6e0f6d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ -dist/main.js -node_modules +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ +/.vs +/node_modules +*.js +/dist/main.js diff --git a/dist/manifest.json b/dist/manifest.json index ce109d3..42536f0 100644 --- a/dist/manifest.json +++ b/dist/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "PMMG Beautifier", "description": "Improve and tweak the interface elements of PrUn website", - "version": "0.4.1", + "version": "0.5.0", "icons": { "128": "icon128.png" }, @@ -18,5 +18,11 @@ "main.js" ] } - ] + ], + "applications": { + "gecko": { + "id": "asd@example.com", + "update_url": "https://dummy.json" + } + } } diff --git a/src/CXFX.ts b/src/CXFX.ts new file mode 100644 index 0000000..e6dbe56 --- /dev/null +++ b/src/CXFX.ts @@ -0,0 +1,125 @@ +import { Selector } from "./Selector"; +import { genericCleanup, shorten, toFixed, getBuffers } from "./util"; + +export class CXFX { + private tag = "pb-cx"; + cleanup() { + genericCleanup(this.tag); + } + run() { + var CXOBBuffers = getBuffers("CXOB"); + var FXOBBuffers = getBuffers("FXOB"); + var CXOSBuffers = getBuffers("CXOS"); + + CXOBBuffers.forEach(buffer => { + const traderNames = buffer.querySelectorAll(Selector.CXOBTable + " > tbody > tr > td > span"); + traderNames.forEach(name => { + if (name.classList.contains('_3ifriA33o8WAhdFSaIgqWi')) { + shortenCompanyName(name); + } + else { + shortenMMName(name); + } + }); + }); + + FXOBBuffers.forEach(buffer => { + const traderNames = buffer.querySelectorAll(Selector.CXOBTable + " > tbody > tr > td > span[class='_3ifriA33o8WAhdFSaIgqWi']"); + traderNames.forEach(name => { + shortenCompanyName(name); + }) + }); + + CXOSBuffers.forEach(buffer => { + workCXOSHeader(buffer, this.tag); + workCXOSRows(buffer, this.tag); + }); + } +} + +function shortenMMName(name) { + const text = name.textContent; + const matches = text && text.match(/^(NEO Charter Exploration|Antares Initiative|Insitor Cooperative|Castillo-Ito Mercantile) Market Maker$/); + if (matches) { + name.textContent = shorten(text); + } +} + +function shortenCompanyName(name) { + const text = name.textContent; + if (text!.length > 23) { + name.textContent = text!.substring(0, 23) + "\*"; + } +} + +const hideMatNameColumn: boolean = true; +const addOrderValueColumn: boolean = true; + +function workCXOSHeader(buffer, tag) { + const CXOSHeader = buffer.querySelector("thead > tr")!; + if (CXOSHeader) { + const orderAmountHeader = CXOSHeader.querySelector("th:nth-of-type(5)"); + if (orderAmountHeader.textContent) { + orderAmountHeader.textContent = "Amount \(i\)"; + } + if (hideMatNameColumn) { + const orderMatNameHeader = CXOSHeader.querySelector("th:nth-of-type(4)"); + orderMatNameHeader.style.display = "None"; + } + if (addOrderValueColumn) { + const orderStatusHeader = CXOSHeader.querySelector("th:nth-of-type(7)"); + const newHeader = document.createElement("th"); + newHeader.classList.add(tag); + newHeader.textContent = "Value"; + CXOSHeader.insertBefore(newHeader, orderStatusHeader); + } + } +} + +function workCXOSRows(buffer, tag) { + const orderRows = buffer.querySelectorAll("tbody > tr") as HTMLElement[]; + Array.from(orderRows).forEach((row) => { + const exchangeNameCell = row.querySelector("td:nth-of-type(1) > span"); + if (exchangeNameCell) { + const text = exchangeNameCell.textContent; + if (text && text.match(/Station Commodity Exchange$/)) { + exchangeNameCell.textContent = shorten(text); + } + } + if (hideMatNameColumn) { + const matNameCell = row.querySelector("td:nth-of-type(4)") as HTMLElement; + if (matNameCell) { + matNameCell.style.display = "None"; + } + } + const orderStatusCell = row.querySelector("td:nth-of-type(7)"); + if (orderStatusCell) { + const orderStatusText = orderStatusCell.querySelector("span"); + if (orderStatusText) { + orderStatusText.childNodes[0].parentElement!.removeAttribute("style"); + if (orderStatusText.textContent == "partially filled" || orderStatusText.textContent == "part fill") { + orderStatusText.textContent = "part fill"; + orderStatusText.childNodes[0].parentElement!.style.color = "#d77342"; + } + } + } + if (addOrderValueColumn) { + if (orderStatusCell!.childElementCount) { + const amount = parseInt(row.querySelector("td:nth-of-type(5)")!.childNodes[0].textContent!.replace(/[,.]/g, '')); + const unitPrice = parseInt(row.querySelector("td:nth-of-type(6)")!.childNodes[0].textContent!.replace(/[,.]/g, '')) / 100; + const type = row.querySelector("td:nth-of-type(2)")!.textContent; + const newCell = document.createElement("td"); + newCell.classList.add(tag); + newCell.textContent = toFixed(amount * unitPrice, 2); + newCell.style.textAlign = "right"; + if (type == "BUY") { + newCell.style.color = "#50c878"; + } + if (type == "SELL") { + newCell.style.color = "#d0312d"; + } + row.insertBefore(newCell, orderStatusCell); + } + } + }); +} \ No newline at end of file diff --git a/src/DB.ts b/src/DB.ts new file mode 100644 index 0000000..4ee481d --- /dev/null +++ b/src/DB.ts @@ -0,0 +1,417 @@ +export function shortenMatName(text) { + const shortTable = { + // Agricultural Products + 'All-Purpose Fodder': 'FOD', + 'Flowery Hops': 'HOP', + 'Caffeinated Beans': 'CAF', + 'High-Carb Grains': 'GRN', + 'High-Carb Maize': 'MAI', + 'Raw Cotton Fiber': 'RCO', + 'Triglyceride Nuts': 'NUT', + 'Triglyceride Fruits': 'VEG', + 'Wine-Quality Grapes': 'GRA', + 'Spicy Herbs': 'HER', + 'Hydrocarbon Plants': 'HCP', + 'Meat Tissue Patties': 'MTP', + 'Protein-Rich Mushrooms': 'MUS', + 'Pineberries': 'PIB', + 'Protein-Rich Algae': 'ALG', + 'Protein-Rich Beans': 'BEA', + 'Protein Paste': 'PPA', + 'Raw Silk Strains': 'RSI', + 'Vita Essence': 'VIT', + + // Alloys + 'Ferrominium': 'FAL', + 'Alpha-Stabilized Titanium': 'ASD', + 'Borosilicate': 'BOS', + 'Bronze': 'BRO', + 'Red Gold': 'RGO', + 'Blue Gold': 'BGO', + 'Ferro-Titanium': 'FET', + 'Alpha-Stabilized Tungsten': 'WAL', + + // Chemicals + 'Artificial Soil': 'SOI', + 'Helpful Bacteria': 'BAC', + 'Desaturation Agent': 'BLE', + 'Breathable Liquid': 'BL', + 'Chemical Reagents': 'REA', + 'Cryogenic Stabilizer': 'CST', + 'Enriched Einsteinium': 'EES', + 'Enriched Technetium': 'ETC', + 'Flux': 'FLX', + 'Indigo Colorant': 'IND', + 'Liquid Crystals': 'LCR', + 'Nano-Enhanced Resin': 'NR', + 'Nutrient Solution': 'NS', + 'Olfactory Substances': 'OLF', + 'DDT Plant Agent': 'DDT', + 'Premium Fertilizer': 'PFE', + 'Sedative Substance': 'JUI', + 'TCL Acid': 'TC', + 'ThermoFluid': 'THF', + 'Sodium Borohydride': 'NAB', + + // Construction Materials + 'Epoxy Resin': 'EPO', + 'InsuFoam': 'INS', + 'MegaTube Coating': 'MTC', + 'Mineral Construction Granulate': 'MCG', + 'Nano-Carbon Sheeting': 'NCS', + 'Nano Fiber': 'NFI', + 'Nano-Coated Glass': 'NG', + 'Reinforced Glass': 'RG', + 'Poly-Sulfite Sealant': 'SEA', + 'Glass': 'GL', + + // Construction Parts + 'Aerostat Foundation': 'AEF', + 'Air Scrubber': 'AIR', + 'Decorative Elements': 'DEC', + 'Floating Tank': 'FLO', + 'Flow Control Device': 'FC', + 'Fluid Piping': 'FLP', + 'Cylindrical Gas Container': 'GC', + 'Gas Vent': 'GV', + 'Magnetic Ground Cover': 'MGC', + 'Metal-Halide Lighting System': 'MHL', + 'Neon Lighting System': 'LIT', + 'Pressure Shielding': 'PSH', + 'Radiation Shielding': 'RSH', + 'Stabilized Technetium': 'TCS', + 'Thermal Shielding': 'TSH', + 'Truss': 'TRU', + + // Construction Prefabs + 'Advanced Bulkhead': 'ABH', + 'Advanced Deck Elements': 'ADE', + 'Advanced Structural Elements': 'ASE', + 'Advanced Transparent Aperture': 'ATA', + 'Basic Bulkhead': 'BBH', + 'Basic Deck Elements': 'BDE', + 'Basic Structural Elements': 'BSE', + 'Basic Transparent Aperture': 'BTA', + 'Hardened Structural Elements': 'HSE', + 'Lightweight Bulkhead': 'LBH', + 'Lightweight Deck Elements': 'LDE', + 'Lightweight Structural Elements': 'LSE', + 'Lightweight Transparent Aperture': 'LTA', + 'Reinforced Bulkhead': 'RBH', + 'Reinforced Deck Elements': 'RDE', + 'Reinforced Structural Elements': 'RSE', + 'Reinforced Transparent Aperture': 'RTA', + + // Consumables (basic) + 'Drinking Water': 'DW', + 'Smart Space Suit': 'HSS', + 'Flavoured Insta-Meal': 'FIM', + 'Personal Data Assistant': 'PDA', + 'Basic Overalls': 'OVE', + 'Basic Rations': 'RAT', + 'AI-Assisted Lab Coat': 'LC', + 'Quality Meat Meal': 'MEA', + 'Scientific Work Station': 'WS', + 'Exoskeleton Work Suit': 'EXO', + 'Power Tools': 'PT', + 'HazMat Work Suit': 'HMS', + 'Basic Medical Kit': 'MED', + 'Multi-Purpose Scanner': 'SCN', + + // Consumables (luxury) + 'Einsteinium-Infused Gin': 'GIN', + 'VitaGel': 'VG', + 'Padded Work Overall': 'PWO', + 'Caffeinated Infusion': 'COF', + 'Smart Zinfandel': 'WIN', + 'NeuroStimulants': 'NST', + 'Kombucha': 'KOM', + 'Repair Kit': 'REP', + 'Stellar Pale Ale': 'ALE', + 'Stem Cell Treatment': 'SC', + + // Drones + 'Crowd Control Drone': 'CCD', + 'Drone Chassis': 'DCH', + 'Drone Frame': 'DRF', + 'Rescue Drone': 'RED', + 'Ship-Repair Drone': 'SRD', + 'Surgical Drone': 'SDR', + 'Surveillance Drone': 'SUD', + + // Electronic Devices + 'Antenna Array': 'AAR', + 'Body Scanner': 'BSC', + 'Full-Body Interaction Device': 'BID', + 'Holographic Display': 'HD', + 'Holographic Glasses': 'HOG', + 'Basic Mainframe': 'BMF', + 'Micro Headphones': 'MHP', + 'Radio Device': 'RAD', + 'Sensor Array': 'SAR', + 'Handheld Personal Console': 'HPC', + 'Active Water Filter': 'AWF', + 'Basic Workstation': 'BWS', + + // Electronic Parts + 'Audio Transmitter': 'TRA', + 'Active Cooling Device': 'FAN', + 'Memory Bank': 'RAM', + 'Micro-Processor': 'MPC', + 'Motherboard': 'MB', + 'Non-Volatile Memory': 'ROM', + 'Printed Circuit Board': 'PCB', + 'Sensor': 'SEN', + 'Tensor Processing Unit': 'TPU', + 'Capacitive Display': 'CD', + 'Information Display': 'DIS', + + // Electronic Pieces + 'Shielded Connector': 'BGC', + 'Electric Field Capacitor': 'CAP', + 'Budget Connectors': 'BCO', + 'Medium Fastener Kit': 'MFK', + 'Small Fastener Kit': 'SFK', + 'Laser Diodes': 'LDI', + 'High-Capacity Connectors': 'HCC', + 'Advanced Transistor': 'TRN', + 'Medium Wafer': 'MWF', + 'Small Wafer': 'SWF', + + // Electronic Systems + 'Audio Distribution System': 'ADS', + 'Automated Cooling System': 'ACS', + 'Climate Controller': 'CC', + 'Communication System': 'COM', + 'Cryogenic Unit': 'CRU', + 'FTL Field Controller': 'FFC', + 'Life Support System': 'LIS', + 'Logistics System': 'LOG', + 'Stability Support System': 'STS', + 'Targeting Computer': 'TAC', + 'Water Reclaimer': 'WR', + + // Elements + 'Beryllium': 'BE', + 'Calcium': 'CA', + 'Carbon': 'C', + 'Chlorine': 'CL', + 'Einsteinium': 'ES', + 'Iodine': 'I', + 'Magnesium': 'MG', + 'Sodium': 'NA', + 'Sulfur': 'S', + 'Tantalum': 'TA', + 'Technetium': 'TC', + 'Zirconium': 'ZR', + + // Energy Systems + 'Large Capacitor Bank': 'CBL', + 'Medium Capacitor Bank': 'CBM', + 'Power Cell': 'POW', + 'Small Capacitor Bank': 'CBS', + 'Solar Cell': 'SOL', + 'Solar Panel': 'SP', + + // Fuels + 'FTL Fuel': 'FF', + 'STL Fuel': 'SF', + + // Gases + 'Ammonia': 'AMM', + 'Argon': 'AR', + 'Fluorine': 'F', + 'Helium': 'HE', + 'Helium-3 Isotope': 'HE3', + 'Hydrogen': 'H', + 'Neon': 'NE', + 'Nitrogen': 'N', + 'Oxygen': 'O', + + // Liquids + 'Heliotrope Extract': 'HEX', + 'Liquid Einsteinium': 'LES', + 'Bacterial Tungsten Solution': 'BTS', + 'Water': 'H2O', + + // Medical Equipment + 'Auto-Doc': 'ADR', + 'Bandages': 'BND', + 'Medical Stretcher': 'STR', + 'Painkillers': 'PK', + 'Surgical Equipment': 'SEQ', + 'Test Tubes': 'TUB', + + // Metals + 'Aluminium': 'AL', + 'Copper': 'CU', + 'Gold': 'AU', + 'Iron': 'FE', + 'Lithium': 'LI', + 'Silicon': 'SI', + 'Steel': 'STL', + 'Titanium': 'TI', + 'Tungsten': 'W', + + // Minerals + 'Beryl Crystals': 'BER', + 'Bioreactive Minerals': 'BRM', + 'Boron Crystals': 'BOR', + 'Caliche Rock': 'CLI', + 'Galerite Rock': 'GAL', + 'Halite Crystals': 'HAL', + 'Limestone': 'LST', + 'Magnesite': 'MGS', + 'Magnetite': 'MAG', + 'Sulfur Crystals': 'SCR', + 'Tantalite Rock': 'TAI', + 'Technetium Oxide': 'TCO', + 'Tectosilisite': 'TS', + 'Zircon Crystals': 'ZIR', + + // Ores + 'Aluminium Ore': 'ALO', + 'Copper Ore': 'CUO', + 'Gold Ore': 'AUO', + 'Iron Ore': 'FEO', + 'Lithium Ore': 'LIO', + 'Silicon Ore': 'SIO', + 'Titanium Ore': 'TIO', + + // Plastics + 'Durable Casing L': 'DCL', + 'Polymer Sheet Type L': 'PSL', + 'Durable Casing M': 'DCM', + 'Polymer Sheet Type M': 'PSM', + 'Poly-Ethylene': 'PE', + 'Polymer Granulate': 'PG', + 'Durable Casing S': 'DCS', + 'Polymer Sheet Type S': 'PSS', + + // Ship Engines + 'Advanced STL Engine': 'AEN', + 'Advanced Fuel Pump': 'AFP', + 'Advanced Fuel Rod': 'AFR', + 'Advanced Nozzle': 'ANZ', + 'Basic Fuel Pump': 'BFP', + 'Basic Fuel Rod': 'BFR', + 'Basic Nozzle': 'NOZ', + 'Combustion Chamber': 'CHA', + 'Fission Reactor': 'FIR', + 'Fuel-saving STL Engine': 'PSE', + 'Glass Combustion Chamber': 'GCH', + 'Glass-based STL Engine': 'GEN', + 'Glass Nozzle': 'GNZ', + 'High-power FTL Reactor': 'HPR', + 'Hyper-power Reactor': 'HYR', + 'Hyperthrust STL Engine': 'HTE', + 'Hyperthrust Nozzle': 'HNZ', + 'Large FTL Emitter': 'LFE', + 'Low-heat Fuel Pump': 'LFP', + 'Medium FTL Emitter': 'MFE', + 'Quick-charge FTL Reactor': 'QCR', + 'Radioisotope Generator': 'RAG', + 'Reactor Control System': 'RCS', + 'Small FTL Emitter': 'SFE', + 'Standard STL Engine': 'ENG', + 'Standard FTL Reactor': 'RCT', + + // Ship Kits + 'High-load Cargo Bay Kit': 'WCB', + 'High-volume Cargo Bay Kit': 'VCB', + 'Large Cargo Bay Kit': 'LCB', + 'Large FTL Fuel Tank Kit': 'LFL', + 'Large STL Fuel Tank Kit': 'LSL', + 'Medium Cargo Bay Kit': 'MCB', + 'Medium FTL Fuel Tank Kit': 'MFL', + 'Medium STL Fuel Tank Kit': 'MSL', + 'Small Cargo Bay Kit': 'SCB', + 'Small FTL Fuel Tank Kit': 'SFL', + 'Small STL Fuel Tank Kit': 'SSL', + 'Tiny Cargo Bay Kit': 'TCB', + 'Very Small Cargo Bay Kit': 'VSC', + + // Ship Parts + 'Advanced High-G Seats': 'AGS', + 'Advanced Hull Plate': 'AHP', + 'Advanced Thermal Protection Material': 'ATP', + 'Basic High-G Seats': 'BGS', + 'Basic Hull Plate': 'BHP', + 'Basic Thermal Protection Material': 'THP', + 'Hardened Hull Plate': 'HHP', + 'Lightweight Hull Plate': 'LHP', + 'Navigation Module MK1': 'NV1', + 'Navigation Module MK2': 'NV2', + 'Reinforced Hull Plate': 'RHP', + 'Structural Spacecraft Component': 'SSC', + + // Ship Shields + 'Advanced Thermal Protection Tile': 'APT', + 'Advanced Anti-rad Plate': 'ARP', + 'Advanced Whipple Shielding': 'AWH', + 'Basic Thermal Protection Tile': 'BPT', + 'Basic Anti-rad Plate': 'BRP', + 'Basic Whipple Shielding': 'BWH', + 'Specialized Anti-rad Plate': 'SRP', + + // Software Components + 'Basic AI Framework': 'BAI', + 'Local Database': 'LD', + 'Machine Learning Interface': 'MLI', + 'Networking Framework': 'NF', + 'Search Algorithm': 'SA', + 'Sorting Algorithm': 'SAL', + 'Window Manager': 'WM', + + // Software Systems + 'Information Data Core': 'IDC', + 'Information Management System': 'IMM', + 'Spatial Navigation Map': 'SNM', + 'Weak Artificial Intelligence': 'WAI', + + // Software Tools + 'Data Analyzer': 'DA', + 'Data Visualizer': 'DV', + 'Distributed Database': 'DD', + 'Entertainment Data core': 'EDC', + 'Neural Network': 'NN', + 'Operating System': 'OS', + + // Textiles + 'Ceramic Fabric': 'CF', + 'Ceramic-Tungsten Fabric': 'CTF', + 'Cotton Fabric': 'COT', + 'Kevlar Fabric': 'KV', + 'Nylon Fabric': 'NL', + 'Silken Fabric': 'SIL', + 'TechnoKevlar Fabric': 'TK', + + // Unit Prefabs + 'Command Bridge MK1': 'BR1', + 'Command Bridge MK2': 'BR2', + 'Short-distance Command Bridge': 'BRS', + 'Crew Quarters (Large)': 'CQL', + 'Crew Quarters (Medium)': 'CQM', + 'Crew Quarters (Small)': 'CQS', + 'Crew Quarters (Tiny)': 'CQT', + 'Drone Operations Unit': 'DOU', + 'Entertainment Unit': 'FUN', + 'Habitat Unit': 'HAB', + 'Handcraft Workshop Unit': 'WOR', + 'Laboratory Unit': 'LU', + 'Large Ship-Repair Drone Operations Unit': 'RDL', + 'Small Ship-Repair Drone Operations Unit': 'RDS', + 'Surgery Unit': 'SU', + 'Trauma Care Unit': 'TCU', + + // Utility + 'Office Supplies': 'OFF', + 'Safety Uniform': 'SUN', + 'Universal Toolset': 'UTS', + + // misc + 'Core Module Kit': 'CMK', + } + + return shortTable[text] || text; +} \ No newline at end of file diff --git a/src/FIN.ts b/src/FIN.ts new file mode 100644 index 0000000..b8af110 --- /dev/null +++ b/src/FIN.ts @@ -0,0 +1,17 @@ +import { shorten, genericCleanup, getBuffers} from "./util"; + +export class FIN { + private tag = "pb-fin"; + cleanup() { + genericCleanup(this.tag); + } + run() { + var FINLABuffers = getBuffers("FINLA"); + FINLABuffers.forEach(buffer => { + const finlaHeaders = Array.from(buffer.querySelectorAll("table > thead > tr > th")) as HTMLElement[]; + Array.from(finlaHeaders).forEach(element => { + element.textContent = shorten(element.textContent); + }); + }); + } +} \ No newline at end of file diff --git a/src/FlightplanETAs.ts b/src/FlightplanETAs.ts index 11ad938..0e2685f 100644 --- a/src/FlightplanETAs.ts +++ b/src/FlightplanETAs.ts @@ -1,16 +1,18 @@ import {convertDurationToETA, createTextSpan, genericCleanup} from "./util"; export class FlightplanETAs { - private tag = "pb-flightplan-eta"; - cleanup() { - genericCleanup(this.tag); - } - run() { - const elements = Array.from(document.querySelectorAll("table[class~='_2VAlxocH7EtoTdOjBzxulS']")); - elements.forEach(tbody => { - const targetRow = tbody.children[0].children[3]; - const eta = convertDurationToETA(targetRow.children[0].textContent); - targetRow.appendChild(createTextSpan(` (${eta})`, this.tag)) - }) - } + private tag = "pb-flightplan-eta"; + cleanup() { + genericCleanup(this.tag); + } + run() { + const elements = Array.from(document.querySelectorAll("tbody[class='_2VAlxocH7EtoTdOjBzxulS'] > tr")); + elements.forEach(destinationRow => { + const targetRow = destinationRow.children[3]; + if (targetRow.children[0] && targetRow.children[0].textContent) { + const eta = convertDurationToETA(targetRow.children[0].textContent); + targetRow.appendChild(createTextSpan(` (${eta})`, this.tag)) + } + }) + } } diff --git a/src/LocalMarketAds.ts b/src/LocalMarketAds.ts index afb90fe..40f2d92 100644 --- a/src/LocalMarketAds.ts +++ b/src/LocalMarketAds.ts @@ -1,25 +1,83 @@ -import {Selector} from "./Selector"; -import {createTextSpan, genericCleanup, toFixed} from "./util"; +import { Selector } from "./Selector"; +import { createTextSpan, genericCleanup, toFixed, colorizeType } from "./util"; export class LocalMarketAds { - private tag = "pb-lm-ads"; + private tag = "pb-lm-ads"; cleanup() { - genericCleanup(this.tag); + genericCleanup(this.tag); } run() { const elements = document.querySelectorAll(Selector.LMCommodityAdText); for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - const text = element.textContent; - const matches = text && text.match(/(?:BUYING|SELLING)\s(\d+)\s.*\s@\s([\d,.]+)\s[A-Z]+\sfor/); - if (matches && matches.length > 2) { - const count = parseInt(matches[1]); - const totalCents = parseInt(matches[2].replace(/[,.]/g, '')); - const perItem = toFixed(totalCents / count / 100, 2); - const priceSpan = element.querySelector(Selector.LMCommodityAdPriceSpan)!; - priceSpan.appendChild(createTextSpan(` (${perItem} ea)`, this.tag)); - } + const element = elements[i]; + const text = element.childNodes[0].textContent; + + const matches = text && text.match(/(?:BUYING|SELLING)\s*(\d+)\s(.*)\s@\s([\d,.]+)\s[A-Z]+/); + if (matches) { + const count = parseInt(matches[1]); + const totalCents = parseInt(matches[3].replace(/[,.]/g, '')); + const perItem = toFixed(totalCents / count / 100, 2); + const entry = element.querySelector(Selector.LMCommodityAdInnerText)!; + const matLongName = matches[2]; + const matShortNameStart = matLongName.indexOf('('); + let shownEntry = entry.cloneNode(true) as HTMLElement; + + const adType = entry.childNodes[0].textContent; + const priceSpan = shownEntry.querySelector(Selector.LMCommodityAdInnerText + " > span")!; + + priceSpan.appendChild(createTextSpan(` (${perItem} ea) `, this.tag)); + entry.childNodes[0].parentElement!.style.display = "None"; + shownEntry.removeAttribute("style"); + + shownEntry.classList.add(this.tag); + shownEntry.replaceChild(colorizeType(adType, this.tag)!, shownEntry.childNodes[0]); + shownEntry.childNodes[1].textContent = ` ${count} ${matLongName.substr(matShortNameStart + 1, matLongName.length - matShortNameStart - 2)} `; + shownEntry.childNodes[3].textContent = ""; + shownEntry.childNodes[4].textContent = ""; + shownEntry.childNodes[5].textContent = ""; + + entry.parentElement!.appendChild(shownEntry); + } + else { + const matchesShip = text && text.match(/(?:SHIPPING)\s*([\d.]+)t\s\/\s([\d.]+)m³\s@\s([\d,.]+)\s[A-Z]+/); + if (matchesShip) { + const totalCost = matchesShip[3]; + const tonnage = parseFloat(matchesShip[1]); + const size = parseFloat(matchesShip[2]); + var unit; + var count; + if (tonnage > size) { + unit = 't'; + count = tonnage; + } else { + unit = 'm³'; + count = size; + } + const totalCents = parseInt(totalCost.replace(/[,.]/g, '')); + const perItem = toFixed(totalCents / count / 100, 2); + const entry = element.querySelector(Selector.LMCommodityAdInnerText)!; + let shownEntry = entry.cloneNode(true) as HTMLElement; + const priceSpan = shownEntry.querySelector(Selector.LMCommodityAdInnerText + " > span")!; + + priceSpan.appendChild(createTextSpan(` (${perItem}/${unit})`, this.tag)); + + entry.childNodes[0].parentElement!.style.display = "None"; + shownEntry.removeAttribute("style"); + + shownEntry.classList.add(this.tag); + shownEntry.replaceChild(colorizeType("SHIPPING", this.tag)!, shownEntry.childNodes[0]); + shownEntry.childNodes[1].textContent = ` ` + shownEntry.childNodes[1].textContent; + shownEntry.childNodes[7].textContent = ` `; + shownEntry.childNodes[8].textContent = shownEntry.childNodes[8].textContent!.replace(/ *\([^)]*\) */g, ""); + shownEntry.childNodes[9].textContent = `->`; + shownEntry.childNodes[10].textContent = shownEntry.childNodes[10].textContent!.replace(/ *\([^)]*\) */g, " "); + shownEntry.removeChild(shownEntry.childNodes[11]); + shownEntry.style.whiteSpace = "pre-wrap"; + + entry.parentElement!.appendChild(shownEntry); + } + } } } } diff --git a/src/OrderETAs.ts b/src/OrderETAs.ts deleted file mode 100644 index d6fa1a6..0000000 --- a/src/OrderETAs.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {convertDurationToETA, createTextSpan, genericCleanup} from "./util"; - -export class OrderETAs { - private tag = "pb-order-eta"; - - cleanup() { - genericCleanup(this.tag); - } - - run() { - this.beautifyOrders(); - this.beautifyProductionQueue(); - } - - /** - * Parse all orders (PROD Screen) - * @private - */ - private beautifyOrders() { - const elements = Array.from(document.querySelectorAll("div[class~='_1a75pC9Q0YF44bObHykWIA'] div[class~='_1j-lU9fMFzEgedyKKsPDtL']")); - elements.forEach(etaDiv => { - const etaSpan = etaDiv.querySelector("span") - if (etaSpan) { - this.beautifyEta(etaSpan); - } - }); - } - - /** - * Parse all ProdQ orders - * @private - */ - private beautifyProductionQueue() { - const tables = Array.from(document.querySelectorAll("table[class~='B5JEuqpNoN-VT8jmA8g3l']")); - tables.forEach(table => { - // Select 4th row, which should contain the ETA - const rows = Array.from(table.querySelectorAll("tbody > tr")) - rows.forEach(row => { - const etaCell = row.querySelectorAll("td").item(4) - if (etaCell) { - const etaSpan = etaCell.querySelector("span") - if (etaSpan) { - this.beautifyEta(etaSpan); - } - } - }); - }); - } - private beautifyEta(etaSpan: Node){ - const eta = convertDurationToETA(etaSpan.textContent); - etaSpan.parentElement!.appendChild(createTextSpan(` (${eta})`, this.tag)); - } - -} diff --git a/src/ParseETAs.ts b/src/ParseETAs.ts index 5948438..299c38a 100644 --- a/src/ParseETAs.ts +++ b/src/ParseETAs.ts @@ -1,26 +1,24 @@ -import {convertDurationToETA, createTextSpan, genericCleanup} from "./util"; +import {convertDurationToETA, createTextSpan, genericCleanup, getBuffers} from "./util"; /** * Parse all ETA times and add the actual date-time of arrival */ export class ParseETAs { - private tag = "pb-other-etas"; - cleanup() { - genericCleanup(this.tag); - } - run() { - const elements = Array.from(document.querySelectorAll("table[class~='_38xIOphw1aA3t-LEbriQzq']")); - elements.forEach(tableElem => { - const tableRows = Array.from(tableElem.querySelectorAll("tbody > tr")); - tableRows.forEach(row => { - // 7th cell contains flight time - const etaCell = row.querySelectorAll("td").item(7); - if (etaCell.textContent) { - const textContent = etaCell.textContent.split('(')[0]; - const eta = convertDurationToETA(textContent); - etaCell.appendChild(createTextSpan(` (${eta})`, this.tag)); - } - }); - }); - } + private tag = "pb-other-etas"; + cleanup() { + genericCleanup(this.tag); + } + run() { + var FLTBuffers = getBuffers("FLT"); + FLTBuffers.forEach(buffer => { + const etaCells = Array.from(buffer.querySelectorAll("div[class='_1JqhiJ8_SwKH8PRALcO9Hc _33A_5lETf4HBqwJi_q-jhZ _1vWRpdI8cKNMPyOPnzlXgX'] > div > div > div > div > table > tbody > tr > td:nth-of-type(8)")) as HTMLElement[]; + etaCells.forEach(etaCell => { + if (etaCell.textContent) { + const textContent = etaCell.textContent.split('(')[0]; + const eta = convertDurationToETA(textContent); + etaCell.appendChild(createTextSpan(` (${eta})`, this.tag)); + } + }); + }); + } } diff --git a/src/PostLM.ts b/src/PostLM.ts index 14b50c1..2f35d35 100644 --- a/src/PostLM.ts +++ b/src/PostLM.ts @@ -14,17 +14,34 @@ export class PostLM { } run() { Array.from(document.querySelectorAll("article[class~='ftMvUGi7LmGCZmg3dJ0_f'] > div > div > form")).forEach(form => { - const amountInput = document.evaluate("div[label/span[text()='Amount']]/div/div/input", form, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue as HTMLInputElement; + const amountInput = document.evaluate("div[label/span[text()='Amount']]/div/div/div/input", form, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue as HTMLInputElement; + const totalPriceInput = document.evaluate("div[label/span[text()='Total price']]/div/div/div/input", form, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue as HTMLInputElement; - const totalPriceInput = document.evaluate("div[label/span[text()='Total price']]/div/div/input", form, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue as HTMLInputElement; - - const displayElement = createTextSpan('-- ea', this.tag); - totalPriceInput.parentNode!.insertBefore(displayElement, totalPriceInput); + const displayElement = createTextSpan('-- ea', this.tag); + totalPriceInput.parentNode!.insertBefore(displayElement, totalPriceInput); + const type = document.evaluate( + "div[label/span[text()='Type']]/div/div", + form, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null + ).singleNodeValue as HTMLElement; + const calculatePricePerUnit = () => { const amount = parseInt(amountInput.value); - const total = parseInt(totalPriceInput.value); - displayElement.textContent = `${toFixed(total / amount, 1)} ea`; + const total = parseInt(totalPriceInput.value); + const feeField = document.evaluate("div[label/span[text()='Fees']]/div/div/div/span", form, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue as HTMLSpanElement; + const postingFee = feeField ? feeField.innerText : "0"; + const postingFeeValue = parseInt(postingFee.replace(/[,.]/g, '')) / 100; + var pricePerUnitWithFee; + switch (type.innerText) { + case "BUYING": + pricePerUnitWithFee = toFixed((total + postingFeeValue) / amount, 2); + break; + case "SELLING": + pricePerUnitWithFee = toFixed((total - postingFeeValue) / amount, 2); + break; + } + const pricePerUnit = toFixed(total / amount, 2); + displayElement.textContent = `${pricePerUnit}ea \(${pricePerUnitWithFee}ea\)`; }; calculatePricePerUnit(); @@ -35,10 +52,6 @@ export class PostLM { }) // Change CMD to better reflect what we are doing - const type = document.evaluate( - "div[label/span[text()='Type']]/div/div", - form, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null - ).singleNodeValue as HTMLElement; const postButton = document.evaluate( "div/div/button[text()='post']", form, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null diff --git a/src/ProdLine.ts b/src/ProdLine.ts new file mode 100644 index 0000000..14c6809 --- /dev/null +++ b/src/ProdLine.ts @@ -0,0 +1,79 @@ +import { Selector } from "./Selector"; +import { convertParsedDurationToETA, createTextSpan, genericCleanup, parseDuration } from "./util"; + +export class ProdLine { + private tag = "pb-prod"; + + cleanup() { + genericCleanup(this.tag); + } + + run() { + this.beautifyAggregateProductionQueue(); + workProdLineColumn(); + } + + private beautifyAggregateProductionQueue() { + const prodLines = Array.from(document.querySelectorAll(Selector.ProdLineItem)); + prodLines.forEach(line => { + const prodItems = Array.from(line.querySelectorAll("div[class='_1a75pC9Q0YF44bObHykWIA']")); + var sumTimes = Array(); + prodItems.forEach(item => { + const itemETA = (item.querySelector("div[class='_1j-lU9fMFzEgedyKKsPDtL _3dW9W1Qi1zDylwVf7nNSih'] > span")); + if (itemETA && itemETA.textContent && !itemETA.textContent.match(/ago$/)) { + const progress = item.querySelector("span:nth-of-type(2)"); + const etaValue = parseDuration(itemETA.textContent); + if (etaValue > 0 && progress && progress != (item.querySelector("span[class='_29auS2ZKnkxm6ry4JazqA6 _1iuItXb2L31l1pCXU2swIX']") || item.querySelector("span[class='_1NuzUEirp5PyigpJY2QYGp _2M0A1DQeA4eEP7b3VGuxl_']"))) { // this item is already being produced, need to use the current value + if (progress == (item.querySelector("span[class='E1aHYdg2zdgvZCsPl3p9y _3RsFeLwUgZ4bFiiA1fteEe']") || item.querySelector("span[class='_2KbBUUZxADDNHtAW9ouHrP _1UD8Nq_edzxyMXDliVlb9d']"))) { + const eta = convertParsedDurationToETA(etaValue); + if (progress.parentElement && eta) { + const etaTag = createTextSpan(` (${eta})`, this.tag); + etaTag.style.position = "relative"; + etaTag.style.zIndex = "1"; + item.appendChild(etaTag); + sumTimes.push(etaValue); + } + } + } + else if (etaValue && sumTimes.length > 0) { // item is in the queue, need to find the earliest slot it can start and add it there + const lowestEta = Math.min(...sumTimes); + const summedEta = lowestEta + etaValue; + sumTimes[sumTimes.indexOf(lowestEta)] = summedEta; + const eta = convertParsedDurationToETA(summedEta); + if (item && eta) { + item.appendChild(createTextSpan(` (${eta})`, this.tag)); + } + } + } + }) + }); + } + +} + +const narrowProdLineColumn: boolean = false; + +function workProdLineColumn() { + const prodLineHeaders = Array.from(document.querySelectorAll(Selector.ProdLineHeader)); + const prodLineItems = Array.from(document.querySelectorAll(Selector.ProdLineItem)); + const prodLineDetails = Array.from(document.querySelectorAll("div[class='_3DTrsPMU_E7m4v9TrUCKI7 _2NKqmMbW69tQxqvJOvKvLL']")); + if (prodLineHeaders && prodLineItems) { + if (narrowProdLineColumn) { + prodLineHeaders.forEach(header => { + const box = header as HTMLElement; + box.classList.remove('_2NKqmMbW69tQxqvJOvKvLL'); + box.style.width = "110px"; + }) + prodLineItems.forEach(item => { + const box = item as HTMLElement; + box.classList.remove('_2NKqmMbW69tQxqvJOvKvLL'); + box.style.width = "110px"; + }) + prodLineDetails.forEach(details => { + const box = details as HTMLElement; + box.classList.remove('_2NKqmMbW69tQxqvJOvKvLL'); + box.style.width = "110px"; + }) + } + } +} \ No newline at end of file diff --git a/src/Selector.ts b/src/Selector.ts index c9269f3..b9fa173 100644 --- a/src/Selector.ts +++ b/src/Selector.ts @@ -1,4 +1,10 @@ export const Selector = { - LMCommodityAdText: "div[class~='_14L--Z4VrwQHE-Dayta1db']", - LMCommodityAdPriceSpan: "div[class~='_1owHJs3IjU2hxdT0zQ1ytB'] > span", + LMCommodityAdText: "div[class='_14L--Z4VrwQHE-Dayta1db']", + LMCommodityAdInnerText: "div[class='_1owHJs3IjU2hxdT0zQ1ytB']", + CXOBTable: "div[class='sTkR6wF4_U-Kmwu7C2eu1'] > table", + FXOBTable: "table[class='e9tTFVletB4P36C7c3_3t _2Fog1ad46aZ4q-RoEgK3R6 _1vWRpdI8cKNMPyOPnzlXgX _33A_5lETf4HBqwJi_q-jhZ']", + CXOrdersExchangeName: "span[class='_3ifriA33o8WAhdFSaIgqWi']", + CXOrdersTable: "div[class='_2-M8WlI-JS7ws_xSL-0yYo'] > div[class='_1h7jHHAYnTmdWfZvSkS4bo'] > div:nth-of-type(3)[class='_1JqhiJ8_SwKH8PRALcO9Hc _33A_5lETf4HBqwJi_q-jhZ _1vWRpdI8cKNMPyOPnzlXgX'] > div[class='shbi17MHX7PpkUzoWVbWm'] > div > div[class='sTkR6wF4_U-Kmwu7C2eu1'] > div > table", + ProdLineHeader: "div[class='_11WSNCrU67r9BAS8vuYINW _2NKqmMbW69tQxqvJOvKvLL']", + ProdLineItem: "div[class~='z8O6A0dWYid_6Vb1y75qz']", } diff --git a/src/ShippingAds.ts b/src/ShippingAds.ts index 08a8b28..99afc62 100644 --- a/src/ShippingAds.ts +++ b/src/ShippingAds.ts @@ -1,5 +1,5 @@ import {Selector} from "./Selector"; -import {createTextSpan, genericCleanup, toFixed} from "./util"; +import {createTextSpan, genericCleanup, toFixed, colorizeType} from "./util"; export class ShippingAds { private tag = "pb-shipping-ads"; @@ -12,9 +12,8 @@ export class ShippingAds { for (let i = 0; i < elements.length; i++) { const element = elements[i]; const text = element.textContent; - const matches = text && text.match(/(?:SHIPPING)\s([\d.]+)t\s\/\s([\d.]+)m³\s@\s([\d,.]+)\s[A-Z]+\sfrom/); - - if (matches && matches.length > 3) { + const matches = text && text.match(/(?:SHIPPING)\s*([\d.]+)t\s\/\s([\d.]+)m³\s@\s([\d,.]+)\s[A-Z]+/); + if (matches) { const totalCost = matches[3]; const tonnage = parseFloat(matches[1]); const size = parseFloat(matches[2]); @@ -27,12 +26,28 @@ export class ShippingAds { unit = 'm³'; count = size; } - const totalCents = parseInt(totalCost.replace(/[,.]/g, '')); const perItem = toFixed(totalCents / count / 100, 2); - const priceSpan = element.querySelector(Selector.LMCommodityAdPriceSpan)!; - priceSpan.appendChild(createTextSpan(` (${perItem}/${unit})`, this.tag)); - } + const entry = element.querySelector(Selector.LMCommodityAdInnerText)!; + let shownEntry = entry.cloneNode(true) as HTMLElement; + const priceSpan = shownEntry.querySelector(Selector.LMCommodityAdInnerText + " > span")!; + + priceSpan.appendChild(createTextSpan(` (${perItem}/${unit})`, this.tag)); + + entry.childNodes[0].parentElement!.style.display = "None"; + shownEntry.removeAttribute("style"); + + shownEntry.classList.add(this.tag); + shownEntry.replaceChild(colorizeType("SHIPPING", this.tag)!, shownEntry.childNodes[0]); + shownEntry.childNodes[1].textContent = ` ` + shownEntry.childNodes[1].textContent; + shownEntry.childNodes[6].textContent = ` `; + shownEntry.childNodes[7].textContent = shownEntry.childNodes[7].textContent!.replace(/ *\([^)]*\) */g, ""); + shownEntry.childNodes[8].textContent = `->`; + shownEntry.childNodes[9].textContent = shownEntry.childNodes[9].textContent!.replace(/ *\([^)]*\) */g, " "); + shownEntry.removeChild(shownEntry.childNodes[10]); + shownEntry.style.whiteSpace = "pre-wrap"; + entry.parentElement!.appendChild(shownEntry); + } } } } diff --git a/src/main.ts b/src/main.ts index 269850e..02bb89c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,20 +1,22 @@ import { FlightplanETAs } from "./FlightplanETAs"; import { LocalMarketAds } from './LocalMarketAds'; import { ModuleRunner } from "./ModuleRunner"; -import { OrderETAs } from "./OrderETAs"; +import { ProdLine } from "./ProdLine"; import { ParseETAs } from "./ParseETAs"; import { PostLM } from "./PostLM"; -import { ShippingAds } from "./ShippingAds"; import { QueueLoad } from "./QueueLoad"; +import { CXFX } from "./CXFX"; +import { FIN } from "./FIN"; const runner = new ModuleRunner([ - new LocalMarketAds(), - new ParseETAs(), - new OrderETAs(), - new FlightplanETAs(), - new ShippingAds(), - new PostLM(), - new QueueLoad(), + new LocalMarketAds(), + new ParseETAs(), + new ProdLine(), + new FlightplanETAs(), + new PostLM(), + new QueueLoad(), + new CXFX(), + new FIN(), ]); (function () { runner.loop() diff --git a/src/util.ts b/src/util.ts index 3d757ae..5acfb3e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -4,20 +4,21 @@ * @returns {string} */ export function convertDurationToETA(duration) { - const parsedSeconds = parseDuration(duration); - const eta = new Date(); - const now = new Date(); - eta.setSeconds(eta.getSeconds() + parsedSeconds); - const diffTime = Math.abs(eta.getTime() - now.getTime()); - const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + const parsedSeconds = parseDuration(duration); + const eta = new Date(); + + eta.setSeconds(eta.getSeconds() + parsedSeconds - eta.getTimezoneOffset() * 60); - let ret = eta.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}); - if (diffDays > 0) { - ret += ` +${diffDays}d`; - } + let ret = eta.toISOString().substr(5, 11).replace("-", ".").replace("T", ". "); return ret; } +export function convertParsedDurationToETA(parsedSeconds) { + const eta = new Date(); + eta.setSeconds(eta.getSeconds() + parsedSeconds - eta.getTimezoneOffset()*60); + return eta.toISOString().substr(5, 11).replace("-", ".").replace("T", ". "); +} + /** * Parse duration into seconds * @param duration string @@ -61,11 +62,71 @@ export function createTextSpan(text, className: string = "prun-remove-js") { export function genericCleanup(className: string = "prun-remove-js") { // remove all elements added in the last run Array.from(document.getElementsByClassName(className)).forEach((elem) => { - elem.parentNode && elem.parentNode.removeChild(elem); + elem && elem.parentNode && elem.parentNode.removeChild(elem); }); } export function toFixed(value: number, precision: number = 2) { - const power = Math.pow(10, precision || 0); - return Math.round(value * power) / power; + const power = Math.pow(10, precision || 0); + const number = Math.round(value * power) / power; + + return number.toLocaleString('en-GB', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +} + +export function shorten(text) { + const shortTable = { + "NEO Charter Exploration Market Maker": "NEO Charter MM", + "Antares Initiative Market Maker": "Antares MM", + "Insitor Cooperative Market Maker": "Insitor MM", + "Castillo-Ito Mercantile Market Maker": "Castillo-Ito MM", + "Station Commodity Exchange": "CX", + "Currency": "Cur", + "Amount": "Amt", + } + + var re = new RegExp(Object.keys(shortTable).join("|"), "g"); + return text.replace(re, function (matched: string) { + return shortTable[matched]; + }); } + +export function colorizeType(type, tag) { + switch (type) { + case "BUYING": { + const typeNode = createTextSpan("BUY", tag); + FontColor(60, 179, 113, typeNode); + return typeNode; + } + case "SELLING": { + const typeNode = createTextSpan("SEL", tag); + FontColor(178, 34, 34, typeNode); + return typeNode; + } + case "SHIPPING": { + + const typeNode = createTextSpan("SHI", tag); + FontColor(79, 130, 180, typeNode); + return typeNode; + } + } +} + +function FontColor(r, g, b, textHolder) { + textHolder.style.color = "rgb(" + r + "," + g + "," + b + ")"; + textHolder.style.fontFamily = "courier"; + textHolder.style.fontSize = "90%"; + textHolder.style.fontWeight = "600"; +} + +export function getBuffers(cmd) { + var xpath = `//div[@class='_1OuLU0zrb4Lq9rlap4OCdX _10F2njzEHtebAJEUV-KJJy'][starts-with(translate(., 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), "${cmd}")]`; + var matchingElements = document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); + var buffers = Array(); + for (var i = 0; i < matchingElements.snapshotLength; i++) { + var bufferItem = matchingElements.snapshotItem(i); + if (bufferItem && bufferItem.parentElement) { + buffers.push(bufferItem.parentElement.parentElement!); + } + } + return buffers; +} \ No newline at end of file