Module:XVideosRedList

From Porn Base Central
Jump to navigation Jump to search

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

-- This module handles the display of XVideos RED channel information
local p = {}

-- Load the i18n submodule for text strings and error messages
local i18n = require('Module:XVideosRedList/i18n')

-- Base URL for XVideos RED
local BASE_URL = "https://www.xvideos.red/"
-- Suffix to append to all XVideos RED URLs
local URL_SUFFIX = "?sxcaf=TARTFSJC35"
-- Tab suffix added after the main suffix
local TAB_SUFFIX = "#_tabRed"
-- Main header URL for title link
local HEADER_URL = "https://www.xvideos.red/gay?sxcaf=TARTFSJC35"
-- Default category for all channels
local DEFAULT_CATEGORY = "XVideos RED models"

-- Genre categories mapping
local GENRE_CATEGORIES = {
    ["Gay"] = "XVideos RED (gay) models",
    ["Straight"] = "XVideos RED (heterosexual) models",
    ["Bisexual"] = "XVideos RED (bisexual) models",
    ["Transexual"] = "XVideos RED (transsexual) models"
}

-- Genre display order priority
local GENRE_PRIORITY = {
    ["Gay"] = 1,
    ["Straight"] = 2,
    ["Bisexual"] = 3,
    ["Transexual"] = 4
}

-- Helper function to check if a table contains a specific value
-- @param tbl table: The table to search
-- @param value any: The value to find
-- @return boolean: True if the value is found, false otherwise
function table.contains(tbl, value)
    for _, v in pairs(tbl) do
        if v == value then return true end
    end
    return false
end

-- Helper function to format error message in red bold text
-- @param error string: The error message
-- @return string: Formatted error message
local function formatError(error)
    return string.format('|-\n| colspan="5" style="text-align:center;vertical-align:middle;color:red;font-weight:bold;" | %s\n', error)
end

-- Table of genres with their aliases for normalization
-- Each key is a standard genre name, and the value is a list of aliases or abbreviations
local genres = {
    Straight = {"s", "str", "str8"},
    Gay = {"g", "homo", "h", "homosexual"},
    Transexual = {"t", "trans", "tra", "trs"},
    Bisexual = {"b", "bi", "bisex"}
}

-- Table of icons for standard genres
-- Maps standard genre names to their corresponding icon file names
local iconMap = {
    ["Straight"] = "StraightIconPNG.png",
    ["Gay"] = "GayIconPNG.png",
    ["Transexual"] = "TranssexualIconPNG.png",
    ["Bisexual"] = "BisexualIconPNG.png",
}

-- Function to normalize genre input to a standard genre name
-- @param input string: The genre input to normalize
-- @return string: The standard genre name if matched, otherwise the original input
-- @return boolean: True if the genre is valid, false otherwise
local function normalizeGenre(input)
    if not input or input == '' then
        return nil, false
    end
    
    local lowerInput = input:lower()  -- Convert input to lowercase for case-insensitive comparison
    for standard, aliases in pairs(genres) do
        if lowerInput == standard:lower() or table.contains(aliases, lowerInput) then
            return standard, true
        end
    end
    return input, false  -- Return original input if no match is found, plus flag indicating no match
end

-- Function to format multiple genres with proper icons and formatting
-- @param genreList table: List of normalized genre names
-- @return string: Formatted genres with icons and proper separators
local function formatGenres(genreList)
    if #genreList == 0 then
        return '<span style="color:red;font-weight:bold;">' .. i18n.errors.noGenreProvided .. '</span>'
    end
    
    -- Sort genres according to priority order
    table.sort(genreList, function(a, b) 
        return (GENRE_PRIORITY[a] or 99) < (GENRE_PRIORITY[b] or 99)
    end)
    
    local formattedGenres = {}
    for _, genre in ipairs(genreList) do
        if iconMap[genre] then
            table.insert(formattedGenres, string.format('[[File:%s|13px|link=]] %s', iconMap[genre], genre))
        else
            table.insert(formattedGenres, genre)
        end
    end
    
    -- Format with appropriate separators based on the number of genres
    if #formattedGenres == 1 then
        return formattedGenres[1]
    elseif #formattedGenres == 2 then
        return formattedGenres[1] .. ' & ' .. formattedGenres[2]
    else
        local result = ""
        for i = 1, #formattedGenres - 1 do
            if i == #formattedGenres - 1 then
                result = result .. formattedGenres[i] .. ' & '
            else
                result = result .. formattedGenres[i] .. ', '
            end
        end
        return result .. formattedGenres[#formattedGenres]
    end
end

-- Function to format country flag using frame:expandTemplate
-- @param country string: The country code or name
-- @param frame table: The current frame object for template expansion
-- @return string: Formatted country flag or empty string if country is nil or empty
local function formatCountryFlag(country, frame)
    if not country or country == "" then
        return ""
    end
    
    -- Use frame:expandTemplate to process the flagicon template
    local flagTemplate = frame:expandTemplate{
        title = 'flagicon',
        args = { country }
    }
    
    return flagTemplate .. ' '
end

-- Main function to handle channel information display
-- @param frame table: The frame object passed from the template
-- @return string: The formatted table row or an error message
function p.channel(frame)
    -- Get template arguments
    local args = require('Module:Arguments').getArgs(frame, {wrappers = 'Template:XVideosRedList'})
    
    -- Check if channel parameter is provided
    if not args.channel or args.channel == '' then
        return formatError(i18n.errors.channelIsEmpty)
    end

    -- Get channel data
    local channel, error = getChannel(args.channel)

    if error then
        return formatError(error)
    elseif channel then
        -- Format channel data, prioritizing template arguments over module data
        local alias = args.alias or channel.alias or 'N/A'
        
        -- Fix for empty notes field
        local notes = args.notes or channel.notes or 'N/A'
        if notes == "" then
            notes = 'N/A'
        end
        
        -- Process multiple genres
        local genreList = {}
        local invalidGenres = {}
        
        -- Process primary genre
        local primaryGenre = args.genre or channel.genre or ''
        local normalizedGenre, isValid = normalizeGenre(primaryGenre)
        if normalizedGenre then
            table.insert(genreList, normalizedGenre)
            if not isValid then
                table.insert(invalidGenres, primaryGenre)
            end
        end
        
        -- Process additional genres (genre2, genre3, genre4)
        for i = 2, 4 do
            local genreArg = args['genre'..i]
            if genreArg and genreArg ~= '' then
                local normalized, valid = normalizeGenre(genreArg)
                if normalized and not table.contains(genreList, normalized) then
                    table.insert(genreList, normalized)
                    if not valid then
                        table.insert(invalidGenres, genreArg)
                    end
                end
            end
        end
        
        -- Check if at least one valid genre was provided
        if #genreList == 0 then
            return formatError(i18n.errors.noGenreProvided)
        end
        
        -- Format genres with appropriate icons and separators
        local genreOutput = formatGenres(genreList)
        
        -- Log any invalid genres for debugging
        for _, invalidGenre in ipairs(invalidGenres) do
            mw.log(string.format(i18n.errors.invalidGenre, invalidGenre))
        end
        
        -- Handle channel URL and country flag
        local channelUrl = ""
        local channelDisplay = channel.name
        local countryFlag = formatCountryFlag(channel.country, frame)
        
        if channel.channel and channel.channel ~= '' then
            channelUrl = BASE_URL .. channel.channel .. URL_SUFFIX .. TAB_SUFFIX
            channelDisplay = string.format('%s[%s %s]', countryFlag, channelUrl, channel.name)
        end

        -- Format row with gathered data
        local result = string.format(
            '|-\n| style="text-align:center;vertical-align:middle;" | %s\n| style="text-align:center;vertical-align:middle;" | %s\n| style="text-align:center;vertical-align:middle;" | %s\n| style="text-align:center;vertical-align:middle;" | %s\n| style="display:none;" |\n',
            channelDisplay,
            genreOutput,
            alias,
            notes
        )

        -- Add categories based on namespace and genres
        local ns = mw.title.getCurrentTitle().namespace
        if ns == 0 then
            -- Add default category for all entries
            result = result .. '[[Category:' .. DEFAULT_CATEGORY .. ']]'
            
            -- Add genre-specific categories
            for _, genre in ipairs(genreList) do
                if GENRE_CATEGORIES[genre] then
                    result = result .. '[[Category:' .. GENRE_CATEGORIES[genre] .. ']]'
                end
            end
            
            -- Add tracking category for main namespace
            result = result .. '[[Category:Articles using XVideosRedList]]'
        elseif ns == 2 then
            result = result .. '[[Category:User pages using XVideosRedList]]'
        elseif ns == 118 then
            result = result .. '[[Category:Draft pages using XVideosRedList]]'
        end

        return result
    else
        return formatError(string.format(i18n.errors.channelNotFound, args.channel))
    end
end

-- Function to find and retrieve channel data from appropriate module
-- @param name string: The name of the channel to find
-- @return table|string: The channel data table or an error message
function getChannel(name)
    if not name then return nil, i18n.errors.channelIsEmpty end
    
    local modulesToTry = {}
    local firstLetter = name:sub(1, 1):upper()
    table.insert(modulesToTry, firstLetter)
    
    -- Handle "The" prefix
    local nameWithoutThe = name:lower():gsub("^the%s*", ""):gsub("%s*", "")
    if nameWithoutThe ~= name:lower() then
        local theFirstLetter = nameWithoutThe:sub(1, 1):upper()
        if theFirstLetter ~= firstLetter then
            table.insert(modulesToTry, theFirstLetter)
        end
    end

    for _, module in ipairs(modulesToTry) do
        local success, channelModule = pcall(require, 'Module:XVideosRedList/' .. module)
        
        if success then
            if type(channelModule) == "table" and type(channelModule.channels) == "table" then
                local matchedChannels = {}
                
                -- First try direct match or aliases
                for _, channel in pairs(channelModule.channels) do
                    if channel.name:lower() == name:lower() or 
                       (channel.aliases and table.contains(channel.aliases, name:lower())) then
                        table.insert(matchedChannels, channel)
                    end
                end
                
                -- If found matches, return results
                if #matchedChannels > 1 then
                    return nil, string.format(i18n.errors.duplicateChannels, name)
                elseif #matchedChannels == 1 then
                    return matchedChannels[1]
                end
                
                -- If no direct matches found, try redirects
                if channelModule.redirects and channelModule.redirects[name:lower()] then
                    return getChannel(channelModule.redirects[name:lower()])
                end
            else
                mw.log(string.format(i18n.errors.invalidModuleStructure, module))
                return nil, string.format(i18n.errors.invalidModuleStructure, module)
            end
        else
            mw.log(string.format(i18n.errors.moduleLoadError, module))
        end
    end
    
    return nil, string.format(i18n.errors.channelNotFound, name)
end

-- Function to generate the table header with XVideos RED logo link
-- @return string: The formatted table header
function p.tableHeader()
    local headerLink = string.format('[[File:Xvideosredlogo.svg|24px|link=]] [%s XVideos RED]', HEADER_URL)
    
    return string.format('! colspan="5" | %s\n|-\n!style="width: 30%%; text-align: center; background-color:#E7E7E7;" |Studio name\n!style="width: 20%%; text-align: center; background-color:#E7E7E7;" |Genre\n!style="width: 25%%; text-align: center; background-color:#E7E7E7;" |Alias\n!style="width: 25%%; text-align: center; background-color:#E7E7E7;" |Notes\n!style="display:none;" |\n', headerLink)
end

return p