Module:Exchange

--[[

--]] -- -- -- Implements various exchange templates -- See Individual method docs for more details -- -- See also: -- - Module:ExchangeData -- - Module:ExchangeDefault --

local p = {}

-- only load commonly used modules here local yesno = require( 'Module:Yesno' ) local addcommas = require( 'Module:Addcommas' )._add

-- map redirects to their correct pages local geRedirects = { ['1/2 anchovy pizza'] = '½ anchovy pizza', ['1/2 meat pizza'] = '½ meat pizza', ["1/2 p'apple pizza"] = "½ p'apple pizza", ['1/2 plain pizza'] = '½ plain pizza', ['1/3 evil turnip'] = '⅓ evil turnip', ['1/3 blue blubber jellyfish'] = '⅓ blue blubber jellyfish', ['1/3 green blubber jellyfish'] = '⅓ green blubber jellyfish', ['2/3 cake'] = '⅔ cake', ['2/3 chocolate cake'] = '⅔ chocolate cake', ['2/3 evil turnip'] = '⅔ evil turnip', ['2/3 blue blubber jellyfish'] = '⅔ blue blubber jellyfish', ['2/3 green blubber jellyfish'] = '⅔ green blubber jellyfish' }

-- -- Makes sure first letter of item is uppercase -- Automatically handles any redirects -- local function checkTitle( item ) -- upper case first letter to make sure we can find a valid item page item = mw.text.split( item, '' ) item[1] = mw.ustring.upper( item[1] ) item = table.concat( item )

-- automatically handle redirects if geRedirects[item] ~= nil then item = geRedirects[item] end

return item end -- -- Simple mw.loadData wrapper used to access data located on module subpages -- -- @param item {string} Item to retrieve data for -- @return {table} Table of item data -- local function load( item ) item = checkTitle( item ) local noErr, ret = pcall( mw.loadData, 'Module:Exchange/' .. item )

if noErr then return ret end

error( ret ) end

-- -- Returns the price of an item -- -- @param item {string} Item to get current price of -- @param multi {number} (optional) Multiplies the output price by the specified number -- @param format {boolean} (optional) Format the result with commas (defaults to false) -- @param round {number} (optional) Round the result to a number of decimal places -- @return {number|string} Price of item. Will return a string if formatted, else a number. -- function p._price( item, multi, format, round ) local price = load( item ).price local multi = type( multi ) == 'number' and multi or 1 local format = type( format ) == 'boolean' and format or false local ret = price * multi -- round the number to X d.p.   if round ~= nil then local multi = 10^( round ) ret = math.floor( ret * multi + 0.5 ) / multi end

if format then return addcommas( ret ) end

return ret end

-- -- Returns the limit of an item -- -- @param item {string} Item to get the limit of -- @return {number} Limit of item -- function p._limit( item ) return load( item ).limit end

-- -- Returns the value of an item -- -- @param item {string} Item to get the value or -- @return {number} Value of item -- function p._value( item ) return load( item ).value end

-- -- Calculates the difference between the current price and the last price of an item -- -- @param item {string} Item to calculate price difference for -- @param format {boolean} `true` if the output is to be formatted with commas --                        Defaults to `false` -- @return {number|string} The price difference as a number --                        If `format` is set to `true` then this returns a string --                        If either of the prices to calculate the diff from are unavailable, this returns `0` (number) -- function p._diff( item, format ) local data = load( item ) local diff = 0

if data.price and data.last then diff = data.price - data.last

if format then diff = addcommas( diff ) end end

return diff end

-- -- internal method -- -- @todo merge into p.table -- -- @param item {string} Item to get data for -- @return {string} -- function p._table( item ) -- load data and any required modules local item = checkTitle( item ) local data = load( item ) local timeago = require( 'Module:TimeAgo' )._ago local changeperday = require( 'Module:ChangePerDay' )._change

-- set variables here to make the row building easier to follow local div = Unknown local limit = data.limit and addcommas( data.limit ) or Unknown local members = Unknown

if data.last then local link = 'http://services.runescape.com/m=itemdb_rs/viewitem.ws?obj=' .. data.itemId local change = math.abs( changeperday( {data.price, data.last, data.date, data.lastDate} ) )

if data.price > data.last then arrow = '' elseif data.price < data.last then arrow = '' else arrow = '' end

if change >= 0.04 then arrow = arrow .. arrow .. arrow elseif change >= 0.02 then arrow = arrow .. arrow end

div = mw.html.create( 'div' ) :css( 'white-space', 'nowrap' ) :wikitext( arrow )

div = tostring( div ) end

if data.members == true then members = '' elseif data.members == false then members = '' end

-- build table row local tr = mw.html.create( 'tr' ) :tag( 'td' ) :wikitext( '' ) :done :tag( 'td' ) :css( {               ['width'] = '15%',                ['text-align'] = 'left'            } ) :wikitext(  .. item ..  ) :done :tag( 'td' ) :wikitext( addcommas( data.price ) ) :done :tag( 'td' ) :wikitext( div ) :done

if data.alchable == nil or yesno( data.alchable ) then local low, high = Unknown, Unknown

if data.value then low = addcommas( math.floor( data.value * 0.4 ) ) high = addcommas( math.floor( data.value * 0.6 ) ) end

tr           :tag( 'td' ) :wikitext( low ) :done :tag( 'td' ) :wikitext( high ) :done else tr           :tag( 'td' ) :attr( 'colspan', '2' ) :wikitext( Cannot be alchemised ) :done end

tr       :tag( 'td' ) :wikitext( limit ) :done :tag( 'td' ) :wikitext( members ) :done :tag( 'td' ) :css( 'white-space', 'nowrap' ) :wikitext( 'view' ) :done :tag( 'td' ) :css( 'font-size', '85%' ) :wikitext( timeago{data.date} ) :done

return tostring( tr )

end

-- -- -- function p.exists( frame ) local args = frame:getParent.args local item = checkTitle( args[1] or '' ) local noErr, data = pcall( mw.loadData, 'Module:Exchange/' .. item )

if noErr then return '1' end

return '0' end

-- -- GEExists for modules -- function p._exists( arg ) local item = checkTitle( arg or '' ) local noErr, data = pcall( mw.loadData, 'Module:Exchange/' .. item )

if noErr then return true end

return false end

-- -- Internal method for p.highAlchTable, p.lowAlchTable and p.genStoreTable -- -- @param item {string} The name of the item -- @param data {table} The item's ge data -- @param alch {number} The item's alch/sell value -- @param min {number} (optional) Sets the cap for amount of items that can be converted to gp per hour -- @param natPrice {number} (optional) Sets the price of a Nature rune (set to `0` by `p.genStoreTable`) -- local function alchTable( item, data, alch, min, natPrice ) local timeago = require( 'Module:TimeAgo' )._ago local round = require( 'Module:Number' )._round -- gen store doesn't need a nat price as it's not used -- therefore we'd set it to 0 local natPrice = natPrice or load( 'Nature rune' ).price local profit = alch - data.price - natPrice

local image = '' local itemStr =  .. item ..  local priceStr = addcommas( data.price ) local alchStr = addcommas( alch ) local profitStr = addcommas( profit ) local roi = tostring( round( ( profit / ( data.price + natPrice ) * 100 ), 1 ) ) .. '%'   local limit = data.limit and addcommas( data.limit ) or Unknown local maxProfit = Unknown local members = Unknown local members_sortkey = 2 local details = 'view' local lastUpdated = timeago{data.date}

if data.limit then -- cap at 4800, the maximum number of alchs that can be cast every 4 hours -- varies for general store rows min = min or 4800 min = ( data.limit > min ) and min or data.limit maxProfit = addcommas( min * profit ) end

mw.log( maxProfit )

if data.members == true then members = '' members_sortkey = 1 elseif data.members == false then members = '' members_sortkey = 0 end

local tr = mw.html.create( 'tr' ) :tag( 'td' ) :wikitext( image ) :done :tag( 'td' ) :css( {               width = '15%',                ['text-align'] = 'left'            } ) :wikitext( itemStr ) :done :tag( 'td' ) :wikitext( priceStr ) :done :tag( 'td' ) :wikitext( alchStr ) :done :tag( 'td' ) :wikitext( profitStr ) :done :tag( 'td' ) :wikitext( roi ) :done :tag( 'td' ) :wikitext( limit ) :done :tag( 'td' ) :wikitext( maxProfit ) :done :tag( 'td' ) :wikitext( members ) :attr('data-sort-value', members_sortkey) :done :tag( 'td' ) :css( 'white-space', 'nowrap' ) :wikitext( details ) :done :tag( 'td' ) :css( 'font-size', '85%' ) :wikitext( lastUpdated ) :done

return tostring( tr ) end

-- -- -- -- @example -- function p.highAlchTable( frame ) local args = frame:getParent.args local item = checkTitle( args[1] ) local data = load( item ) local alch = math.floor( data.value * 0.6 )

return alchTable( item, data, alch ) end

-- -- -- -- @example -- function p.lowAlchTable( frame ) local args = frame:getParent.args local item = checkTitle( args[1] ) local data = load( item ) local alch = math.floor( data.value * 0.4 )

return alchTable( item, data, alch ) end

-- -- -- -- @example -- function p.genStoreTable( frame ) local args = frame:getParent.args local item = checkTitle( args[1] ) local data = load( item ) local alch = math.floor( data.value * 0.3 )

return alchTable( item, data, alch, 50000, 0 ) end

-- -- -- -- -- @example -- @example -- @example -- function p.price( frame ) -- usage: or     local args = frame.args local pargs = frame:getParent.args local item = pargs[1] local expr = mw.ext.ParserFunctions.expr local round = tonumber( pargs.round )

if item then item = mw.text.trim( item ) else error( '"item" argument not specified', 0 ) end

-- default to formatted for backwards compatibility with old GE templates local format = true local multi = 1

-- format is set with #invoke -- so set it first to allow it to be overridden by template args if args.format ~= nil then format = yesno( args.format ) end

if tonumber( pargs[2] ) ~= nil then multi = tonumber( pargs[2] )

-- indicated someone is trying to pass an equation as a mulitplier -- known use cases are fractions, but pass it to #expr to make sure it's handled correctly elseif pargs[2] ~= nil and mw.ustring.find( pargs[2], '[/*+-]' ) then multi = tonumber( expr( pargs[2] ) )

-- uses elseif to prevent something like -- causing a formatted output, as 1 casts to true when passed to yesno elseif type( yesno( pargs[2] ) ) == 'boolean' then format = yesno( pargs[2] )

if tonumber( pargs[3] ) ~= nil then multi = tonumber( pargs[3] ) end end

return p._price( item, multi, format, round ) end

-- -- -- -- @example -- function p.table( frame ) local args = frame:getParent.args local item = args[1]

if item then item = mw.text.trim( item ) else error( '"item" argument not specified', 0 ) end

return p._table( item ) end

-- -- experimental limit method for Grand Exchange/Buying Limits -- function p.gemwlimit( frame ) local item = frame:getParent.args[1] local data = mw.loadData( 'Module:Exchange/' .. item ) return data.limit end

-- -- -- -- -- -- -- -- @example -- @example -- @example -- @example -- @example -- function p.view( frame ) local fargs = frame.args local pargs = frame:getParent.args local item = pargs[1] or fargs.item local view = fargs.view or '' local loadView = {limit=true, value=true, itemId=true, members=true, category=true, examine=true, alchable=true}

if item then item = mw.text.trim( item ) else error( '"item" argument not specified', 0 ) end

view = mw.ustring.lower( view )

if view == 'itemid' then view = 'itemId' end

if view == 'diff' then return p._diff( item )

elseif loadView[view] then return load( item )[view]

else local default = require( 'Module:ExchangeDefault' ) -- handle redirects and casing of item before passing it on       item = checkTitle( item ) return default.main( item ) end end

return p