Module:Products: Difference between revisions

From Brighter Shores Wiki
Jump to navigation Jump to search
Content added Content deleted
No edit summary
(recipes which have the passive tag will have their quantities displayed as per-hour, rather than per-recipe)
 
(13 intermediate revisions by 3 users not shown)
Line 1: Line 1:
require('Module:Mw.html extension')
local Array = require('Module:Array')
local currency = require('Module:Currency').parse
local yesno = require('Module:Yesno')
local purge = require('Module:Purge')._purge

local p = {}
local p = {}


Line 5: Line 11:
end
end


function p._main(args)
local function recipe_sort(recipe_a, recipe_b)
--if one is nil but not both, put nil levels after known levels
args = args or {}
if (recipe_a.level == nil) ~= (recipe_b.level == nil) then
args.item = args.item or mw.title.getCurrentTitle().text
return recipe_b.level == nil
local showPrices = args.showPrices or false
end
local showValues = args.showValues or false
local products = p.getProducts(args)
-- if both are nil, sort by name
if products ~= 0 then
if recipe_a.level == nil then
local recipeNames = p.extractRecipeNames(products)
return recipe_a.output[1].name < recipe_b.output[1].name
local prodprices = p.getShopBuyPrices(products)
end
-- Generate and return a table containing the product information
local output = p.displayProductTable(prodprices, showPrices, showValues)
-- if neither is nil, sort first by level
return output
if recipe_a.level ~= recipe_b.level then
else
return recipe_a.level < recipe_b.level
return "There are no known products for item '''args.item'''"
end
end
end


-- if neither is nil and levels are the same, sort by name
function p.extractRecipeNames(products)
return recipe_a.output[1].name < recipe_b.output[1].name
local recipeNames = {}
for _, recipeInfo in ipairs(products) do
local recipeLink = recipeInfo["Recipe"]
if recipeLink then
local displayName = recipeLink:match("%[%[.-|(.+)%]%]")
if displayName then
table.insert(recipeNames, displayName)
end
end
end
return recipeNames
end
end


function p.getProducts(args)
function p._main(args)
args = args or {}
local item = mw.smw.ask('[[:+]][[Uses item::' .. args.item .. ']]|?Uses item |?Uses item and quantity |?Profession A |?Profession Level A|?Value |mainlabel=Recipe') or 0
local item = args[1] or mw.title.getCurrentTitle().text
return item
local showPrices = yesno(args.showPrices)
end
local showValues = yesno(args.showValues)
local limit = tonumber(args.limit or 0) or 0
if limit <= 0 then
limit = 500
end
-- Query for data
local smw_data = mw.smw.ask{
'[[Uses item::' .. item .. ']] OR [[Activity input::' .. item .. ']] OR [[Activity container::' .. item .. ']]',
'?Recipe JSON',
'?Activity JSON',
showValues and '?Value',
limit = limit
}
if not smw_data then
return ":''No products found. To force an update, click "
..purge('dml-'..mw.uri.anchorEncode(item), 'here', 'span')
..".''[[Category:Empty products lists]]"
end


-- Create a list of all recipes that use this item
function p.getShopBuyPrices(products)
local mw = require('mw')
local recipes = {}
for _, product in ipairs(smw_data) do
-- this part will need to be addressed if a page has both a recipe and an activity
local jsons = product['Recipe JSON'] or product['Activity JSON']
if type(jsons) == 'string' then
jsons = { jsons }
end
for _, json in ipairs(jsons) do
local json = mw.text.jsonDecode(json)
json.Value = product.Value
-- Filter out when this item isn't actually used (Will happen on pages with multiple recipes)
if Array.any(json.materials, function(mat)
return mat.name == item
end) then
table.insert(recipes, json)
end
end
end


-- Sort list of recipes by the level of the recipe (cross-profession)
for _, product in ipairs(products) do
table.sort(recipes, recipe_sort)
local usesItems = product["Uses item and quantity"]
if type(usesItems) == "table" then
for _, item in ipairs(usesItems) do
-- Extract item name and quantity from the "item,#" format
local itemName, quantity = item:match("([^,]+),(%d+)")
quantity = tonumber(quantity) or 1 -- Default to 1 if quantity is missing


-- if recipe is passive, want per hour
if itemName then
for _, recipe in ipairs(recipes) do
-- Query for the shop buy price of the item
recipe.recipePerHour = recipe.duration and math.floor(3600/recipe.duration)
local shopPriceQuery = '[[:+]][[Sold item::' .. itemName .. ']]|?Shop buy price|mainlabel=' .. itemName
-- replace quantities with per-hour quantities
local shopPriceResult = mw.smw.ask(shopPriceQuery) or {}
-- if recipe.passive then
local shopPrice = 0
for _,item in ipairs(recipe.output) do

item.displayQuantity = (item.quantity and recipe.recipePerHour and item.quantity * recipe.recipePerHour) or ''
if shopPriceResult[1] and shopPriceResult[1]["Shop buy price"] then
end
shopPrice = tonumber(shopPriceResult[1]["Shop buy price"]) or 0
for _,item in ipairs(recipe.materials) do
end
item.displayQuantity = (item.quantity and recipe.recipePerHour and item.quantity * recipe.recipePerHour) or ''

end
-- Multiply the price by the quantity
-- end
local totalPrice = shopPrice * quantity

-- Update the product with the formatted string and total price
product[itemName .. "_Shop_buy_price"] = totalPrice > 0 and totalPrice or "N/A"
product[itemName .. "_Formatted"] = quantity .. " x " .. itemName
end
end
elseif type(usesItems) == "string" then
-- Handle single uses item_and_quantity (not a table)
local itemName, quantity = usesItems:match("([^,]+),(%d+)")
quantity = tonumber(quantity) or 1 -- Default to 1 if quantity is missing

if itemName then
-- Query for the shop buy price of the item
local shopPriceQuery = '[[:+]][[Sold item::' .. itemName .. ']]|?Shop buy price|mainlabel=' .. itemName
local shopPriceResult = mw.smw.ask(shopPriceQuery) or {}
local shopPrice = 0

if shopPriceResult[1] and shopPriceResult[1]["Shop buy price"] then
shopPrice = tonumber(shopPriceResult[1]["Shop buy price"]) or 0
end

-- Multiply the price by the quantity
local totalPrice = shopPrice * quantity

-- Update the product with the formatted string and total price
product[itemName .. "_Shop_buy_price"] = totalPrice > 0 and totalPrice or "N/A"
product[itemName .. "_Formatted"] = quantity .. " x " .. itemName
end
end
end
return products
end

function p.displayProductTable(products, showPrices, showValues)
local currency = require('Module:Currency').parse
local out = {}
table.insert(out, '{| class="wikitable"')
local headerRow = {}
table.insert(headerRow, '!colspan="2" | Recipe')
table.insert(headerRow, '!! Level')

if showValues then
table.insert(headerRow, '!! Value')
end
end

-- Calculate shop prices
table.insert(headerRow, '!! Ingredients')

if showPrices then
if showPrices then
local price_cache = {}
table.insert(headerRow, '!! Price')
for _, recipe in ipairs(recipes) do
end
for _, item in ipairs(recipe.materials) do
-- Extract item name and quantity from the "item,#" format


-- Query for the shop buy price of the item
table.insert(out, table.concat(headerRow, ' '))
local shopPriceResult = price_cache[item.name] or mw.smw.ask{
'[[Sold item::' .. item.name .. ']]',
'?Shop buy price'
} or {}
price_cache[item.name] = shopPriceResult
local shopPrice


if shopPriceResult[1] then
for _, product in ipairs(products) do
shopPrice = tonumber(shopPriceResult[1]['Shop buy price'] or 0) or 0
local recipeRawText = product["Recipe"]:match("%[%[.-|(.+)%]%]")
end
local recipeImage = "[[File:" .. recipeRawText .. ".png|30px]]"
local recipe = product["Recipe"] or "Unknown"
local professionName = product["Profession A"]:match("%[%[.-|(.+)%]%]") or product["Profession A"]:match("%[%[(.-)%]%]") or product["Profession A"] or "Unknown"
local professionLevel = "[[File:" .. professionName .. " small icon.png|15px]] " .. (product["Profession Level A"] or "Unknown")
local usesItems = product["Uses item"] or {}
local value = currency(product["Value"]) or "Unknown"


-- Update the product with the total price
-- Concatenate uses items and their shop buy prices in unordered lists
if shopPrice ~= nil then
local usesItemStr = "<ul style='list-style:none; margin:0; padding-left:0;'>"
item.price = shopPrice * item.quantity
local shopBuyPriceStr = "<ul style='list-style:none; margin:0; padding-left:0; text-align:right;'>"
end
end
end
end


-- Create table
if type(usesItems) == "table" then
local out = mw.html.create('table')
for _, item in ipairs(usesItems) do
:addClass('wikitable align-right-1 sortable')
local itemName = item:match("%[%[.-|(.+)%]%]") or item:match("%[%[(.-)%]%]") or item
:tag('tr')
usesItemStr = usesItemStr .. "<li>" .. product[itemName .. "_Formatted"] .. "</li>"
:tag('th')
local shopBuyPrice = product[itemName .. "_Shop_buy_price"] or 0
:attr{ colspan = '3' }
if shopBuyPrice == "N/A" then shopBuyPrice = 0 end
:wikitext('Product')
shopBuyPriceStr = shopBuyPriceStr .. "<li>" .. currency(shopBuyPrice) .. "</li>"
:done()
end
:tag('th'):wikitext('Level'):done()
elseif type(usesItems) == "string" then
:IF(showValues)
local itemName = usesItems:match("%[%[.-|(.+)%]%]") or usesItems:match("%[%[(.-)%]%]") or usesItems
:tag('th'):wikitext('Value'):done()
usesItemStr = usesItemStr .. "<li>" .. product[itemName .. "_Formatted"] .. "</li>"
:END()
local shopBuyPrice = product[itemName .. "_Shop_buy_price"] or 0
:tag('th'):wikitext('Inputs'):done()
if shopBuyPrice == "N/A" then shopBuyPrice = 0 end
:IF(showPrices)
shopBuyPriceStr = shopBuyPriceStr .. "<li>" .. currency(shopBuyPrice) .. "</li>"
:tag('th'):wikitext('Price'):done()
end
:END()
:done()


for _, recipe in ipairs(recipes) do
usesItemStr = usesItemStr .. "</ul>"
local row = out:tag('tr')
shopBuyPriceStr = shopBuyPriceStr .. "</ul>"
:tag('td')

:css{ ['border-right'] = '0', ['padding-right'] = '0' }
table.insert(out, '|-')
:attr{ ['data-sort-value'] = recipe.output[1].name }
:IF(recipe.passive)
local valuesRow = {}
:wikitext(recipe.output[1].displayQuantity .. ' (/hr) &times;')
:ELSE()
table.insert(valuesRow, '| ' .. recipeImage)
:wikitext(recipe.output[1].quantity .. ' &times;')
table.insert(valuesRow, '|| ' .. recipe)
:END()
table.insert(valuesRow, '|| ' .. professionLevel)
:done()
:tag('td')
:addClass('plinkt-image no-border')
:css{ ['border-left'] = '0', ['padding-left'] = '0' }
:wikitext('[[File:' .. recipe.output[1].name .. '.png|link=' .. recipe.output[1].name .. '|30px]]')
:done()
:tag('td')
:addClass('plinkt-link no-border')
:wikitext('[[' .. recipe.output[1].name .. ']]')
:done()
:tag('td')
:IF(recipe.profession)
:wikitext(('[[File:%s small icon.png|15px|link=%s]] %s'):format(recipe.profession, recipe.profession, recipe.level or 'Unknown'))
:ELSE()
:wikitext(('[[FileUnknown profession small icon.png|15px|link=Professions]] %s'):format(recipe.level or 'Unknown'))
:END()
:done()


if showValues then
if showValues then
if recipe.Value then
table.insert(valuesRow, '|| ' .. value)
row:tag('td')
:wikitext(currency(recipe.Value))
:done()
else
row:tag('td')
:attr{ ['data-sort-value'] = '' }
:wikitext('Unknown')
:done()
end
end
end


local ingredients = row:tag('td')
table.insert(valuesRow, '|| ' .. usesItemStr)
:attr{ ['data-sort-value'] = table.concat(Array.map(recipe.materials, function(item) return item.name end), '\0') }
:tag('ul')
:css{ ['list-style'] = 'none', ['margin'] = '0', ['padding-left'] = '0' }

for _, item in ipairs(recipe.materials) do
ingredients:tag('li')
:IF(recipe.passive)
:wikitext(('%s (/hr) &times; [[File:%s.png|link=%s|18px]] [[%s]]'):format(item.displayQuantity, item.name, item.name, item.name))
:ELSE()
:wikitext(('%s &times; [[File:%s.png|link=%s|18px]] [[%s]]'):format(item.quantity, item.name, item.name, item.name))
:END()
:done()
end


if showPrices then
if showPrices then
local prices = row:tag('td')
table.insert(valuesRow, '|| ' .. shopBuyPriceStr)
:tag('ul')
:css{ ['list-style'] = 'none', ['margin'] = '0', ['padding-left'] = '0' }

for _, item in ipairs(recipe.materials) do
if item.price then
prices:tag('li'):wikitext(currency(item.price)):done()
else
prices:tag('li'):wikitext('Unknown'):done()
end
end
end
end


end
table.insert(out, table.concat(valuesRow, ' '))
--table.insert(out, '| ' .. recipe .. ' || ' .. professionLevel .. ' || ' .. value .. ' || ' .. usesItemStr .. ' || ' .. shopBuyPriceStr)
end


return out
table.insert(out, '|}')
return table.concat(out, '\n')
end
end



Latest revision as of 19:16, 28 December 2024

Module documentation
This documentation is transcluded from Module:Products/doc. [edit] [history] [purge]
This module does not have any documentation. Please consider adding documentation at Module:Products/doc. [edit]
Module:Products's function main is invoked by Template:Products.
Module:Products requires Module:Array.
Module:Products requires Module:Currency.
Module:Products requires Module:Mw.html extension.
Module:Products requires Module:Purge.
Module:Products requires Module:Yesno.

require('Module:Mw.html extension')
local Array = require('Module:Array')
local currency = require('Module:Currency').parse
local yesno = require('Module:Yesno')
local purge = require('Module:Purge')._purge

local p = {}

function p.main(frame)
	return p._main(frame:getParent().args)
end

local function recipe_sort(recipe_a, recipe_b)
	--if one is nil but not both, put nil levels after known levels
	if (recipe_a.level == nil) ~= (recipe_b.level == nil) then
		return recipe_b.level == nil
	end
	
	-- if both are nil, sort by name
	if recipe_a.level == nil then
		return recipe_a.output[1].name < recipe_b.output[1].name
	end
	
	-- if neither is nil, sort first by level
	if recipe_a.level ~= recipe_b.level then
		return recipe_a.level < recipe_b.level
	end

	-- if neither is nil and levels are the same, sort by name
	return recipe_a.output[1].name < recipe_b.output[1].name
end

function p._main(args)
	args = args or {}
	local item = args[1] or mw.title.getCurrentTitle().text
	local showPrices = yesno(args.showPrices)
	local showValues = yesno(args.showValues)
	local limit = tonumber(args.limit or 0) or 0
	if limit <= 0 then
		limit = 500
	end
	
	-- Query for data
	local smw_data = mw.smw.ask{
		'[[Uses item::' .. item .. ']] OR [[Activity input::' .. item .. ']] OR [[Activity container::' .. item .. ']]',
		'?Recipe JSON',
		'?Activity JSON',
		showValues and '?Value',
		limit = limit
	}
	if not smw_data then
		return ":''No products found. To force an update, click "
				..purge('dml-'..mw.uri.anchorEncode(item), 'here', 'span')
				..".''[[Category:Empty products lists]]"
	end

	-- Create a list of all recipes that use this item
	local recipes = {}
	for _, product in ipairs(smw_data) do
		-- this part will need to be addressed if a page has both a recipe and an activity
		local jsons = product['Recipe JSON'] or product['Activity JSON'] 
		if type(jsons) == 'string' then
			jsons = { jsons }
		end
		for _, json in ipairs(jsons) do
			local json = mw.text.jsonDecode(json)
			json.Value = product.Value
			-- Filter out when this item isn't actually used (Will happen on pages with multiple recipes)
			if Array.any(json.materials, function(mat)
				return mat.name == item
			end) then
				table.insert(recipes, json)
			end
		end
	end

	-- Sort list of recipes by the level of the recipe (cross-profession)
	table.sort(recipes, recipe_sort)

	-- if recipe is passive, want per hour
	for _, recipe in ipairs(recipes) do
		recipe.recipePerHour = recipe.duration and math.floor(3600/recipe.duration)
		-- replace quantities with per-hour quantities
--		if recipe.passive then
			for _,item in ipairs(recipe.output) do
				item.displayQuantity = (item.quantity and recipe.recipePerHour and item.quantity * recipe.recipePerHour) or ''
			end
			for _,item in ipairs(recipe.materials) do
				item.displayQuantity = (item.quantity and recipe.recipePerHour and item.quantity * recipe.recipePerHour) or ''
			end
--		end
	end
	
	-- Calculate shop prices
	if showPrices then
		local price_cache = {}
		for _, recipe in ipairs(recipes) do
			for _, item in ipairs(recipe.materials) do
				-- Extract item name and quantity from the "item,#" format

				-- Query for the shop buy price of the item
				local shopPriceResult = price_cache[item.name] or mw.smw.ask{
					'[[Sold item::' .. item.name .. ']]',
					'?Shop buy price'
				} or {}
				price_cache[item.name] = shopPriceResult
				local shopPrice

				if shopPriceResult[1] then
					shopPrice = tonumber(shopPriceResult[1]['Shop buy price'] or 0) or 0
				end

				-- Update the product with the total price
				if shopPrice ~= nil then
					item.price = shopPrice * item.quantity
				end
			end
		end
	end

	-- Create table
	local out = mw.html.create('table')
		:addClass('wikitable align-right-1 sortable')
		:tag('tr')
			:tag('th')
				:attr{ colspan = '3' }
				:wikitext('Product')
			:done()
			:tag('th'):wikitext('Level'):done()
			:IF(showValues)
				:tag('th'):wikitext('Value'):done()
			:END()
			:tag('th'):wikitext('Inputs'):done()
			:IF(showPrices)
				:tag('th'):wikitext('Price'):done()
			:END()
		:done()

	for _, recipe in ipairs(recipes) do
		local row = out:tag('tr')
			:tag('td')
				:css{ ['border-right'] = '0', ['padding-right'] = '0' }
				:attr{ ['data-sort-value'] = recipe.output[1].name }
				:IF(recipe.passive)
					:wikitext(recipe.output[1].displayQuantity .. ' (/hr) &times;')
				:ELSE()
					:wikitext(recipe.output[1].quantity .. ' &times;')
				:END()
			:done()
			:tag('td')
				:addClass('plinkt-image no-border')
				:css{ ['border-left'] = '0', ['padding-left'] = '0' }
				:wikitext('[[File:' .. recipe.output[1].name .. '.png|link=' .. recipe.output[1].name .. '|30px]]')
			:done()
			:tag('td')
				:addClass('plinkt-link no-border')
				:wikitext('[[' .. recipe.output[1].name .. ']]')
			:done()
			:tag('td')
				:IF(recipe.profession)
					:wikitext(('[[File:%s small icon.png|15px|link=%s]] %s'):format(recipe.profession, recipe.profession, recipe.level or 'Unknown'))
				:ELSE()
					:wikitext(('[[FileUnknown profession small icon.png|15px|link=Professions]] %s'):format(recipe.level or 'Unknown'))
				:END()
			:done()

		if showValues then
			if recipe.Value then
				row:tag('td')
					:wikitext(currency(recipe.Value))
				:done()
			else
				row:tag('td')
					:attr{ ['data-sort-value'] = '' }
					:wikitext('Unknown')
				:done()
			end
		end

		local ingredients = row:tag('td')
			:attr{ ['data-sort-value'] = table.concat(Array.map(recipe.materials, function(item) return item.name end), '\0') }
			:tag('ul')
				:css{ ['list-style'] = 'none', ['margin'] = '0', ['padding-left'] = '0' }

		for _, item in ipairs(recipe.materials) do
			ingredients:tag('li')
				:IF(recipe.passive)
					:wikitext(('%s (/hr) &times; [[File:%s.png|link=%s|18px]] [[%s]]'):format(item.displayQuantity, item.name, item.name, item.name))
				:ELSE()
					:wikitext(('%s &times; [[File:%s.png|link=%s|18px]] [[%s]]'):format(item.quantity, item.name, item.name, item.name))
				:END()
			:done()
		end

		if showPrices then
			local prices = row:tag('td')
				:tag('ul')
					:css{ ['list-style'] = 'none', ['margin'] = '0', ['padding-left'] = '0' }

			for _, item in ipairs(recipe.materials) do
				if item.price then
					prices:tag('li'):wikitext(currency(item.price)):done()
				else
					prices:tag('li'):wikitext('Unknown'):done()
				end
			end
		end

	end

	return out
end

return p