MediaWiki:Gadget-livePricesMMG-core.js: Difference between revisions
Jump to navigation
Jump to search
Content added Content deleted
(Created page with "// // TODO: Do I just do something kind of dumb like use an object here for constant lookup? EXEMPT_FROM_TAX = [ 'Old school bond', 'Chisel', 'Gardening trowel', 'Glassblowing pipe', 'Hammer', 'Needle', 'Pestle and mortar', 'Rake', 'Saw', 'Secateurs', 'Seed dibber', 'Shears', 'Spade', 'Watering can (0)' ]; // Tax is never higher than 5m per item MAX_TAX_AMOUNT = 5000000; MMG_SMW_DATA_ENDPOINT = "https://oldschool.runescape.wiki/api.php?acti...") |
No edit summary |
||
Line 1: | Line 1: | ||
"use strict"; |
|||
// |
// |
||
// TODO: Do I just do something kind of dumb like use an object here for constant lookup? |
// TODO: Do I just do something kind of dumb like use an object here for constant lookup? |
||
EXEMPT_FROM_TAX = ['Old school bond', 'Chisel', 'Gardening trowel', 'Glassblowing pipe', 'Hammer', 'Needle', 'Pestle and mortar', 'Rake', 'Saw', 'Secateurs', 'Seed dibber', 'Shears', 'Spade', 'Watering can (0)']; |
|||
EXEMPT_FROM_TAX = [ |
|||
'Old school bond', |
|||
'Chisel', |
|||
'Gardening trowel', |
|||
'Glassblowing pipe', |
|||
'Hammer', |
|||
'Needle', |
|||
'Pestle and mortar', |
|||
'Rake', |
|||
'Saw', |
|||
'Secateurs', |
|||
'Seed dibber', |
|||
'Shears', |
|||
'Spade', |
|||
'Watering can (0)' |
|||
]; |
|||
// Tax is never higher than 5m per item |
// Tax is never higher than 5m per item |
||
MAX_TAX_AMOUNT = 5000000; |
MAX_TAX_AMOUNT = 5000000; |
||
MMG_SMW_DATA_ENDPOINT = "https://oldschool.runescape.wiki/api.php?action=ask&query=[[MMG%20JSON::%2B]]%7C%3FMMG%20JSON%7Climit=10000&format=json"; |
|||
MMG_SMW_DATA_ENDPOINT = "https://oldschool.runescape.wiki/api.php?action=ask&query=[[MMG%20JSON::%2B]]%7C%3FMMG%20JSON%7Climit=10000&format=json" |
|||
MAPPING_ENDPOINT = 'https://prices.runescape.wiki/api/v1/osrs/mapping'; |
MAPPING_ENDPOINT = 'https://prices.runescape.wiki/api/v1/osrs/mapping'; |
||
ALLOWED_GRANULARITIES = ['latest', '5m', '1h', '6h', '24h']; |
ALLOWED_GRANULARITIES = ['latest', '5m', '1h', '6h', '24h']; |
||
SHOULD_TAX_ON_BUY = false; |
SHOULD_TAX_ON_BUY = false; |
||
SHOULD_TAX_ON_SELL = true; |
SHOULD_TAX_ON_SELL = true; |
||
mmgJs = { |
mmgJs = { |
||
init: function () { |
init: function init() { |
||
mmgJs.livePrices = {}; |
mmgJs.livePrices = {}; |
||
mmgJs.officialPrices = {}; |
mmgJs.officialPrices = {}; |
||
Line 44: | Line 28: | ||
mmgJs.loadMMGData(); |
mmgJs.loadMMGData(); |
||
}, |
}, |
||
initButtons: function initButtons() { |
|||
initButtons: function () { |
|||
// Set up the ButtonSelectWidget that toggles between live and official prices |
// Set up the ButtonSelectWidget that toggles between live and official prices |
||
var buttonDiv = document.querySelector('.mmg-list-table-buttons'); |
var buttonDiv = document.querySelector('.mmg-list-table-buttons'); |
||
var officialPricesButton = new OO.ui.ButtonOptionWidget({ |
var officialPricesButton = new OO.ui.ButtonOptionWidget({ |
||
title: 'Official Prices', |
title: 'Official Prices', |
||
label: 'Official Prices', |
label: 'Official Prices', |
||
data: mmgJs.updateTableOfficialPrices |
data: mmgJs.updateTableOfficialPrices |
||
}) |
}); |
||
var livePricesButton = new OO.ui.ButtonOptionWidget({ |
var livePricesButton = new OO.ui.ButtonOptionWidget({ |
||
title: 'Live Prices', |
title: 'Live Prices', |
||
label: 'Live Prices', |
label: 'Live Prices', |
||
data: function() { |
data: function data() { |
||
mmgJs.initUpdateTableLivePrices('24h'); |
|||
}) |
|||
} |
|||
mmgJs.buttonSelect = new OO.ui.ButtonSelectWidget({ items: [officialPricesButton, livePricesButton] }) |
|||
}); |
|||
mmgJs.buttonSelect.on('select', function(item) { item.data() }) |
|||
mmgJs.buttonSelect = new OO.ui.ButtonSelectWidget({ |
|||
items: [officialPricesButton, livePricesButton] |
|||
}); |
|||
mmgJs.buttonSelect.on('select', function (item) { |
|||
item.data(); |
|||
}); |
|||
$(buttonDiv).append(mmgJs.buttonSelect.$element); |
|||
}, |
}, |
||
// Build a map of MMG name -> tr for access later |
// Build a map of MMG name -> tr for access later |
||
indexTableRows: function () { |
indexTableRows: function indexTableRows() { |
||
var table = document.querySelector('.mmg-list-table') |
var table = document.querySelector('.mmg-list-table'); |
||
var trs = table.getElementsByClassName('mmg-list-table-row'); |
var trs = table.getElementsByClassName('mmg-list-table-row'); |
||
mmgJs.trsIndexedByName = {} |
mmgJs.trsIndexedByName = {}; |
||
for (var i = 0; i < trs.length; i++) { |
for (var i = 0; i < trs.length; i++) { |
||
var tr = trs[i]; |
var tr = trs[i]; |
||
Line 74: | Line 61: | ||
} |
} |
||
}, |
}, |
||
getTrMmgName: function getTrMmgName(thisTr) { |
|||
getTrMmgName: function (thisTr) { |
|||
return thisTr.querySelector('a').title; |
return thisTr.querySelector('a').title; |
||
}, |
}, |
||
loadMapping: function loadMapping(callback) { |
|||
loadMapping: function (callback) { |
|||
// Get the live prices mapping data since we will need it to match names and ids (is there a better place to look?) |
// Get the live prices mapping data since we will need it to match names and ids (is there a better place to look?) |
||
if (mmgJs.mapping) { |
if (mmgJs.mapping) { |
||
callback(); |
callback(); |
||
} |
} else { |
||
else { |
|||
$.ajax({ |
$.ajax({ |
||
type: "GET", |
type: "GET", |
||
url: MAPPING_ENDPOINT, |
url: MAPPING_ENDPOINT, |
||
dataType: "json", |
dataType: "json", |
||
success: function (msg) { |
success: function success(msg) { |
||
mmgJs.mapping = {}; |
mmgJs.mapping = {}; |
||
for (var index in msg) { |
for (var index in msg) { |
||
Line 95: | Line 79: | ||
} |
} |
||
}, |
}, |
||
error: function (req) { |
error: function error(req) { |
||
console.log('ERROR: Mapping endpoint failed') |
console.log('ERROR: Mapping endpoint failed'); |
||
} |
} |
||
}).done(callback); |
}).done(callback); |
||
} |
} |
||
}, |
}, |
||
loadLivePrices: function loadLivePrices(granularity) { |
|||
loadLivePrices: function (granularity) { |
|||
// Check for mapping |
// Check for mapping |
||
var endpoint = "https://prices.runescape.wiki/api/v1/osrs/" + granularity |
var endpoint = "https://prices.runescape.wiki/api/v1/osrs/" + granularity; |
||
// Get the live prices data |
// Get the live prices data |
||
$.ajax({ |
$.ajax({ |
||
Line 110: | Line 93: | ||
url: endpoint, |
url: endpoint, |
||
dataType: "json", |
dataType: "json", |
||
indexValue: { |
indexValue: { |
||
granularity: granularity |
|||
success: function (msg) { |
|||
}, |
|||
success: function success(msg) { |
|||
mmgJs.livePrices[this.indexValue.granularity] = {}; |
mmgJs.livePrices[this.indexValue.granularity] = {}; |
||
for (var key in msg['data']) { |
for (var key in msg['data']) { |
||
Line 118: | Line 103: | ||
continue; |
continue; |
||
} |
} |
||
var itemName = mmgJs.mapping[key]['name'] |
var itemName = mmgJs.mapping[key]['name']; |
||
mmgJs.livePrices[this.indexValue.granularity][itemName] = msg['data'][key] |
mmgJs.livePrices[this.indexValue.granularity][itemName] = msg['data'][key]; |
||
} |
} |
||
mmgJs.updateTableLivePrices(granularity); |
mmgJs.updateTableLivePrices(granularity); |
||
}, |
}, |
||
error: function (req) { |
error: function error(req) { |
||
onFailure() |
onFailure(); |
||
} |
} |
||
}); |
}); |
||
}, |
}, |
||
loadMMGData: function loadMMGData() { |
|||
loadMMGData: function () { |
|||
// Gets the MMG data via SMW. This should be called once per run. |
// Gets the MMG data via SMW. This should be called once per run. |
||
$.ajax({ |
$.ajax({ |
||
Line 135: | Line 119: | ||
url: MMG_SMW_DATA_ENDPOINT, |
url: MMG_SMW_DATA_ENDPOINT, |
||
dataType: "json", |
dataType: "json", |
||
success: function (msg) { |
success: function success(msg) { |
||
mmgJs.mmgData = msg; |
mmgJs.mmgData = msg; |
||
mmgJs.results = msg['query']['results']; |
mmgJs.results = msg['query']['results']; |
||
Line 144: | Line 128: | ||
mmgJs.buttonSelect.selectItemByLabel('Live Prices'); |
mmgJs.buttonSelect.selectItemByLabel('Live Prices'); |
||
}, |
}, |
||
error: function (req) { |
error: function error(req) { |
||
console.log('ERROR: MMG SMW call failed...Aborting') |
console.log('ERROR: MMG SMW call failed...Aborting'); |
||
} |
} |
||
}); |
}); |
||
}, |
}, |
||
updateTableOfficialPrices: function updateTableOfficialPrices() { |
|||
updateTableOfficialPrices: function () { |
|||
// We only get one price with the official prices so we have to assume buying and selling at that price |
// We only get one price with the official prices so we have to assume buying and selling at that price |
||
mmgJs.buyingPriceMap = mmgJs.officialPrices; |
mmgJs.buyingPriceMap = mmgJs.officialPrices; |
||
Line 156: | Line 139: | ||
mmgJs.createList(); |
mmgJs.createList(); |
||
}, |
}, |
||
storeOfficialPrices: function storeOfficialPrices(mmgData) { |
|||
storeOfficialPrices: function (mmgData) { |
|||
// TODO: There's probably a smarter way to get these values, but this is likely not our bottleneck anyway |
// TODO: There's probably a smarter way to get these values, but this is likely not our bottleneck anyway |
||
mmgJs.officialPrices = {} |
mmgJs.officialPrices = {}; |
||
for (var mmg in mmgData['query']['results']) { |
for (var mmg in mmgData['query']['results']) { |
||
var d = mmgData['query']['results'][mmg]['printouts']['MMG JSON'][0]; |
var d = mmgData['query']['results'][mmg]['printouts']['MMG JSON'][0]; |
||
Line 166: | Line 148: | ||
var inputs = parsedData['inputs']; |
var inputs = parsedData['inputs']; |
||
var outputs = parsedData['outputs']; |
var outputs = parsedData['outputs']; |
||
for (var index in inputs) { |
for (var index in inputs) { |
||
// TODO: Is there something idiomatic in old js? I think this is for-of now |
// TODO: Is there something idiomatic in old js? I think this is for-of now |
||
var item = inputs[index]; |
var item = inputs[index]; |
||
if (item['pricetype'] == 'gemw') { |
if (item['pricetype'] == 'gemw') { |
||
mmgJs.officialPrices[item['name']] = item['value'] |
mmgJs.officialPrices[item['name']] = item['value']; |
||
} |
} |
||
} |
} |
||
Line 178: | Line 159: | ||
var item = outputs[index]; |
var item = outputs[index]; |
||
if (item['pricetype'] == 'gemw') { |
if (item['pricetype'] == 'gemw') { |
||
mmgJs.officialPrices[item['name']] = item['value'] |
mmgJs.officialPrices[item['name']] = item['value']; |
||
} |
} |
||
} |
} |
||
} |
} |
||
}, |
}, |
||
storeMMGIO: function storeMMGIO() { |
|||
storeMMGIO: function () { |
|||
mmgJs.parsedResults = {}; |
mmgJs.parsedResults = {}; |
||
for (var mmg in mmgJs.results) { |
for (var mmg in mmgJs.results) { |
||
Line 192: | Line 171: | ||
} |
} |
||
}, |
}, |
||
initUpdateTableLivePrices: function initUpdateTableLivePrices(granularity) { |
|||
initUpdateTableLivePrices: function (granularity) { |
|||
// Build a price list based on what we asked for |
// Build a price list based on what we asked for |
||
if (!ALLOWED_GRANULARITIES.includes(granularity)) { |
if (!ALLOWED_GRANULARITIES.includes(granularity)) { |
||
Line 200: | Line 178: | ||
// We will call updateTableLivePrices via a callback in loadLivePrices if we don't already have the prices |
// We will call updateTableLivePrices via a callback in loadLivePrices if we don't already have the prices |
||
if (mmgJs.livePrices[granularity] === undefined) { |
if (mmgJs.livePrices[granularity] === undefined) { |
||
mmgJs.loadMapping(function () { |
mmgJs.loadMapping(function () { |
||
mmgJs.loadLivePrices(granularity); |
|||
} |
|||
}); |
|||
} else { |
|||
mmgJs.updateTableLivePrices(granularity); |
mmgJs.updateTableLivePrices(granularity); |
||
} |
} |
||
}, |
}, |
||
updateTableLivePrices: function updateTableLivePrices(granularity) { |
|||
updateTableLivePrices: function (granularity) { |
|||
var granularityMap = mmgJs.livePrices[granularity]; |
var granularityMap = mmgJs.livePrices[granularity]; |
||
mmgJs.buyingPriceMap = {}; |
mmgJs.buyingPriceMap = {}; |
||
Line 216: | Line 193: | ||
mmgJs.buyingPriceMap[itemName] = granularityMap[itemName]['high']; |
mmgJs.buyingPriceMap[itemName] = granularityMap[itemName]['high']; |
||
mmgJs.sellingPriceMap[itemName] = granularityMap[itemName]['low']; |
mmgJs.sellingPriceMap[itemName] = granularityMap[itemName]['low']; |
||
} |
} else { |
||
else { |
|||
mmgJs.buyingPriceMap[itemName] = granularityMap[itemName]['avgHighPrice']; |
mmgJs.buyingPriceMap[itemName] = granularityMap[itemName]['avgHighPrice']; |
||
mmgJs.sellingPriceMap[itemName] = granularityMap[itemName]['avgLowPrice']; |
mmgJs.sellingPriceMap[itemName] = granularityMap[itemName]['avgLowPrice']; |
||
Line 224: | Line 200: | ||
mmgJs.createList(); |
mmgJs.createList(); |
||
}, |
}, |
||
createList: function createList() { |
|||
createList: function () { |
|||
for (var mmg in mmgJs.parsedResults) { |
for (var mmg in mmgJs.parsedResults) { |
||
var mmgName = mmgJs.results[mmg]['fulltext']; |
|||
var mmgName = mmgJs.results[mmg]['fulltext'] |
|||
// Identify what row this is |
// Identify what row this is |
||
Line 237: | Line 210: | ||
continue; |
continue; |
||
} |
} |
||
var numActions = mmgJs.getKphLocalStorage(mmgName) || mmgJs.parsedResults[mmg]['prices']['default_kph'] |
var numActions = mmgJs.getKphLocalStorage(mmgName) || mmgJs.parsedResults[mmg]['prices']['default_kph']; |
||
if (thisTr.querySelector('.mmg-kph-selector') === null) { |
if (thisTr.querySelector('.mmg-kph-selector') === null) { |
||
mmgJs.addCellToggle(thisTr, numActions); |
mmgJs.addCellToggle(thisTr, numActions); |
||
Line 246: | Line 219: | ||
} |
} |
||
}, |
}, |
||
updateTableRow: function updateTableRow(thisTr, numActions) { |
|||
updateTableRow: function (thisTr, numActions) { |
|||
//console.log('Updating row with numActions = ' + numActions); |
//console.log('Updating row with numActions = ' + numActions); |
||
Line 258: | Line 230: | ||
// Add the correct class to the table cell |
// Add the correct class to the table cell |
||
if (profit > 0) { |
if (profit > 0) { |
||
profitCell.classList.remove('coins-neg') |
profitCell.classList.remove('coins-neg'); |
||
profitCell.classList.add('coins-pos') |
profitCell.classList.add('coins-pos'); |
||
} else if (profit < 0) { |
|||
profitCell.classList.remove('coins-pos'); |
|||
profitCell.classList.add('coins-neg'); |
|||
} else { |
|||
profitCell.classList.remove('coins-pos'); |
|||
profitCell.classList.remove('coins-neg'); |
|||
} |
} |
||
else if (profit < 0) { |
|||
profitCell.classList.remove('coins-pos') |
|||
profitCell.classList.add('coins-neg') |
|||
} |
|||
else { |
|||
profitCell.classList.remove('coins-pos') |
|||
profitCell.classList.remove('coins-neg') |
|||
} |
|||
profitCell.innerHTML = profit.toLocaleString(); |
profitCell.innerHTML = profit.toLocaleString(); |
||
}, |
}, |
||
addCellToggle: function addCellToggle(thisTr, valueToUse) { |
|||
addCellToggle: function (thisTr, valueToUse) { |
|||
var mmgName = mmgJs.getTrMmgName(thisTr); |
var mmgName = mmgJs.getTrMmgName(thisTr); |
||
var kphCell = thisTr.querySelector('.mmg-list-table-kph-cell'); |
var kphCell = thisTr.querySelector('.mmg-list-table-kph-cell'); |
||
var kphField = new OO.ui.NumberInputWidget({ |
var kphField = new OO.ui.NumberInputWidget({ |
||
min: 0, |
min: 0, |
||
input: { |
input: { |
||
value: valueToUse |
|||
}, |
|||
classes: ['mmg-kph-selector'], |
classes: ['mmg-kph-selector'], |
||
showButtons: false, |
showButtons: false, |
||
title: mmgJs.parsedResults[mmgName]['prices']['kph_text'] || 'Kills per hour' |
title: mmgJs.parsedResults[mmgName]['prices']['kph_text'] || 'Kills per hour' |
||
}); |
}); |
||
function updateThisRow() { |
function updateThisRow() { |
||
mmgJs.setKphLocalStorage(mmgName, kphField.getNumericValue()); |
mmgJs.setKphLocalStorage(mmgName, kphField.getNumericValue()); |
||
return mmgJs.updateTableRow(thisTr, kphField.getNumericValue()); |
return mmgJs.updateTableRow(thisTr, kphField.getNumericValue()); |
||
} |
} |
||
kphField.on('change', updateThisRow); |
kphField.on('change', updateThisRow); |
||
var resetButton = new OO.ui.ButtonWidget({ |
var resetButton = new OO.ui.ButtonWidget({ |
||
icon: 'reload', |
icon: 'reload', |
||
Line 303: | Line 268: | ||
kphField.setValue(mmgJs.parsedResults[mmgName]['prices']['default_kph']); |
kphField.setValue(mmgJs.parsedResults[mmgName]['prices']['default_kph']); |
||
mmgJs.resetKphLocalStorage(mmgName); |
mmgJs.resetKphLocalStorage(mmgName); |
||
}) |
}); |
||
var layout = new OO.ui.ActionFieldLayout(kphField, resetButton, { |
var layout = new OO.ui.ActionFieldLayout(kphField, resetButton, { |
||
classes: ['mmg-kph-selector-field'] |
classes: ['mmg-kph-selector-field'] |
||
}); |
}); |
||
kphCell.innerHTML = ''; |
kphCell.innerHTML = ''; |
||
$(kphCell).append(layout.$element); |
$(kphCell).append(layout.$element); |
||
}, |
}, |
||
calculateProfit: function calculateProfit(mmg, buyingPriceMap, sellingPriceMap, numActions) { |
|||
calculateProfit: function (mmg, buyingPriceMap, sellingPriceMap, numActions) { |
|||
var inputMap = mmgJs.parsedResults[mmg]['inputs']; |
var inputMap = mmgJs.parsedResults[mmg]['inputs']; |
||
var outputMap = mmgJs.parsedResults[mmg]['outputs']; |
var outputMap = mmgJs.parsedResults[mmg]['outputs']; |
||
var inputAmount = mmgJs.calculateValue(inputMap, buyingPriceMap, numActions, SHOULD_TAX_ON_BUY); |
var inputAmount = mmgJs.calculateValue(inputMap, buyingPriceMap, numActions, SHOULD_TAX_ON_BUY); |
||
var outputAmount = mmgJs.calculateValue(outputMap, sellingPriceMap, numActions, SHOULD_TAX_ON_SELL); |
var outputAmount = mmgJs.calculateValue(outputMap, sellingPriceMap, numActions, SHOULD_TAX_ON_SELL); |
||
return Math.floor(outputAmount - inputAmount); |
return Math.floor(outputAmount - inputAmount); |
||
}, |
}, |
||
getItemPrice: function getItemPrice(item, givenPrice) { |
|||
getItemPrice: function (item, givenPrice) { |
|||
// If the item does not use GE prices, always return the price as is |
// If the item does not use GE prices, always return the price as is |
||
if (item['pricetype'] != 'gemw') |
if (item['pricetype'] != 'gemw') return item['value']; |
||
if (givenPrice === undefined || givenPrice === null) { |
|||
console.log('WARNING: This item has no price in the price map you gave me! ' + item['name']); |
|||
return item['value']; |
return item['value']; |
||
if (givenPrice === undefined || givenPrice === null) { |
|||
console.log('WARNING: This item has no price in the price map you gave me! ' + item['name']) |
|||
return item['value'] |
|||
} |
} |
||
return givenPrice; |
return givenPrice; |
||
}, |
}, |
||
calculateValue: function calculateValue(itemAmountMap, priceMap, numActions, useTax) { |
|||
calculateValue: function (itemAmountMap, priceMap, numActions, useTax) { |
|||
var sum = 0; |
var sum = 0; |
||
for (var index in itemAmountMap) { |
for (var index in itemAmountMap) { |
||
Line 340: | Line 298: | ||
// For each item, determine the value |
// For each item, determine the value |
||
// console.log("Item: " + JSON.stringify(item)); |
// console.log("Item: " + JSON.stringify(item)); |
||
// Stub this out to get the correct price |
// Stub this out to get the correct price |
||
Line 352: | Line 309: | ||
if (item['isph']) { |
if (item['isph']) { |
||
numberUsed = quantity; |
numberUsed = quantity; |
||
} |
} else { |
||
else { |
|||
numberUsed = quantity * numActions; |
numberUsed = quantity * numActions; |
||
} |
} |
||
if (useTax) { |
if (useTax) { |
||
// Subtract tax since it is per item and not per txn |
// Subtract tax since it is per item and not per txn |
||
sum += numberUsed * mmgJs.applyTax(item['name'], value) |
sum += numberUsed * mmgJs.applyTax(item['name'], value); |
||
} |
} else { |
||
else { |
|||
sum += numberUsed * value; |
sum += numberUsed * value; |
||
} |
} |
||
Line 367: | Line 321: | ||
return sum; |
return sum; |
||
}, |
}, |
||
applyTax: function applyTax(itemName, price) { |
|||
if (EXEMPT_FROM_TAX.includes(itemName)) return price; |
|||
applyTax: function (itemName, price) { |
|||
if (EXEMPT_FROM_TAX.includes(itemName)) |
|||
return price; |
|||
return price - Math.min(Math.floor(price / 100), MAX_TAX_AMOUNT); |
return price - Math.min(Math.floor(price / 100), MAX_TAX_AMOUNT); |
||
}, |
}, |
||
/** |
/** |
||
* LocalStorage helper methods to retrieve and set values |
* LocalStorage helper methods to retrieve and set values |
||
*/ |
*/ |
||
getLSKeyNameForMmg: function (mmgName) { |
getLSKeyNameForMmg: function getLSKeyNameForMmg(mmgName) { |
||
// mmgName should always have a "Money making guide/" prefix |
// mmgName should always have a "Money making guide/" prefix |
||
// This will work for anything that is a subpage and we could have some default for a top level page, but I don't want to pollute LS |
// This will work for anything that is a subpage and we could have some default for a top level page, but I don't want to pollute LS |
||
var mmg = mmgName.split('/')[1]; |
var mmg = mmgName.split('/')[1]; |
||
if (mmg === undefined) |
if (mmg === undefined) return undefined; |
||
return undefined; |
|||
return mmg + '-mmg-kph'; |
return mmg + '-mmg-kph'; |
||
}, |
}, |
||
getKphLocalStorage: function getKphLocalStorage(mmgName) { |
|||
getKphLocalStorage: function (mmgName) { |
|||
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName); |
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName); |
||
if (lsKey !== undefined) |
if (lsKey !== undefined) return localStorage.getItem(lsKey); |
||
return localStorage.getItem(lsKey); |
|||
}, |
}, |
||
setKphLocalStorage: function setKphLocalStorage(mmgName, valueToUse) { |
|||
setKphLocalStorage: function (mmgName, valueToUse) { |
|||
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName); |
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName); |
||
if (lsKey !== undefined) |
if (lsKey !== undefined) localStorage.setItem(lsKey, valueToUse); |
||
localStorage.setItem(lsKey, valueToUse); |
|||
}, |
}, |
||
resetKphLocalStorage: function resetKphLocalStorage(mmgName) { |
|||
resetKphLocalStorage: function (mmgName) { |
|||
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName); |
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName); |
||
if (lsKey !== undefined) |
if (lsKey !== undefined) localStorage.removeItem(lsKey); |
||
localStorage.removeItem(lsKey); |
|||
} |
} |
||
}; |
|||
} |
|||
$(mmgJs.init); |
$(mmgJs.init); |
Latest revision as of 12:06, 20 October 2024
"use strict";
//
// TODO: Do I just do something kind of dumb like use an object here for constant lookup?
EXEMPT_FROM_TAX = ['Old school bond', 'Chisel', 'Gardening trowel', 'Glassblowing pipe', 'Hammer', 'Needle', 'Pestle and mortar', 'Rake', 'Saw', 'Secateurs', 'Seed dibber', 'Shears', 'Spade', 'Watering can (0)'];
// Tax is never higher than 5m per item
MAX_TAX_AMOUNT = 5000000;
MMG_SMW_DATA_ENDPOINT = "https://oldschool.runescape.wiki/api.php?action=ask&query=[[MMG%20JSON::%2B]]%7C%3FMMG%20JSON%7Climit=10000&format=json";
MAPPING_ENDPOINT = 'https://prices.runescape.wiki/api/v1/osrs/mapping';
ALLOWED_GRANULARITIES = ['latest', '5m', '1h', '6h', '24h'];
SHOULD_TAX_ON_BUY = false;
SHOULD_TAX_ON_SELL = true;
mmgJs = {
init: function init() {
mmgJs.livePrices = {};
mmgJs.officialPrices = {};
mmgJs.buttonSelect = null;
// Builds a map of MMG names to tr elements so we can manipulate them later
mmgJs.indexTableRows();
// Creates the buttons to select the price type
mmgJs.initButtons();
// Loads prices and bootstraps the table
mmgJs.loadMMGData();
},
initButtons: function initButtons() {
// Set up the ButtonSelectWidget that toggles between live and official prices
var buttonDiv = document.querySelector('.mmg-list-table-buttons');
var officialPricesButton = new OO.ui.ButtonOptionWidget({
title: 'Official Prices',
label: 'Official Prices',
data: mmgJs.updateTableOfficialPrices
});
var livePricesButton = new OO.ui.ButtonOptionWidget({
title: 'Live Prices',
label: 'Live Prices',
data: function data() {
mmgJs.initUpdateTableLivePrices('24h');
}
});
mmgJs.buttonSelect = new OO.ui.ButtonSelectWidget({
items: [officialPricesButton, livePricesButton]
});
mmgJs.buttonSelect.on('select', function (item) {
item.data();
});
$(buttonDiv).append(mmgJs.buttonSelect.$element);
},
// Build a map of MMG name -> tr for access later
indexTableRows: function indexTableRows() {
var table = document.querySelector('.mmg-list-table');
var trs = table.getElementsByClassName('mmg-list-table-row');
mmgJs.trsIndexedByName = {};
for (var i = 0; i < trs.length; i++) {
var tr = trs[i];
mmgJs.trsIndexedByName[mmgJs.getTrMmgName(tr)] = tr;
}
},
getTrMmgName: function getTrMmgName(thisTr) {
return thisTr.querySelector('a').title;
},
loadMapping: function loadMapping(callback) {
// Get the live prices mapping data since we will need it to match names and ids (is there a better place to look?)
if (mmgJs.mapping) {
callback();
} else {
$.ajax({
type: "GET",
url: MAPPING_ENDPOINT,
dataType: "json",
success: function success(msg) {
mmgJs.mapping = {};
for (var index in msg) {
mmgJs.mapping[msg[index]['id']] = msg[index];
}
},
error: function error(req) {
console.log('ERROR: Mapping endpoint failed');
}
}).done(callback);
}
},
loadLivePrices: function loadLivePrices(granularity) {
// Check for mapping
var endpoint = "https://prices.runescape.wiki/api/v1/osrs/" + granularity;
// Get the live prices data
$.ajax({
type: "GET",
url: endpoint,
dataType: "json",
indexValue: {
granularity: granularity
},
success: function success(msg) {
mmgJs.livePrices[this.indexValue.granularity] = {};
for (var key in msg['data']) {
if (!(key in mmgJs.mapping)) {
console.log('WARNING: Key ' + key + ' not found in mapping. If the id is 2659 this is expected.');
continue;
}
var itemName = mmgJs.mapping[key]['name'];
mmgJs.livePrices[this.indexValue.granularity][itemName] = msg['data'][key];
}
mmgJs.updateTableLivePrices(granularity);
},
error: function error(req) {
onFailure();
}
});
},
loadMMGData: function loadMMGData() {
// Gets the MMG data via SMW. This should be called once per run.
$.ajax({
type: "GET",
url: MMG_SMW_DATA_ENDPOINT,
dataType: "json",
success: function success(msg) {
mmgJs.mmgData = msg;
mmgJs.results = msg['query']['results'];
mmgJs.storeOfficialPrices(msg);
mmgJs.storeMMGIO(msg);
// Set the default to Live Prices
mmgJs.buttonSelect.selectItemByLabel('Live Prices');
},
error: function error(req) {
console.log('ERROR: MMG SMW call failed...Aborting');
}
});
},
updateTableOfficialPrices: function updateTableOfficialPrices() {
// We only get one price with the official prices so we have to assume buying and selling at that price
mmgJs.buyingPriceMap = mmgJs.officialPrices;
mmgJs.sellingPriceMap = mmgJs.officialPrices;
mmgJs.createList();
},
storeOfficialPrices: function storeOfficialPrices(mmgData) {
// TODO: There's probably a smarter way to get these values, but this is likely not our bottleneck anyway
mmgJs.officialPrices = {};
for (var mmg in mmgData['query']['results']) {
var d = mmgData['query']['results'][mmg]['printouts']['MMG JSON'][0];
var parsedData = JSON.parse($("<textarea/>").html(d).text());
var inputs = parsedData['inputs'];
var outputs = parsedData['outputs'];
for (var index in inputs) {
// TODO: Is there something idiomatic in old js? I think this is for-of now
var item = inputs[index];
if (item['pricetype'] == 'gemw') {
mmgJs.officialPrices[item['name']] = item['value'];
}
}
for (var index in outputs) {
// TODO: Is there something idiomatic in old js? I think this is for-of now
var item = outputs[index];
if (item['pricetype'] == 'gemw') {
mmgJs.officialPrices[item['name']] = item['value'];
}
}
}
},
storeMMGIO: function storeMMGIO() {
mmgJs.parsedResults = {};
for (var mmg in mmgJs.results) {
var d = mmgJs.results[mmg]['printouts']['MMG JSON'][0];
mmgJs.parsedResults[mmg] = JSON.parse($("<textarea/>").html(d).text());
}
},
initUpdateTableLivePrices: function initUpdateTableLivePrices(granularity) {
// Build a price list based on what we asked for
if (!ALLOWED_GRANULARITIES.includes(granularity)) {
console.log('ERROR: ' + granularity + ' is not a supported granularity');
}
// We will call updateTableLivePrices via a callback in loadLivePrices if we don't already have the prices
if (mmgJs.livePrices[granularity] === undefined) {
mmgJs.loadMapping(function () {
mmgJs.loadLivePrices(granularity);
});
} else {
mmgJs.updateTableLivePrices(granularity);
}
},
updateTableLivePrices: function updateTableLivePrices(granularity) {
var granularityMap = mmgJs.livePrices[granularity];
mmgJs.buyingPriceMap = {};
mmgJs.sellingPriceMap = {};
for (var itemName in granularityMap) {
if (granularity === 'latest') {
mmgJs.buyingPriceMap[itemName] = granularityMap[itemName]['high'];
mmgJs.sellingPriceMap[itemName] = granularityMap[itemName]['low'];
} else {
mmgJs.buyingPriceMap[itemName] = granularityMap[itemName]['avgHighPrice'];
mmgJs.sellingPriceMap[itemName] = granularityMap[itemName]['avgLowPrice'];
}
}
mmgJs.createList();
},
createList: function createList() {
for (var mmg in mmgJs.parsedResults) {
var mmgName = mmgJs.results[mmg]['fulltext'];
// Identify what row this is
var thisTr = mmgJs.trsIndexedByName[mmgName];
if (thisTr === undefined) {
console.log('WARNING: MMG not found in the table: ' + mmgName);
continue;
}
var numActions = mmgJs.getKphLocalStorage(mmgName) || mmgJs.parsedResults[mmg]['prices']['default_kph'];
if (thisTr.querySelector('.mmg-kph-selector') === null) {
mmgJs.addCellToggle(thisTr, numActions);
}
// Probably want to grab price info and pass it in here (or do we load then populate the rows?)
mmgJs.updateTableRow(thisTr, numActions);
}
},
updateTableRow: function updateTableRow(thisTr, numActions) {
//console.log('Updating row with numActions = ' + numActions);
// Update profit cell
var profitCell = thisTr.querySelector('.mmg-list-table-profit-cell');
// numActions should be value in the box or default
var mmg = thisTr.querySelector('a').title;
var profit = mmgJs.calculateProfit(mmg, mmgJs.buyingPriceMap, mmgJs.sellingPriceMap, numActions);
// Add the correct class to the table cell
if (profit > 0) {
profitCell.classList.remove('coins-neg');
profitCell.classList.add('coins-pos');
} else if (profit < 0) {
profitCell.classList.remove('coins-pos');
profitCell.classList.add('coins-neg');
} else {
profitCell.classList.remove('coins-pos');
profitCell.classList.remove('coins-neg');
}
profitCell.innerHTML = profit.toLocaleString();
},
addCellToggle: function addCellToggle(thisTr, valueToUse) {
var mmgName = mmgJs.getTrMmgName(thisTr);
var kphCell = thisTr.querySelector('.mmg-list-table-kph-cell');
var kphField = new OO.ui.NumberInputWidget({
min: 0,
input: {
value: valueToUse
},
classes: ['mmg-kph-selector'],
showButtons: false,
title: mmgJs.parsedResults[mmgName]['prices']['kph_text'] || 'Kills per hour'
});
function updateThisRow() {
mmgJs.setKphLocalStorage(mmgName, kphField.getNumericValue());
return mmgJs.updateTableRow(thisTr, kphField.getNumericValue());
}
kphField.on('change', updateThisRow);
var resetButton = new OO.ui.ButtonWidget({
icon: 'reload',
label: 'Reset',
invisibleLabel: true,
classes: ['mmg-kph-refresh-field']
});
resetButton.on('click', function () {
// Reset the key AFTER setValue since setValue would otherwise write default_kph to LS
kphField.setValue(mmgJs.parsedResults[mmgName]['prices']['default_kph']);
mmgJs.resetKphLocalStorage(mmgName);
});
var layout = new OO.ui.ActionFieldLayout(kphField, resetButton, {
classes: ['mmg-kph-selector-field']
});
kphCell.innerHTML = '';
$(kphCell).append(layout.$element);
},
calculateProfit: function calculateProfit(mmg, buyingPriceMap, sellingPriceMap, numActions) {
var inputMap = mmgJs.parsedResults[mmg]['inputs'];
var outputMap = mmgJs.parsedResults[mmg]['outputs'];
var inputAmount = mmgJs.calculateValue(inputMap, buyingPriceMap, numActions, SHOULD_TAX_ON_BUY);
var outputAmount = mmgJs.calculateValue(outputMap, sellingPriceMap, numActions, SHOULD_TAX_ON_SELL);
return Math.floor(outputAmount - inputAmount);
},
getItemPrice: function getItemPrice(item, givenPrice) {
// If the item does not use GE prices, always return the price as is
if (item['pricetype'] != 'gemw') return item['value'];
if (givenPrice === undefined || givenPrice === null) {
console.log('WARNING: This item has no price in the price map you gave me! ' + item['name']);
return item['value'];
}
return givenPrice;
},
calculateValue: function calculateValue(itemAmountMap, priceMap, numActions, useTax) {
var sum = 0;
for (var index in itemAmountMap) {
// TODO: Is there something idiomatic in old js? I think this is for-of now
var item = itemAmountMap[index];
// For each item, determine the value
// console.log("Item: " + JSON.stringify(item));
// Stub this out to get the correct price
var value = mmgJs.getItemPrice(item, priceMap[item['name']]);
var quantity = item['qty'];
// Might want to always use this value if pricetype is not gemw since that means it isnt on the ge
var priceType = item['pricetype'];
// Number used will not change based on numActions if isph is true
var numberUsed;
if (item['isph']) {
numberUsed = quantity;
} else {
numberUsed = quantity * numActions;
}
if (useTax) {
// Subtract tax since it is per item and not per txn
sum += numberUsed * mmgJs.applyTax(item['name'], value);
} else {
sum += numberUsed * value;
}
}
return sum;
},
applyTax: function applyTax(itemName, price) {
if (EXEMPT_FROM_TAX.includes(itemName)) return price;
return price - Math.min(Math.floor(price / 100), MAX_TAX_AMOUNT);
},
/**
* LocalStorage helper methods to retrieve and set values
*/
getLSKeyNameForMmg: function getLSKeyNameForMmg(mmgName) {
// mmgName should always have a "Money making guide/" prefix
// This will work for anything that is a subpage and we could have some default for a top level page, but I don't want to pollute LS
var mmg = mmgName.split('/')[1];
if (mmg === undefined) return undefined;
return mmg + '-mmg-kph';
},
getKphLocalStorage: function getKphLocalStorage(mmgName) {
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName);
if (lsKey !== undefined) return localStorage.getItem(lsKey);
},
setKphLocalStorage: function setKphLocalStorage(mmgName, valueToUse) {
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName);
if (lsKey !== undefined) localStorage.setItem(lsKey, valueToUse);
},
resetKphLocalStorage: function resetKphLocalStorage(mmgName) {
var lsKey = mmgJs.getLSKeyNameForMmg(mmgName);
if (lsKey !== undefined) localStorage.removeItem(lsKey);
}
};
$(mmgJs.init);