Jump to content

Module:DailyFeatured: Difference between revisions

From the Vrienden Universe, a fictional wiki
No edit summary
No edit summary
Line 2: Line 2:


local POOL_TEMPLATE = "Featured pool"
local POOL_TEMPLATE = "Featured pool"
local function trim(s)
return mw.text.trim(s or "")
end
local function hasImageExtension(name)
if not name or name == "" then
return false
end
local lower = mw.ustring.lower(name)
return lower:match("%.png$")
or lower:match("%.jpg$")
or lower:match("%.jpeg$")
or lower:match("%.svg$")
end


local function getPool()
local function getPool()
Line 8: Line 25:


for line in mw.text.gsplit(content or "", "\n", true) do
for line in mw.text.gsplit(content or "", "\n", true) do
line = mw.text.trim(line or "")
line = trim(line)
line = line:gsub("<!%-%-.-%-%->", "")
line = line:gsub("<!%-%-.-%-%->", "")
line = mw.text.trim(line)
line = trim(line)


if line ~= "" then
if line ~= "" then
line = line:gsub("^%*%s*", "")
line = line:gsub("^%*%s*", "")
line = mw.text.trim(line)
line = trim(line)


local linkedTitle = line:match("^%[%[([^|%]]+)%]%]$")
local linkedTitle =
line:match("^%[%[([^|%]]+)%]%]$")
or line:match("^%[%[([^|%]]+)|.-%]%]$")
or line:match("^%[%[([^|%]]+)|.-%]%]$")


Line 23: Line 41:
end
end


line = mw.text.trim(line)
line = trim(line)


if line ~= "" then
if line ~= "" then
Line 34: Line 52:
end
end


local function removeTemplates(s)
local function removeBalancedTemplates(s)
local previous
local previous


Line 42: Line 60:
until s == previous
until s == previous


return s
end
local function removeRefs(s)
s = s:gsub("<ref[^>/]-/>", "")
s = s:gsub("<ref.->.-</ref>", "")
s = s:gsub("<ref.->", "")
return s
end
local function removeFiles(s)
s = s:gsub("%[%[%s*[Ff]ile%s*:.-%]%]", "")
s = s:gsub("%[%[%s*[Ii]mage%s*:.-%]%]", "")
return s
return s
end
end
Line 51: Line 82:


s = s:gsub("<!%-%-.-%-%->", "")
s = s:gsub("<!%-%-.-%-%->", "")
s = s:gsub("<ref[^>/]-/>", "")
s = removeRefs(s)
s = s:gsub("<ref.->.-</ref>", "")
s = removeFiles(s)
s = s:gsub("<ref.->", "")
s = removeBalancedTemplates(s)
s = s:gsub("%[%[[Ff]ile:.-%]%]", "")
s = s:gsub("<br%s*/?>", "\n")
s = s:gsub("%[%[[Ii]mage:.-%]%]", "")
s = s:gsub("<br%s*>", "\n")
s = removeTemplates(s)
s = s:gsub("\r", "")
 
s = s:gsub("'''", "")
s = s:gsub("'''", "")
s = s:gsub("''", "")
s = s:gsub("''", "")
Line 63: Line 95:
s = s:gsub("%[https?://[^%s%]]+%s+([^%]]+)%]", "%1")
s = s:gsub("%[https?://[^%s%]]+%s+([^%]]+)%]", "%1")
s = s:gsub("%[https?://[^%s%]]+%]", "")
s = s:gsub("%[https?://[^%s%]]+%]", "")
s = s:gsub("<br%s*/?>", "\n")
s = s:gsub("<[^>]+>", "")
s = s:gsub("<[^>]+>", "")
s = s:gsub("\r", "")


local cleanedLines = {}
local cleanedLines = {}


for line in mw.text.gsplit(s, "\n", true) do
for line in mw.text.gsplit(s, "\n", true) do
local trimmed = mw.text.trim(line)
local cleaned = trim(line)


if trimmed ~= ""
if cleaned ~= ""
and not trimmed:match("^__%u+__$")
and not cleaned:match("^__%u+__$")
and not trimmed:match("^%[%[[Cc]ategory:")
and not cleaned:match("^%[%[[Cc]ategory:")
and not trimmed:match("^%s*|")
and not cleaned:match("^%s*|")
and not trimmed:match("^%s*}") then
and not cleaned:match("^%s*}") then
table.insert(cleanedLines, trimmed)
table.insert(cleanedLines, cleaned)
end
end
end
end


return mw.text.trim(table.concat(cleanedLines, "\n\n"))
return trim(table.concat(cleanedLines, "\n\n"))
end
end


Line 102: Line 132:
end
end


local function normalizeFileName(value)
local function normalizeImageFile(value)
if not value or value == "" then
if not value or value == "" then
return nil
return nil
end
end


value = mw.text.trim(value)
value = trim(value)
value = value:gsub("<!%-%-.-%-%->", "")
value = value:gsub("<!%-%-.-%-%->", "")
value = mw.text.trim(value)
value = trim(value)


if value == "" then
if value == "" then
Line 115: Line 145:
end
end


local linkedFile =
local linked =
value:match("%[%[%s*[Ff]ile%s*:%s*([^|%]]+)")
value:match("%[%[%s*[Ff]ile%s*:%s*([^|%]]+)")
or value:match("%[%[%s*[Ii]mage%s*:%s*([^|%]]+)")
or value:match("%[%[%s*[Ii]mage%s*:%s*([^|%]]+)")


if linkedFile then
if linked then
value = linkedFile
value = linked
else
else
value = value:gsub("^%s*[Ff]ile%s*:%s*", "")
value = value:gsub("^%s*[Ff]ile%s*:%s*", "")
Line 128: Line 158:
end
end


value = mw.text.trim(value)
value = trim(value)


if value == "" then
if value == "" then
Line 136: Line 166:
local lower = mw.ustring.lower(value)
local lower = mw.ustring.lower(value)


if lower == "none" or lower == "no" or lower == "n/a" then
if lower == "none" or lower == "no" or lower == "n/a" or lower == "-" then
return nil
end
 
if not hasImageExtension(value) then
return nil
return nil
end
end


return "File:" .. value
return "File:" .. value
end
local function isImageParameterName(name)
if not name then
return false
end
name = mw.ustring.lower(trim(name))
return name == "image"
or name == "photo"
or name == "picture"
or name == "portrait"
or name == "logo"
or name == "flag"
or name == "emblem"
or name:match("^image%d+$")
or name:match("^photo%d+$")
or name:match("^picture%d+$")
or name:match("^portrait%d+$")
or name:match("^logo%d+$")
or name:match("^flag%d+$")
or name:match("^emblem%d+$")
end
end


Line 148: Line 205:
end
end


-- 1) First check normal infobox lines:
-- | image = Angelo Noord.png
-- | image = [[File:Angelo Noord.png]]
for line in mw.text.gsplit(wikitext, "\n", true) do
for line in mw.text.gsplit(wikitext, "\n", true) do
local value =
local name, value = line:match("^%s*|%s*([%w_%-]+)%s*=%s*(.-)%s*$")
line:match("^%s*|%s*image%d*%s*=%s*(.-)%s*$")
 
or line:match("^%s*|%s*photo%d*%s*=%s*(.-)%s*$")
if name and value and isImageParameterName(name) then
or line:match("^%s*|%s*portrait%d*%s*=%s*(.-)%s*$")
local file = normalizeImageFile(value)
or line:match("^%s*|%s*logo%d*%s*=%s*(.-)%s*$")
 
or line:match("^%s*|%s*flag%d*%s*=%s*(.-)%s*$")
if file then
or line:match("^%s*|%s*emblem%d*%s*=%s*(.-)%s*$")
return file
end
end
end


if value then
-- 2) Then check compact one-line infobox/template parameters:
local file = normalizeFileName(value)
-- {{Character|title1=Angelo Noord|image=Angelo Noord.png|...}}
for name, value in wikitext:gmatch("|%s*([%w_%-]+)%s*=%s*([^|\n}]+)") do
if isImageParameterName(name) then
local file = normalizeImageFile(value)


if file then
if file then
Line 166: Line 232:
end
end


local fallback =
-- 3) Then check explicit file links anywhere on the featured page.
wikitext:match("%[%[%s*[Ff]ile%s*:%s*([^|%]]+)")
for file in wikitext:gmatch("%[%[%s*[Ff]ile%s*:%s*([^|%]]+)") do
or wikitext:match("%[%[%s*[Ii]mage%s*:%s*([^|%]]+)")
local normalized = normalizeImageFile(file)
 
if normalized then
return normalized
end
end
 
for file in wikitext:gmatch("%[%[%s*[Ii]mage%s*:%s*([^|%]]+)") do
local normalized = normalizeImageFile(file)


if fallback then
if normalized then
return normalizeFileName(fallback)
return normalized
end
end
 
-- 4) Final fallback: scan any bare .png, .jpg, .jpeg, or .svg filename.
for file in wikitext:gmatch("([%w%s%._%-%(%)]+%.[Pp][Nn][Gg])") do
local normalized = normalizeImageFile(file)
 
if normalized then
return normalized
end
end
 
for file in wikitext:gmatch("([%w%s%._%-%(%)]+%.[Jj][Pp][Gg])") do
local normalized = normalizeImageFile(file)
 
if normalized then
return normalized
end
end
 
for file in wikitext:gmatch("([%w%s%._%-%(%)]+%.[Jj][Pp][Ee][Gg])") do
local normalized = normalizeImageFile(file)
 
if normalized then
return normalized
end
end
 
for file in wikitext:gmatch("([%w%s%._%-%(%)]+%.[Ss][Vv][Gg])") do
local normalized = normalizeImageFile(file)
 
if normalized then
return normalized
end
end
end


Line 215: Line 323:


return
return
'<div style="float:right; width:105px; margin:0 0 0.5em 0.75em; padding:4px; border:1px solid #a2a9b1; background:#f8f9fa; border-radius:6px; text-align:center;">' ..
'<div style="float:right; width:105px; margin:0 0 0.5em 0.8em; padding:4px; border:1px solid #a2a9b1; background:#f8f9fa; border-radius:6px; text-align:center;">' ..
'[[' .. data.image .. '|frameless|100x100px|link=' .. data.title .. '|' .. data.title .. ']]' ..
'[[' .. data.image .. '|frameless|100x100px|link=' .. data.title .. '|' .. data.title .. ']]' ..
'</div>'
'</div>'
Line 235: Line 343:
return data.image or ""
return data.image or ""
end
end
local imageBlock = makeImageBlock(data)


return
return
imageBlock ..
makeImageBlock(data) ..
"'''[[" .. data.title .. "]]'''" ..
"'''[[" .. data.title .. "]]'''" ..
'<div style="margin-top:0.35em; line-height:1.5;">' ..
'<div style="margin-top:0.35em; line-height:1.5;">' ..
Line 256: Line 362:
'</div>'
'</div>'
end
end
local imageBlock = makeImageBlock(data)


return
return
Line 263: Line 367:
'<div style="padding:0.5em 0.75em; font-size:110%; font-weight:bold; background:#eaecf0; border-bottom:1px solid #a2a9b1;">Featured page</div>' ..
'<div style="padding:0.5em 0.75em; font-size:110%; font-weight:bold; background:#eaecf0; border-bottom:1px solid #a2a9b1;">Featured page</div>' ..
'<div style="padding:0.8em; font-size:105%; line-height:1.5;">' ..
'<div style="padding:0.8em; font-size:105%; line-height:1.5;">' ..
imageBlock ..
makeImageBlock(data) ..
'<div style="font-size:110%; margin-bottom:0.35em;">' ..
'<div style="font-size:110%; margin-bottom:0.35em;">' ..
"'''[[" .. data.title .. "]]'''" ..
"'''[[" .. data.title .. "]]'''" ..

Revision as of 10:12, 14 May 2026


local p = {}

local POOL_TEMPLATE = "Featured pool"

local function trim(s)
	return mw.text.trim(s or "")
end

local function hasImageExtension(name)
	if not name or name == "" then
		return false
	end

	local lower = mw.ustring.lower(name)

	return lower:match("%.png$")
		or lower:match("%.jpg$")
		or lower:match("%.jpeg$")
		or lower:match("%.svg$")
end

local function getPool()
	local content = mw.getCurrentFrame():expandTemplate{ title = POOL_TEMPLATE }
	local pages = {}

	for line in mw.text.gsplit(content or "", "\n", true) do
		line = trim(line)
		line = line:gsub("<!%-%-.-%-%->", "")
		line = trim(line)

		if line ~= "" then
			line = line:gsub("^%*%s*", "")
			line = trim(line)

			local linkedTitle =
				line:match("^%[%[([^|%]]+)%]%]$")
				or line:match("^%[%[([^|%]]+)|.-%]%]$")

			if linkedTitle then
				line = linkedTitle
			end

			line = trim(line)

			if line ~= "" then
				table.insert(pages, line)
			end
		end
	end

	return pages
end

local function removeBalancedTemplates(s)
	local previous

	repeat
		previous = s
		s = s:gsub("%b{}", "")
	until s == previous

	return s
end

local function removeRefs(s)
	s = s:gsub("<ref[^>/]-/>", "")
	s = s:gsub("<ref.->.-</ref>", "")
	s = s:gsub("<ref.->", "")
	return s
end

local function removeFiles(s)
	s = s:gsub("%[%[%s*[Ff]ile%s*:.-%]%]", "")
	s = s:gsub("%[%[%s*[Ii]mage%s*:.-%]%]", "")
	return s
end

local function cleanLeadWikitext(s)
	if not s or s == "" then
		return ""
	end

	s = s:gsub("<!%-%-.-%-%->", "")
	s = removeRefs(s)
	s = removeFiles(s)
	s = removeBalancedTemplates(s)
	s = s:gsub("<br%s*/?>", "\n")
	s = s:gsub("<br%s*>", "\n")
	s = s:gsub("\r", "")

	s = s:gsub("'''", "")
	s = s:gsub("''", "")
	s = s:gsub("%[%[([^|%]]+)|([^%]]+)%]%]", "%2")
	s = s:gsub("%[%[([^%]]+)%]%]", "%1")
	s = s:gsub("%[https?://[^%s%]]+%s+([^%]]+)%]", "%1")
	s = s:gsub("%[https?://[^%s%]]+%]", "")
	s = s:gsub("<[^>]+>", "")

	local cleanedLines = {}

	for line in mw.text.gsplit(s, "\n", true) do
		local cleaned = trim(line)

		if cleaned ~= ""
			and not cleaned:match("^__%u+__$")
			and not cleaned:match("^%[%[[Cc]ategory:")
			and not cleaned:match("^%s*|")
			and not cleaned:match("^%s*}") then
			table.insert(cleanedLines, cleaned)
		end
	end

	return trim(table.concat(cleanedLines, "\n\n"))
end

local function extractLeadFromContent(wikitext)
	if not wikitext or wikitext == "" then
		return ""
	end

	local leadLines = {}

	for line in mw.text.gsplit(wikitext, "\n", true) do
		if line:match("^%s*==+%s*[^=].-==+%s*$") then
			break
		end

		table.insert(leadLines, line)
	end

	return cleanLeadWikitext(table.concat(leadLines, "\n"))
end

local function normalizeImageFile(value)
	if not value or value == "" then
		return nil
	end

	value = trim(value)
	value = value:gsub("<!%-%-.-%-%->", "")
	value = trim(value)

	if value == "" then
		return nil
	end

	local linked =
		value:match("%[%[%s*[Ff]ile%s*:%s*([^|%]]+)")
		or value:match("%[%[%s*[Ii]mage%s*:%s*([^|%]]+)")

	if linked then
		value = linked
	else
		value = value:gsub("^%s*[Ff]ile%s*:%s*", "")
		value = value:gsub("^%s*[Ii]mage%s*:%s*", "")
		value = value:gsub("|.*$", "")
		value = value:gsub("%]%].*$", "")
	end

	value = trim(value)

	if value == "" then
		return nil
	end

	local lower = mw.ustring.lower(value)

	if lower == "none" or lower == "no" or lower == "n/a" or lower == "-" then
		return nil
	end

	if not hasImageExtension(value) then
		return nil
	end

	return "File:" .. value
end

local function isImageParameterName(name)
	if not name then
		return false
	end

	name = mw.ustring.lower(trim(name))

	return name == "image"
		or name == "photo"
		or name == "picture"
		or name == "portrait"
		or name == "logo"
		or name == "flag"
		or name == "emblem"
		or name:match("^image%d+$")
		or name:match("^photo%d+$")
		or name:match("^picture%d+$")
		or name:match("^portrait%d+$")
		or name:match("^logo%d+$")
		or name:match("^flag%d+$")
		or name:match("^emblem%d+$")
end

local function extractImageFromContent(wikitext)
	if not wikitext or wikitext == "" then
		return nil
	end

	-- 1) First check normal infobox lines:
	-- | image = Angelo Noord.png
	-- | image = [[File:Angelo Noord.png]]
	for line in mw.text.gsplit(wikitext, "\n", true) do
		local name, value = line:match("^%s*|%s*([%w_%-]+)%s*=%s*(.-)%s*$")

		if name and value and isImageParameterName(name) then
			local file = normalizeImageFile(value)

			if file then
				return file
			end
		end
	end

	-- 2) Then check compact one-line infobox/template parameters:
	-- {{Character|title1=Angelo Noord|image=Angelo Noord.png|...}}
	for name, value in wikitext:gmatch("|%s*([%w_%-]+)%s*=%s*([^|\n}]+)") do
		if isImageParameterName(name) then
			local file = normalizeImageFile(value)

			if file then
				return file
			end
		end
	end

	-- 3) Then check explicit file links anywhere on the featured page.
	for file in wikitext:gmatch("%[%[%s*[Ff]ile%s*:%s*([^|%]]+)") do
		local normalized = normalizeImageFile(file)

		if normalized then
			return normalized
		end
	end

	for file in wikitext:gmatch("%[%[%s*[Ii]mage%s*:%s*([^|%]]+)") do
		local normalized = normalizeImageFile(file)

		if normalized then
			return normalized
		end
	end

	-- 4) Final fallback: scan any bare .png, .jpg, .jpeg, or .svg filename.
	for file in wikitext:gmatch("([%w%s%._%-%(%)]+%.[Pp][Nn][Gg])") do
		local normalized = normalizeImageFile(file)

		if normalized then
			return normalized
		end
	end

	for file in wikitext:gmatch("([%w%s%._%-%(%)]+%.[Jj][Pp][Gg])") do
		local normalized = normalizeImageFile(file)

		if normalized then
			return normalized
		end
	end

	for file in wikitext:gmatch("([%w%s%._%-%(%)]+%.[Jj][Pp][Ee][Gg])") do
		local normalized = normalizeImageFile(file)

		if normalized then
			return normalized
		end
	end

	for file in wikitext:gmatch("([%w%s%._%-%(%)]+%.[Ss][Vv][Gg])") do
		local normalized = normalizeImageFile(file)

		if normalized then
			return normalized
		end
	end

	return nil
end

local function getFeaturedData()
	local pages = getPool()

	if #pages == 0 then
		return nil, "No featured pages defined."
	end

	local day = tonumber(os.date("%j")) or 1
	local index = ((day - 1) % #pages) + 1
	local title = pages[index]

	local page = mw.title.new(title)
	local lead = ""
	local image = nil

	if page and page.exists then
		local content = page:getContent() or ""
		lead = extractLeadFromContent(content)
		image = extractImageFromContent(content)
	end

	if lead == "" then
		lead = "Featured page for " .. os.date("%d %B %Y") .. "."
	end

	return {
		title = title,
		lead = lead,
		image = image
	}
end

local function makeImageBlock(data)
	if not data.image then
		return ""
	end

	return
		'<div style="float:right; width:105px; margin:0 0 0.5em 0.8em; padding:4px; border:1px solid #a2a9b1; background:#f8f9fa; border-radius:6px; text-align:center;">' ..
		'[[' .. data.image .. '|frameless|100x100px|link=' .. data.title .. '|' .. data.title .. ']]' ..
		'</div>'
end

function p.main(frame)
	local args = frame and frame.args or {}
	local data, err = getFeaturedData()

	if not data then
		return err
	end

	if args.titleonly == "1" then
		return data.title
	end

	if args.imageonly == "1" then
		return data.image or ""
	end

	return
		makeImageBlock(data) ..
		"'''[[" .. data.title .. "]]'''" ..
		'<div style="margin-top:0.35em; line-height:1.5;">' ..
		data.lead ..
		'</div>'
end

function p.box()
	local data, err = getFeaturedData()

	if not data then
		return
			'<div style="width:100%; max-width:820px; margin:0.5em 0; border:1px solid #a2a9b1; border-radius:8px; background:#ffffff; overflow:hidden;">' ..
			'<div style="padding:0.5em 0.75em; font-size:110%; font-weight:bold; background:#eaecf0; border-bottom:1px solid #a2a9b1;">Featured page</div>' ..
			'<div style="padding:0.8em;">' .. err .. '</div>' ..
			'</div>'
	end

	return
		'<div style="width:100%; max-width:820px; margin:0.5em 0; border:1px solid #a2a9b1; border-radius:8px; background:#ffffff; overflow:hidden;">' ..
		'<div style="padding:0.5em 0.75em; font-size:110%; font-weight:bold; background:#eaecf0; border-bottom:1px solid #a2a9b1;">Featured page</div>' ..
		'<div style="padding:0.8em; font-size:105%; line-height:1.5;">' ..
		makeImageBlock(data) ..
		'<div style="font-size:110%; margin-bottom:0.35em;">' ..
		"'''[[" .. data.title .. "]]'''" ..
		'</div>' ..
		'<div>' .. data.lead .. '</div>' ..
		'<div style="margin-top:0.6em; clear:both;">[[:' .. data.title .. '|Open page]]</div>' ..
		'</div>' ..
		'</div>'
end

return p