Module:DynamicYSWSList: Difference between revisions

mNo edit summary
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'}


-- one cell: collapse newlines (they break rows), nowiki anything markup-shaped
local function str(v)
local function cell(s)
     if type(v) ~= 'string' and type(v) ~= 'number' then return '' end
     if s == nil or s == '' then return '—' end
     return mw.text.trim(tostring(v))
     return mw.text.nowiki((tostring(s):gsub('[\r\n]+', ' ')))
end
end
 
local function first(...)
function p.fetchlist(frame)
    for i = 1, select('#', ...) do
     local data, errors = mw.ext.externalData.getExternalData{
        local v = str(select(i, ...))
         url = 'https://hackclub.com/api/v1/events',
        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 = 'name=$.data[*].name,description=$.data[*].description,link=$.data[*].url,'
         data = {
             .. 'start=$.data[*].startDate,finish=$.data[*].endDate,status=$.data[*].status'
            name = '$.data[*].name',
     }
            description = '$.data[*].description',
 
            url = '$.data[*].url',
     if not data or #data == 0 then
             startDate = '$.data[*].startDate',
        local msgs = {}
            endDate = '$.data[*].endDate'
        for _, m in pairs(errors or {}) do msgs[#msgs + 1] = m end
        }
        local msg = '<span class="error">Error fetch failed</span>'
     })
         if #msgs > 0 then msg = msg .. '<br>(' .. table.concat(msgs, '; ') .. ')' end
     if not ok or type(rows) ~= 'table' then return nil, 'Could not load public events.' end
         return msg
    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)
    local want = frame.args and frame.args.status  -- optional, e.g. |status=ongoing
        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">',
         '! Event !! Description !! Status !! Starts !! Ends'
        '<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 _, row in ipairs(data) do
     for _, item in ipairs(programs) do
         if not want or row.status == want then
         out[#out+1] = '|- class="ysws-program" data-start="' .. (item.startDate or '') .. '" data-end="' .. (item.endDate or '') .. '"'
            local name = cell(row.name)
        out[#out+1] = '! scope="row" class="ysws-name" | [[' .. item.name .. '|' .. safe(item.name) .. ']]'
            local title = (row.link and row.link ~= '') and ('[' .. row.link .. ' ' .. name .. ']') or name
        out[#out+1] = '| class="ysws-description" | ' .. (item.description ~= '' and safe(item.description) or '—')
            out[#out + 1] = '|-\n| ' .. title
        out[#out+1] = dateCell(item.startDate, 'ysws-start')
                .. '\n| ' .. cell(row.description)
        out[#out+1] = dateCell(item.endDate, 'ysws-end')
                .. '\n| ' .. cell(row.status)
        out[#out+1] = '| class="ysws-status" | Unknown'
                .. '\n| ' .. cell(row.start)
        out[#out+1] = '| class="ysws-website" | ' .. (item.url and ('[' .. item.url .. ' Website]') or '—')
                .. '\n| ' .. cell(row.finish)
        end
     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