Module:ArchiveLink
From the only original and legitimate Battlestar Wiki: the free-as-in-beer, non-corporate, open-content encyclopedia, analytical reference, and episode guide on all things Battlestar Galactica. Accept neither subpar substitutes nor subpar clones.
More actions
Documentation for this module may be created at Module:ArchiveLink/doc
--[[
================================================================================
Module:ArchiveLink — BattlestarWiki ArchiveLink extension
================================================================================
Public entry points ({{#invoke:ArchiveLink|functionName|...}}):
linkStatus inline status badge for a single URL
statusIcon just the emoji icon, no label
sourcesLink wikilink to the Sources: archive page
captureStatus "✅ Archived" / "❌ Not archived" indicator
urlHealth compact one-liner: icon + status + archive note
linkTable wikitable of all tracked URLs on a page
All reads go through the DB cache (mw.ext.ArchiveLink PHP bridge).
No live HTTP checks happen at render time.
================================================================================
--]]
local ArchiveLink = {}
-- ---------------------------------------------------------------------------
-- Bridge accessor
-- ---------------------------------------------------------------------------
local function bridge()
return mw.ext and mw.ext.ArchiveLink or nil
end
-- ---------------------------------------------------------------------------
-- Constants
-- ---------------------------------------------------------------------------
local ICON = { live='🟢', dead='🔴', redirected='🟡', unknown='⚪' }
local CLS = { live='archivelink-status--live', dead='archivelink-status--dead',
redirected='archivelink-status--redirect', unknown='archivelink-status--unknown' }
local LABEL = { live='Live', dead='Dead', redirected='Redirect', unknown='Unknown' }
local CICON = { live='📦', wayback='📅' }
-- ---------------------------------------------------------------------------
-- Helpers
-- ---------------------------------------------------------------------------
local function trim(s) return s and s:match('^%s*(.-)%s*$') or '' end
local function esc(s)
s = tostring(s or '')
return s:gsub('&','&'):gsub('<','<'):gsub('>','>')
:gsub('%[%[','[['):gsub('{{','{{')
end
local function param(args, ...)
for _,k in ipairs{...} do
local v = trim(args[k] or '')
if v ~= '' then return v end
end
return nil
end
local MONTHS = {'January','February','March','April','May','June',
'July','August','September','October','November','December'}
local function fmtDate(ts)
if not ts or ts == '' or ts == false then return '' end
local y,m,d = tostring(ts):match('^(%d%d%d%d)-(%d%d)-(%d%d)')
if not y then y,m,d = tostring(ts):match('^(%d%d%d%d)(%d%d)(%d%d)') end
if not y then return tostring(ts) end
return tostring(tonumber(d))..' '..(MONTHS[tonumber(m)] or m)..' '..y
end
local function srcLink(slug, label)
if not slug or slug == '' then return '' end
return '[[Sources:'..slug..'|'..(label or ('Sources:'..slug))..']]'
end
local function getStatus(url)
local b = bridge()
if not b then return {status='unknown'} end
return b.getStatus(url) or {status='unknown'}
end
local function getCapture(url)
local b = bridge()
if not b then return nil end
return b.getCapture(url)
end
-- ---------------------------------------------------------------------------
-- 1. linkStatus
-- {{#invoke:ArchiveLink|linkStatus|url=...|label=...|noicon=1|nolink=1}}
-- ---------------------------------------------------------------------------
function ArchiveLink.linkStatus(frame)
local args = (frame:getParent() and frame:getParent().args) or frame.args
local url = trim(args.url or args[1] or '')
if url == '' then return '<span class="error">ArchiveLink: no URL</span>' end
local showIcon = not param(args,'noicon')
local showLink = not param(args,'nolink')
local custLbl = param(args,'label')
local row = getStatus(url)
local status = row.status or 'unknown'
local icon = ICON[status] or '⚪'
local cls = CLS[status] or CLS.unknown
local lbl = custLbl or LABEL[status] or status
local codeSfx = ''
if row.http_code and row.http_code ~= false and row.http_code ~= 0 then
codeSfx = ' <small>('..tostring(row.http_code)..')</small>'
end
local badge = '<span class="archivelink-status-badge '..cls..'">'
..(showIcon and (icon..' ') or '')
..esc(lbl)..codeSfx..'</span>'
local note = ''
if showLink then
local cap = getCapture(url)
if cap and cap.sources_page then
local ci = CICON[cap.capture_type] or '📦'
local dt = fmtDate(cap.captured_at or '')
local link = srcLink(cap.sources_page)
note = ' <span class="archivelink-archived-note">'..ci..' '..link
..(dt~='' and (' <small>('..dt..')</small>') or '')..'</span>'
elseif status == 'dead' then
if row.wayback_available then
note = ' <span class="archivelink-wayback-note">📅 <small>available on Wayback</small></span>'
else
note = ' <span class="archivelink-unarchived-note"><small>(not archived)</small></span>'
end
end
end
return frame:preprocess(badge..note)
end
-- ---------------------------------------------------------------------------
-- 2. statusIcon — just the emoji
-- {{#invoke:ArchiveLink|statusIcon|url=...}}
-- ---------------------------------------------------------------------------
function ArchiveLink.statusIcon(frame)
local args = (frame:getParent() and frame:getParent().args) or frame.args
local url = trim(args.url or args[1] or '')
if url == '' then return '⚪' end
return ICON[getStatus(url).status] or '⚪'
end
-- ---------------------------------------------------------------------------
-- 3. sourcesLink — wikilink to Sources: page
-- {{#invoke:ArchiveLink|sourcesLink|url=...|label=...}}
-- ---------------------------------------------------------------------------
function ArchiveLink.sourcesLink(frame)
local args = (frame:getParent() and frame:getParent().args) or frame.args
local url = trim(args.url or args[1] or '')
local label = param(args,'label')
if url == '' then return '' end
local cap = getCapture(url)
if not cap or not cap.sources_page then return '' end
return frame:preprocess(srcLink(cap.sources_page, label))
end
-- ---------------------------------------------------------------------------
-- 4. captureStatus — archived / not archived
-- {{#invoke:ArchiveLink|captureStatus|url=...}}
-- ---------------------------------------------------------------------------
function ArchiveLink.captureStatus(frame)
local args = (frame:getParent() and frame:getParent().args) or frame.args
local url = trim(args.url or args[1] or '')
if url == '' then return '<span class="error">ArchiveLink: no URL</span>' end
local cap = getCapture(url)
if cap and cap.sources_page then
local ci = CICON[cap.capture_type] or '📦'
local dt = fmtDate(cap.captured_at or '')
local link = srcLink(cap.sources_page,'view')
local when = dt~='' and (' '..dt) or ''
return frame:preprocess(
'<span class="archivelink-capture-status archivelink-capture-status--yes">'
..'✅ Archived'..when..' '..ci..' ('..link..')</span>'
)
else
local row = getStatus(url)
local wb = row.wayback_available
and ' <small>(Wayback available)</small>' or ''
return '<span class="archivelink-capture-status archivelink-capture-status--no">'
..'❌ Not archived'..wb..'</span>'
end
end
-- ---------------------------------------------------------------------------
-- 5. urlHealth — compact one-liner
-- {{#invoke:ArchiveLink|urlHealth|url=...}}
-- ---------------------------------------------------------------------------
function ArchiveLink.urlHealth(frame)
local args = (frame:getParent() and frame:getParent().args) or frame.args
local url = trim(args.url or args[1] or '')
if url == '' then return '' end
local row = getStatus(url)
local status = row.status or 'unknown'
local parts = { (ICON[status] or '⚪')..' '..(LABEL[status] or status) }
local cap = getCapture(url)
if cap and cap.sources_page then
local ci = CICON[cap.capture_type] or '📦'
table.insert(parts, ci..' '..srcLink(cap.sources_page,'archived'))
elseif status == 'dead' or status == 'unknown' then
if row.wayback_available then
table.insert(parts, '📅 <small>Wayback available</small>')
else
table.insert(parts, '<small>not archived</small>')
end
end
return frame:preprocess(
'<span class="archivelink-url-health">'
..table.concat(parts,' · ')..'</span>'
)
end
-- ---------------------------------------------------------------------------
-- 6. linkTable — full wikitable of all URLs on a page
-- {{#invoke:ArchiveLink|linkTable|page=...|compact=1|caption=...}}
-- ---------------------------------------------------------------------------
function ArchiveLink.linkTable(frame)
local args = (frame:getParent() and frame:getParent().args) or frame.args
local pageTitle = param(args,'page') or mw.title.getCurrentTitle().prefixedText
local compact = param(args,'compact') ~= nil
local caption = param(args,'caption') or ''
local b = bridge()
if not b then
return '<span class="error">ArchiveLink extension not loaded</span>'
end
local urls = b.getPageLinks(pageTitle)
if not urls or #urls == 0 then
return '<span class="archivelink-table-empty">No tracked external links on this page.</span>'
end
-- Header
local hdrs = compact
and {'URL','Status','Archived'}
or {'URL','Status','Last checked','Archived'}
local lines = {
'{| class="wikitable archivelink-link-table" style="width:100%"',
caption~='' and ('|+ '..esc(caption)) or nil,
'|-',
'! '..table.concat(hdrs,' !! '),
}
-- remove nil
local clean = {}
for _,v in ipairs(lines) do if v then table.insert(clean,v) end end
lines = clean
for _, url in ipairs(urls) do
local row = getStatus(url)
local cap = getCapture(url)
local status = (row and row.status) or 'unknown'
local icon = ICON[status] or '⚪'
local cls = CLS[status] or CLS.unknown
-- URL cell
local disp = url
if #disp > 70 then disp = disp:sub(1,67)..'…' end
local urlCell = '['..url..' <code>'..esc(disp)..'</code>]'
-- Status cell
local stCell = '<span class="archivelink-status-badge '..cls..'">'
..icon..' '..(LABEL[status] or status)..'</span>'
if row and row.http_code and row.http_code ~= false then
stCell = stCell..' <small>('..tostring(row.http_code)..')</small>'
end
-- Archived cell
local archCell
if cap and cap.sources_page then
local ci = CICON[cap.capture_type] or '📦'
local dt = fmtDate(cap.captured_at or '')
archCell = '✅ '..ci..' '..srcLink(cap.sources_page)
..(dt~='' and ('<br><small>'..dt..'</small>') or '')
elseif row and row.wayback_available then
archCell = '📅 <small>Wayback only</small>'
else
archCell = '❌ <small>—</small>'
end
-- Checked cell
local chkCell = ''
if not compact then
if row and row.status_checked and row.status_checked ~= false then
chkCell = '<small>'..fmtDate(row.status_checked)..'</small>'
else
chkCell = '<small>never</small>'
end
end
local cells = compact
and {urlCell, stCell, archCell}
or {urlCell, stCell, chkCell, archCell}
table.insert(lines, '|-')
table.insert(lines, '| '..table.concat(cells,' || '))
end
table.insert(lines, '|}')
return frame:preprocess(table.concat(lines,'\n'))
end
-- ---------------------------------------------------------------------------
return ArchiveLink