Module:DailyFeatured: Difference between revisions
Appearance
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 = | line = trim(line) | ||
line = line:gsub("<!%-%-.-%-%->", "") | line = line:gsub("<!%-%-.-%-%->", "") | ||
line = | line = trim(line) | ||
if line ~= "" then | if line ~= "" then | ||
line = line:gsub("^%*%s*", "") | line = line:gsub("^%*%s*", "") | ||
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 = | line = trim(line) | ||
if line ~= "" then | if line ~= "" then | ||
| Line 34: | Line 52: | ||
end | end | ||
local function | 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 | s = removeRefs(s) | ||
s = s | s = removeFiles(s) | ||
s = s:gsub("< | s = removeBalancedTemplates(s) | ||
s = s:gsub("% | s = s:gsub("<br%s*/?>", "\n") | ||
s = s:gsub(" | s = s:gsub("<br%s*>", "\n") | ||
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("<[^>]+>", "") | s = s:gsub("<[^>]+>", "") | ||
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 | local cleaned = trim(line) | ||
if | if cleaned ~= "" | ||
and not | and not cleaned:match("^__%u+__$") | ||
and not | and not cleaned:match("^%[%[[Cc]ategory:") | ||
and not | and not cleaned:match("^%s*|") | ||
and not | and not cleaned:match("^%s*}") then | ||
table.insert(cleanedLines, | table.insert(cleanedLines, cleaned) | ||
end | end | ||
end | end | ||
return | return trim(table.concat(cleanedLines, "\n\n")) | ||
end | end | ||
| Line 102: | Line 132: | ||
end | end | ||
local function | local function normalizeImageFile(value) | ||
if not value or value == "" then | if not value or value == "" then | ||
return nil | return nil | ||
end | end | ||
value = | value = trim(value) | ||
value = value:gsub("<!%-%-.-%-%->", "") | value = value:gsub("<!%-%-.-%-%->", "") | ||
value = | value = trim(value) | ||
if value == "" then | if value == "" then | ||
| Line 115: | Line 145: | ||
end | end | ||
local | 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 | if linked then | ||
value = | 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 = | 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*$") | ||
if name and value and isImageParameterName(name) then | |||
local file = normalizeImageFile(value) | |||
if file then | |||
return file | |||
end | |||
end | |||
end | |||
if | -- 2) Then check compact one-line infobox/template parameters: | ||
local file = | -- {{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 | ||
-- 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 | if normalized then | ||
return | 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. | '<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 | ||
return | return | ||
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 | ||
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;">' .. | ||
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