Module:PersonRelatedNews: Difference between revisions

From TwogPedia
No edit summary
Tag: Reverted
No edit summary
 
(3 intermediate revisions by the same user not shown)
Line 94: Line 94:
     return os.date("%b %d, %Y", time)
     return os.date("%b %d, %Y", time)
end
end
-- function getRelatedNews(limit, title)
--    local tables = 'News'
--    local fields = '_pageName, date, tags, image, category, content'
--    local words = mw.text.split(title, ' ')
--    local conditions = {}
--    for _, word in ipairs(words) do
--        if #word > 2 then
--            table.insert(conditions, '_pageName LIKE "%' .. word .. '%"')
--        end
--    end
--    local whereClause = table.concat(conditions, ' OR ')
--    local cargoArgs = {
--        where = whereClause,
--        orderBy = 'date DESC',
--        limit = limit
--    }
--    local results = cargo.query(tables, fields, cargoArgs)
--    return results or {}
-- end


function getRelatedNews(limit, title)
function getRelatedNews(limit, title)
     local tables = 'News'
     local tables = 'News'
     local fields = '_pageName, date, tags, image, category, content'
     local fields = '_pageName, date, tags, image, category, content'
    limit = tonumber(limit) or 4


     local words = mw.text.split(title, ' ')
    -- Local helpers (scoped to this function)
     local conditions = {}
     local STOP = {
        ["the"]=true,["and"]=true,["for"]=true,["with"]=true,["from"]=true,["into"]=true,
        ["of"]=true,["in"]=true,["on"]=true,["at"]=true,["to"]=true,["by"]=true,["is"]=true,["are"]=true,
        ["a"]=true,["an"]=true,["as"]=true,["or"]=true,["if"]=true,["but"]=true,["not"]=true
    }
    local function escape_like(s)
        return (s or ""):gsub("%%","%%%%"):gsub("_","__"):gsub('"','\\"')
    end
    local function normalize(s)
        s = mw.ustring.lower(s or "")
        s = s:gsub("^[^:]+:", "")      -- strip namespace if present
        s = s:gsub("[%p%c]", " ")      -- punctuation/control -> space
        s = s:gsub("%s+", " ")
        return mw.text.trim(s)
    end
    local function terms_from_title(t)
        local out, seen = {}, {}
        for w in mw.ustring.gmatch(normalize(t), "[%w%d]+") do
            if #w >= 3 and not STOP[w] and not seen[w] then
                seen[w] = true
                table.insert(out, w)
            end
        end
        return out
    end
    -- Simple deterministic RNG (LCG) so "randoms" are stable per page title
    local function stable_seed(s)
        local h = 0
        for i = 1, #s do
            h = (h * 131 + string.byte(s, i)) % 2147483647
        end
        if h == 0 then h = 1 end
        return h
    end
    local function shuffle_in_place(t, seed)
        local mod = 2147483647
        local a, c = 1103515245, 12345
        local function rnd()
            seed = (a * seed + c) % mod
            return seed / mod
        end
        for i = #t, 2, -1 do
            local j = math.floor(rnd() * i) + 1
            t[i], t[j] = t[j], t[i]
        end
    end
 
    local current = mw.title.getCurrentTitle().fullText
     local terms = terms_from_title(title)


     for _, word in ipairs(words) do
     -- Build strict "related" filter: require >=2 distinct term matches (tags or title)
         if #word > 4 then
    local results, picked = {}, {}
             table.insert(conditions, '_pageName LIKE "%' .. word .. '%"')
    if #terms >= 2 then
        table.sort(terms, function(a,b) return #a > #b end)
        local MAX_TERMS = 6
        while #terms > MAX_TERMS do table.remove(terms) end
 
        local pairClauses = {}
         for i = 1, #terms - 1 do
            local ti = escape_like(terms[i])
             local left = string.format('(tags HOLDS LIKE "%%%s%%" OR _pageName LIKE "%%%s%%")', ti, ti)
            for j = i + 1, #terms do
                local tj = escape_like(terms[j])
                local right = string.format('(tags HOLDS LIKE "%%%s%%" OR _pageName LIKE "%%%s%%")', tj, tj)
                table.insert(pairClauses, '(' .. left .. ' AND ' .. right .. ')')
            end
        end
 
        local whereClause = '(' .. table.concat(pairClauses, ' OR ') ..
                            string.format(') AND _pageName != "%s"', escape_like(current))
 
        local related = mw.ext.cargo.query(tables, fields, {
            where = whereClause,
            orderBy = 'date DESC',
            limit = limit * 2
        }) or {}
 
        for _, r in ipairs(related) do
            if not picked[r._pageName] then
                table.insert(results, r)
                picked[r._pageName] = true
                if #results >= limit then break end
            end
         end
         end
     end
     end


     local whereClause = table.concat(conditions, ' OR ')
     -- If not enough related, fill with random recent (excluding current & duplicates)
    if #results < limit then
        local recent = mw.ext.cargo.query(tables, fields, {
            orderBy = 'date DESC',
            limit = math.max(limit * 10, 40)  -- pull a pool to randomize
        }) or {}
 
        -- filter out current and already picked
        local pool = {}
        for _, r in ipairs(recent) do
            if r._pageName ~= current and not picked[r._pageName] then
                table.insert(pool, r)
            end
        end


    local cargoArgs = {
        if #pool > 0 then
        where = whereClause,
            shuffle_in_place(pool, stable_seed(current))
        orderBy = 'date DESC',
            for _, r in ipairs(pool) do
        limit = limit
                table.insert(results, r)
     }
                picked[r._pageName] = true
                if #results >= limit then break end
            end
        end
     end


     local results = cargo.query(tables, fields, cargoArgs)
     -- Final guarantee: never empty. If still short (very small dataset), just return what we have.
     return results or {}
     return results
end
end


return p
return p

Latest revision as of 08:36, 23 October 2025

Documentation for this module may be created at Module:PersonRelatedNews/doc

local cargo = mw.ext.cargo
local p = {}

function p.main(frame)
    local newsItemContainer = mw.html.create('div'):addClass('related__news')

    local title = mw.title.getCurrentTitle().fullText
    local newsTitle = "Related news"
    -- if title:match("^Companies/") then
    --     newsTitle = "Team News"
    -- end

    local iconHtml = '[[File:News-icon.png|30px|alt=' .. newsTitle .. ' Icon]]'
    local iconElement = mw.html.create('h3'):addClass('news-icon'):wikitext(iconHtml .. ' ' .. newsTitle)

    local titleContainer = mw.html.create('div'):addClass('related__news__title-container'):attr('style', 'display: flex; align-items: center;')
    titleContainer:node(iconElement)

    -- local viewAllElement = mw.html.create('div'):addClass('related__news__view-all'):attr('id', 'view-all-button'):wikitext('View All')
    local viewAllElement = mw.html.create('span')
    :addClass('related__news__view-all')
    :attr('id', 'view-all-button')
    :wikitext('[[News|View All]]')


    titleContainer:node(viewAllElement)

    local container = mw.html.create('div'):addClass('related__news__container'):node(titleContainer):node(newsItemContainer)

    local relatedNews = getRelatedNews(4, title)

    if #relatedNews > 0 then
        for i = 1, #relatedNews do
            addArticleToContainer(relatedNews[i], newsItemContainer)
        end
    end

    return container
end

function addArticleToContainer(result, container)
    local image = result.image or 'News placeholder.png'
    local link = result._pageName
    local category = result.category and mw.text.split(result.category, ',')[1] or 'Uncategorized'

    local categoryClassMap = {
        ['News'] = 'news-category-news',
        ['Transfer Market'] = 'news-category-transfer',
        ['Drama'] = 'news-category-drama',
        ['Business'] = 'news-category-business',
        ['Sponsorships'] = 'news-category-sponsorships'
    }

    local categoryClass = categoryClassMap[category] or 'default-category-class'
    
    local date = result.date and formatDate(result.date) or 'Unknown date'
    
    local title = mw.html.create('div')
	    :addClass('news-title-twogpedia')
	    :wikitext('[[' .. result._pageName .. '|' .. mw.ext.displaytitle.get(result._pageName) .. ']]')

    local metaContainer = mw.html.create('div'):addClass('news-meta-container')
    local categoryElement = mw.html.create('p'):addClass('news-meta news-category-meta ' .. categoryClass):wikitext(category)
    local dateElement = mw.html.create('p'):addClass('news-meta news-date'):wikitext(date)
    
    metaContainer:node(categoryElement):node(dateElement)

    local contentSnippet = getContentSnippet(result.content)
    local contentElement = mw.html.create('p'):addClass('news-content'):wikitext(contentSnippet)

    local imageHtml = '[[File:' .. image .. '|link=' .. link .. '|800px|class=news-image|alt=News Image]]'
    
    local imageContainer = mw.html.create('div'):addClass('news-image-container'):wikitext(imageHtml)
    
    local newsItem = mw.html.create('div')
        :addClass('related__article')
        :node(imageContainer)
        :node(title)
        :node(contentElement)
        :node(metaContainer)

    container:node(newsItem)
end

function getContentSnippet(content)
    local words = mw.text.split(content, ' ')
    local snippet = table.concat(words, ' ', 1, math.min(20, #words))
    return snippet .. ( #words > 20 and '...' or '' ) 
end

function formatDate(dateStr)
    local year, month, day = dateStr:match("(%d+)%-(%d+)%-(%d+)")
    local time = os.time({year = year, month = month, day = day})
    return os.date("%b %d, %Y", time)
end

-- function getRelatedNews(limit, title)
--     local tables = 'News'
--     local fields = '_pageName, date, tags, image, category, content'

--     local words = mw.text.split(title, ' ')
--     local conditions = {}

--     for _, word in ipairs(words) do
--         if #word > 2 then 
--             table.insert(conditions, '_pageName LIKE "%' .. word .. '%"')
--         end
--     end

--     local whereClause = table.concat(conditions, ' OR ')

--     local cargoArgs = {
--         where = whereClause,
--         orderBy = 'date DESC',
--         limit = limit
--     }

--     local results = cargo.query(tables, fields, cargoArgs)
--     return results or {}
-- end

function getRelatedNews(limit, title)
    local tables = 'News'
    local fields = '_pageName, date, tags, image, category, content'
    limit = tonumber(limit) or 4

    -- Local helpers (scoped to this function)
    local STOP = {
        ["the"]=true,["and"]=true,["for"]=true,["with"]=true,["from"]=true,["into"]=true,
        ["of"]=true,["in"]=true,["on"]=true,["at"]=true,["to"]=true,["by"]=true,["is"]=true,["are"]=true,
        ["a"]=true,["an"]=true,["as"]=true,["or"]=true,["if"]=true,["but"]=true,["not"]=true
    }
    local function escape_like(s)
        return (s or ""):gsub("%%","%%%%"):gsub("_","__"):gsub('"','\\"')
    end
    local function normalize(s)
        s = mw.ustring.lower(s or "")
        s = s:gsub("^[^:]+:", "")      -- strip namespace if present
        s = s:gsub("[%p%c]", " ")      -- punctuation/control -> space
        s = s:gsub("%s+", " ")
        return mw.text.trim(s)
    end
    local function terms_from_title(t)
        local out, seen = {}, {}
        for w in mw.ustring.gmatch(normalize(t), "[%w%d]+") do
            if #w >= 3 and not STOP[w] and not seen[w] then
                seen[w] = true
                table.insert(out, w)
            end
        end
        return out
    end
    -- Simple deterministic RNG (LCG) so "randoms" are stable per page title
    local function stable_seed(s)
        local h = 0
        for i = 1, #s do
            h = (h * 131 + string.byte(s, i)) % 2147483647
        end
        if h == 0 then h = 1 end
        return h
    end
    local function shuffle_in_place(t, seed)
        local mod = 2147483647
        local a, c = 1103515245, 12345
        local function rnd()
            seed = (a * seed + c) % mod
            return seed / mod
        end
        for i = #t, 2, -1 do
            local j = math.floor(rnd() * i) + 1
            t[i], t[j] = t[j], t[i]
        end
    end

    local current = mw.title.getCurrentTitle().fullText
    local terms = terms_from_title(title)

    -- Build strict "related" filter: require >=2 distinct term matches (tags or title)
    local results, picked = {}, {}
    if #terms >= 2 then
        table.sort(terms, function(a,b) return #a > #b end)
        local MAX_TERMS = 6
        while #terms > MAX_TERMS do table.remove(terms) end

        local pairClauses = {}
        for i = 1, #terms - 1 do
            local ti = escape_like(terms[i])
            local left = string.format('(tags HOLDS LIKE "%%%s%%" OR _pageName LIKE "%%%s%%")', ti, ti)
            for j = i + 1, #terms do
                local tj = escape_like(terms[j])
                local right = string.format('(tags HOLDS LIKE "%%%s%%" OR _pageName LIKE "%%%s%%")', tj, tj)
                table.insert(pairClauses, '(' .. left .. ' AND ' .. right .. ')')
            end
        end

        local whereClause = '(' .. table.concat(pairClauses, ' OR ') ..
                            string.format(') AND _pageName != "%s"', escape_like(current))

        local related = mw.ext.cargo.query(tables, fields, {
            where = whereClause,
            orderBy = 'date DESC',
            limit = limit * 2
        }) or {}

        for _, r in ipairs(related) do
            if not picked[r._pageName] then
                table.insert(results, r)
                picked[r._pageName] = true
                if #results >= limit then break end
            end
        end
    end

    -- If not enough related, fill with random recent (excluding current & duplicates)
    if #results < limit then
        local recent = mw.ext.cargo.query(tables, fields, {
            orderBy = 'date DESC',
            limit = math.max(limit * 10, 40)  -- pull a pool to randomize
        }) or {}

        -- filter out current and already picked
        local pool = {}
        for _, r in ipairs(recent) do
            if r._pageName ~= current and not picked[r._pageName] then
                table.insert(pool, r)
            end
        end

        if #pool > 0 then
            shuffle_in_place(pool, stable_seed(current))
            for _, r in ipairs(pool) do
                table.insert(results, r)
                picked[r._pageName] = true
                if #results >= limit then break end
            end
        end
    end

    -- Final guarantee: never empty. If still short (very small dataset), just return what we have.
    return results
end

return p