Module:DynamicYSWSList: Difference between revisions
U080F22CTJN (talk | contribs) mNo edit summary |
U080F22CTJN (talk | contribs) m does this work |
||
| (2 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
-- Module:DynamicYSWSList | |||
-- Public API only. Wikitext table avoids unsupported HTML tags in MediaWiki. | |||
local p = {} | local p = {} | ||
local API_URL = 'https://hackclub.com/api/v1/events' | |||
local MONTHS = {'January','February','March','April','May','June','July','August','September','October','November','December'} | |||
local function str(v) | |||
local function | if type(v) ~= 'string' and type(v) ~= 'number' then return '' end | ||
if | return mw.text.trim(tostring(v)) | ||
return mw.text. | |||
end | end | ||
local function first(...) | |||
function | for i = 1, select('#', ...) do | ||
local data, | local v = str(select(i, ...)) | ||
url = | if v ~= '' then return v end | ||
end | |||
return '' | |||
end | |||
local function safe(v) | |||
-- Escape user/API text so it cannot inject table delimiters or wiki markup. | |||
return mw.text.nowiki(str(v)) | |||
end | |||
local function isoDate(v) | |||
local y,m,d = str(v):match('^(%d%d%d%d)%-(%d%d)%-(%d%d)') | |||
if not y then return nil end | |||
y,m,d = tonumber(y),tonumber(m),tonumber(d) | |||
if m < 1 or m > 12 then return nil end | |||
local lengths = {31,28,31,30,31,30,31,31,30,31,30,31} | |||
if y % 4 == 0 and (y % 100 ~= 0 or y % 400 == 0) then lengths[2] = 29 end | |||
if d < 1 or d > lengths[m] then return nil end | |||
return string.format('%04d-%02d-%02d',y,m,d) | |||
end | |||
local function displayDate(date) | |||
if not date then return '—' end | |||
local y,m,d = date:match('^(%d%d%d%d)%-(%d%d)%-(%d%d)$') | |||
return string.format('%d %s %s', tonumber(d), MONTHS[tonumber(m)], y) | |||
end | |||
local function dateCell(date, class) | |||
return '| data-sort-value="' .. (date or '9999-12-31') .. '" class="' .. class .. '" | ' .. displayDate(date) | |||
end | |||
local function validUrl(v) | |||
local url = str(v) | |||
if url:match('^https?://[^%s%[%]<>|{}"\']+$') then return url end | |||
return nil | |||
end | |||
local function description(v) | |||
local s = str(v):gsub('<[^>]*>', ''):gsub('%s+', ' ') | |||
s = mw.text.trim(mw.text.decode(s)) | |||
if mw.ustring.len(s) > 240 then s = mw.ustring.sub(s, 1, 237) .. '…' end | |||
return s | |||
end | |||
local function fetch() | |||
if not (mw.ext and mw.ext.externalData and type(mw.ext.externalData.getExternalData) == 'function') then | |||
return nil, 'External Data is unavailable.' | |||
end | |||
local ok, rows = pcall(mw.ext.externalData.getExternalData, { | |||
url = API_URL, | |||
format = 'json with jsonpath', | format = 'json with jsonpath', | ||
data = | data = { | ||
name = '$.data[*].name', | |||
} | description = '$.data[*].description', | ||
url = '$.data[*].url', | |||
if not | startDate = '$.data[*].startDate', | ||
endDate = '$.data[*].endDate' | |||
} | |||
}) | |||
if | if not ok or type(rows) ~= 'table' then return nil, 'Could not load public events.' end | ||
return rows | |||
end | |||
function p.main(frame) | |||
local data, err = fetch() | |||
if not data then return '<strong class="error">' .. safe(err) .. '</strong>' end | |||
local programs = {} | |||
for _, row in ipairs(data) do | |||
if type(row) == 'table' then | |||
local name = first(row.name, row.title) | |||
if name ~= '' then | |||
programs[#programs + 1] = { | |||
name = name, | |||
description = description(first(row.description, row.summary)), | |||
startDate = isoDate(first(row.startDate,row.start_date,row.start)), | |||
endDate = isoDate(first(row.endDate,row.end_date,row['end'])), | |||
url = validUrl(first(row.url,row.website,row.link)) | |||
} | |||
end | |||
end | |||
end | end | ||
table.sort(programs, function(a,b) | |||
return mw.ustring.lower(a.name) < mw.ustring.lower(b.name) | |||
end) | |||
if #programs == 0 then return 'No public programs were returned.' end | |||
local out = { | local out = { | ||
'{| class="wikitable sortable"', | '<div class="ysws-directory">', | ||
'! | '<p class="ysws-directory-count">' .. #programs .. ' public event entries listed.</p>', | ||
'<div class="ysws-table-wrapper">', | |||
'{| class="wikitable sortable ysws-table"', | |||
'! Program\n! Description\n! Start date\n! End date\n! Status\n! Website' | |||
} | } | ||
for _, | for _, item in ipairs(programs) do | ||
out[#out+1] = '|- class="ysws-program" data-start="' .. (item.startDate or '') .. '" data-end="' .. (item.endDate or '') .. '"' | |||
out[#out+1] = '! scope="row" class="ysws-name" | [[' .. item.name .. '|' .. safe(item.name) .. ']]' | |||
out[#out+1] = '| class="ysws-description" | ' .. (item.description ~= '' and safe(item.description) or '—') | |||
out[#out+1] = dateCell(item.startDate, 'ysws-start') | |||
out[#out+1] = dateCell(item.endDate, 'ysws-end') | |||
out[#out+1] = '| class="ysws-status" | Unknown' | |||
out[#out+1] = '| class="ysws-website" | ' .. (item.url and ('[' .. item.url .. ' Website]') or '—') | |||
end | end | ||
out[#out + 1] = '|}' | out[#out+1] = '|}' | ||
out[#out+1] = '</div>' | |||
out[#out+1] = '<p class="ysws-source-note">Generated from the public Hack Club events API. Dates and descriptions reflect the public feed.</p>' | |||
out[#out+1] = '</div>' | |||
return frame:preprocess(table.concat(out, '\n')) | return frame:preprocess(table.concat(out, '\n')) | ||
end | end | ||
return p | return p | ||