Jump to content

Module:TroubleshooterAchievements

From ICANNWiki

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

-- Module:TroubleshooterAchievements
-- A comprehensive diagnostic tool for ICANNWiki's achievement system that helps identify and resolve issues.
-- Features:
-- * Analyzes JSON achievement data loading and structure
-- * Detects key type mismatches between string and numeric page IDs
-- * Displays detailed achievement information for the current page
-- * Provides testing capabilities for specific page IDs
-- * Handles various edge cases in achievement data parsing
-- * Renders diagnostic output

local p = {}

-- Helper function to ensure an array is retrieved
local function ensureArray(value)
    if type(value) ~= "table" then
        return {}
    end
    
    -- Check if it's an array-like table
    local isArray = true
    local count = 0
    for _ in pairs(value) do
        count = count + 1
    end
    
    -- If it has no numeric indices or is empty, return empty array
    if count == 0 then
        return {}
    end
    
    -- If it's a single string, wrap it in an array
    if count == 1 and type(value[1]) == "string" then
        return {value[1]}
    end
    
    -- If it has a single non-array value, try to convert it to an array
    if count == 1 and next(value) and type(next(value)) ~= "number" then
        local k, v = next(value)
        if type(v) == "string" then
            return {v}
        end
    end
    
    -- Return the original table if it seems to be an array
    return value
end

-- Function to render diagnostic information
function p.run(frame)
    -- Get current page info in both string and number formats
    local currentPageIdStr = tostring(mw.title.getCurrentTitle().id)
    local currentPageIdNum = tonumber(currentPageIdStr)
    local currentPageName = mw.title.getCurrentTitle().fullText
    
    -- Load JSON data using multiple methods to ensure proper array handling
    local jsonText = frame:preprocess('{{MediaWiki:AchievementData.json}}')
    
    -- Try different JSON decode approaches
    local success, data
    
    -- First try WITHOUT PRESERVE_KEYS flag to ensure proper array indexing
    success, data = pcall(function()
        return mw.text.jsonDecode(jsonText)
    end)
    
    -- If that failed, try with JSON_TRY_FIXING flag
    if not success then
        success, data = pcall(function()
            return mw.text.jsonDecode(jsonText, mw.text.JSON_TRY_FIXING)
        end)
    end
    
    -- Only as a last resort, try with PRESERVE_KEYS, which is REALLY NOT RECOMMENDED, as it tends to alter data in ways the main module cannot handle well
    if not success then
        success, data = pcall(function()
            return mw.text.jsonDecode(jsonText, mw.text.JSON_PRESERVE_KEYS)
        end)
    end
    
    -- Start building HTML output
    local html = {}
    table.insert(html, '<div style="border: 1px solid rgba(128, 128, 128, 0.3); padding: 15px; margin: 15px 0; border-radius: 5px; font-family: monospace;">')
    table.insert(html, '<h3>SimpleAchievements Diagnostic</h3>')
    
    -- Display current page info
    table.insert(html, string.format('<p>Current page: <b>%s</b> (ID: <b>%s</b>)</p>', currentPageName, currentPageIdStr))
    
    -- Display JSON loading result
    table.insert(html, '<h4>JSON Loading</h4>')
    if success then
        table.insert(html, '<p style="color: #4CAF50;"><b>✓ JSON loaded successfully</b></p>')
        table.insert(html, '<pre style="background-color: rgba(128, 128, 128, 0.1); padding: 10px; border-radius: 3px; max-height: 200px; overflow: auto; color: inherit;">')
        table.insert(html, jsonText:sub(1, 500) .. (jsonText:len() > 500 and "..." or ""))
        table.insert(html, '</pre>')
    else
        table.insert(html, '<p style="color: #F44336;"><b>✗ Failed to load JSON</b></p>')
        table.insert(html, '<p>Error: ' .. tostring(data) .. '</p>')
        table.insert(html, '</div>')
        return table.concat(html)
    end
    
    -- Display key type information
    table.insert(html, '<h4>Key Type Analysis</h4>')
    table.insert(html, '<ul>')
    table.insert(html, string.format('<li>Current page ID as string: <b>%s</b> (type: string)</li>', currentPageIdStr))
    table.insert(html, string.format('<li>Current page ID as number: <b>%s</b> (type: number)</li>', tostring(currentPageIdNum)))
    
    -- Show all keys in the user_achievements object
    if data and data.user_achievements then
        table.insert(html, '<li>Keys in user_achievements object:</li>')
        table.insert(html, '<ul>')
        for pageId, _ in pairs(data.user_achievements) do
            table.insert(html, string.format(
                '<li><b>%s</b> (type: %s)</li>',
                tostring(pageId),
                type(pageId)
            ))
        end
        table.insert(html, '</ul>')
    end
    table.insert(html, '</ul>')
    
    -- Display page achievements from JSON
    table.insert(html, '<h4>Page Achievements in JSON</h4>')
    table.insert(html, '<ul>')
    
    if data and data.user_achievements then
        local foundAny = false
        for pageId, achievements in pairs(data.user_achievements) do
            foundAny = true
            
            -- Ensure achievements is an array
            local achievementsArray = ensureArray(achievements)
            local items = {}
            
            -- Extract achievement types from the objects
            for i, achievement in ipairs(achievementsArray) do
                if type(achievement) == "table" and achievement.type then
                    table.insert(items, achievement.type)
                else
                    table.insert(items, tostring(achievement))
                end
            end
            
            local achievementsList = table.concat(items, ", ")
            
            table.insert(html, string.format(
                '<li>Page ID <b>%s</b> (type: %s): %s</li>',
                tostring(pageId),
                type(pageId),
                achievementsList
            ))
        end
        
        if not foundAny then
            table.insert(html, '<li><i>No user achievements defined in JSON</i></li>')
        end
    else
        table.insert(html, '<li><i>No user_achievements section found in JSON</i></li>')
    end
    table.insert(html, '</ul>')
    
    -- Display current page status
    table.insert(html, '<h4>Current Page Status</h4>')
    
    -- Try to find achievements for the current page using different key types
    local currentPageAchievements = {}
    local keyTypeUsed = "none"
    
    -- Try string key
    if data and data.user_achievements and data.user_achievements[currentPageIdStr] then
        local rawAchievements = ensureArray(data.user_achievements[currentPageIdStr])
        -- Extract achievement types
        for _, achievement in ipairs(rawAchievements) do
            if type(achievement) == "table" and achievement.type then
                table.insert(currentPageAchievements, achievement.type)
            else
                table.insert(currentPageAchievements, achievement)
            end
        end
        keyTypeUsed = "string"
    -- Try number key
    elseif data and data.user_achievements and data.user_achievements[currentPageIdNum] then
        local rawAchievements = ensureArray(data.user_achievements[currentPageIdNum])
        -- Extract achievement types
        for _, achievement in ipairs(rawAchievements) do
            if type(achievement) == "table" and achievement.type then
                table.insert(currentPageAchievements, achievement.type)
            else
                table.insert(currentPageAchievements, achievement)
            end
        end
        keyTypeUsed = "number"
    -- Try string comparison as last resort
    elseif data and data.user_achievements then
        for pageId, achievements in pairs(data.user_achievements) do
            if tostring(pageId) == currentPageIdStr then
                local rawAchievements = ensureArray(achievements)
                -- Extract achievement types
                for _, achievement in ipairs(rawAchievements) do
                    if type(achievement) == "table" and achievement.type then
                        table.insert(currentPageAchievements, achievement.type)
                    else
                        table.insert(currentPageAchievements, achievement)
                    end
                end
                keyTypeUsed = "string comparison with " .. type(pageId) .. " key"
                break
            end
        end
    end
    
    -- Show array details
    table.insert(html, '<p>Achievement array type: ' .. type(currentPageAchievements) .. '</p>')
    table.insert(html, '<p>Achievement array length: ' .. #currentPageAchievements .. '</p>')
    
    if #currentPageAchievements > 0 then
        table.insert(html, '<p>Achievement IDs:</p>')
        table.insert(html, '<ul>')
        for i, id in ipairs(currentPageAchievements) do
            table.insert(html, string.format('<li>%d: %s</li>', i, tostring(id)))
        end
        table.insert(html, '</ul>')
        
        table.insert(html, string.format(
            '<p style="color: #4CAF50;"><b>✓ This page (ID %s) has %d achievement(s) defined in JSON</b></p>',
            currentPageIdStr,
            #currentPageAchievements
        ))
        table.insert(html, string.format(
            '<p>Found using key type: <b>%s</b></p>',
            keyTypeUsed
        ))
    else
        table.insert(html, string.format(
            '<p style="color: #FF9800;"><b>⚠ This page (ID %s) has no achievements defined in JSON</b></p>',
            currentPageIdStr
        ))
    end
    
    -- Display achievement definitions
    table.insert(html, '<h4>Achievement Definitions</h4>')
    table.insert(html, '<ul>')
    
    if data and data.achievement_types then
        for _, achievement in ipairs(data.achievement_types) do
            table.insert(html, string.format(
                '<li>ID: <b>%s</b>, Name: <b>%s</b>, Type: <b>%s</b></li>',
                achievement.id or "nil",
                achievement.name or "nil",
                achievement.type or "nil"
            ))
        end
    else
        table.insert(html, '<li><i>No achievement definitions found in JSON</i></li>')
    end
    
    table.insert(html, '</ul>')
    
    -- Add help text for functions
    table.insert(html, '<p>To check achievements for a specific page ID: <code>{{#invoke:SimpleAchievementDiagnostic|pageAchievements|18451}}</code></p>')
    
    table.insert(html, '</div>')
    
    return table.concat(html)
end

-- Function to test specific page ID (alias for backward compatibility)
function p.pageAchievements(frame)
    return p.testPage(frame)
end

-- Function to test specific page ID
function p.testPage(frame)
    local pageIdStr = frame.args[1] or "18451"
    local pageIdNum = tonumber(pageIdStr)
    
    -- Load JSON data without PRESERVE_KEYS flag to ensure proper array indexing
    local jsonText = frame:preprocess('{{MediaWiki:AchievementData.json}}')
    
    -- Try different JSON decode approaches
    local success, data
    
    -- First try WITHOUT PRESERVE_KEYS flag to ensure proper array indexing
    success, data = pcall(function()
        return mw.text.jsonDecode(jsonText)
    end)
    
    -- If that failed, try with JSON_TRY_FIXING flag
    if not success then
        success, data = pcall(function()
            return mw.text.jsonDecode(jsonText, mw.text.JSON_TRY_FIXING)
        end)
    end
    
    -- Only as a last resort, try with PRESERVE_KEYS
    if not success then
        success, data = pcall(function()
            return mw.text.jsonDecode(jsonText, mw.text.JSON_PRESERVE_KEYS)
        end)
    end
    
    -- Start building HTML output
    local html = {}
    table.insert(html, string.format('<div style="border: 1px solid rgba(128, 128, 128, 0.3); padding: 15px; margin: 15px 0; border-radius: 5px;"><h3>Test for Page ID: %s</h3>', pageIdStr))
    
    if not success then
        table.insert(html, '<p style="color: #F44336;"><b>✗ Failed to load JSON</b></p>')
        table.insert(html, '<p>Error: ' .. tostring(data) .. '</p>')
        table.insert(html, '</div>')
        return table.concat(html)
    end
    
    -- Try to find achievements for this page using different key types
    local pageAchievements = {}
    local rawAchievements = {}
    local keyTypeUsed = "none"
    
    -- Try string key
    if data and data.user_achievements and data.user_achievements[pageIdStr] then
        rawAchievements = ensureArray(data.user_achievements[pageIdStr])
        keyTypeUsed = "string"
    -- Try number key
    elseif data and data.user_achievements and pageIdNum and data.user_achievements[pageIdNum] then
        rawAchievements = ensureArray(data.user_achievements[pageIdNum])
        keyTypeUsed = "number"
    -- Try string comparison as last resort
    elseif data and data.user_achievements then
        for pageId, achievements in pairs(data.user_achievements) do
            if tostring(pageId) == pageIdStr then
                rawAchievements = ensureArray(achievements)
                keyTypeUsed = "string comparison with " .. type(pageId) .. " key"
                break
            end
        end
    end
    
    -- Extract achievement types from objects
    for _, achievement in ipairs(rawAchievements) do
        if type(achievement) == "table" and achievement.type then
            table.insert(pageAchievements, achievement.type)
        else
            table.insert(pageAchievements, achievement)
        end
    end
    
    -- Display key type information
    table.insert(html, '<h4>Key Type Analysis</h4>')
    table.insert(html, '<ul>')
    table.insert(html, string.format('<li>Test page ID as string: <b>%s</b> (type: string)</li>', pageIdStr))
    if pageIdNum then
        table.insert(html, string.format('<li>Test page ID as number: <b>%s</b> (type: number)</li>', tostring(pageIdNum)))
    else
        table.insert(html, '<li>Test page ID cannot be converted to number</li>')
    end
    
    -- Show all keys in the user_achievements object
    if data and data.user_achievements then
        table.insert(html, '<li>Keys in user_achievements object:</li>')
        table.insert(html, '<ul>')
        for pageId, achievements in pairs(data.user_achievements) do
            -- Extract achievement types and convert to string list
            local achievementsArray = ensureArray(achievements)
            local items = {}
            for i, achievement in ipairs(achievementsArray) do
                if type(achievement) == "table" and achievement.type then
                    table.insert(items, achievement.type)
                else
                    table.insert(items, tostring(achievement))
                end
            end
            local achievementsList = table.concat(items, ", ")
            
            table.insert(html, string.format(
                '<li><b>%s</b> (type: %s): %s</li>',
                tostring(pageId),
                type(pageId),
                achievementsList
            ))
        end
        table.insert(html, '</ul>')
    end
    table.insert(html, '</ul>')
    
    -- Show array details
    table.insert(html, '<p>Achievement array type: ' .. type(pageAchievements) .. '</p>')
    table.insert(html, '<p>Achievement array length: ' .. #pageAchievements .. '</p>')
    
    if #pageAchievements > 0 then
        table.insert(html, '<p>Achievement IDs:</p>')
        table.insert(html, '<ul>')
        for i, id in ipairs(pageAchievements) do
            table.insert(html, string.format('<li>%d: %s</li>', i, tostring(id)))
        end
        table.insert(html, '</ul>')
        
        table.insert(html, string.format(
            '<p><b>Found %d achievement(s) in JSON:</b> %s</p>',
            #pageAchievements,
            table.concat(pageAchievements, ", ")
        ))
        table.insert(html, string.format(
            '<p>Found using key type: <b>%s</b></p>',
            keyTypeUsed
        ))
        
        -- Display achievement details
        if data and data.achievement_types then
            table.insert(html, '<h4>Achievement Details</h4>')
            table.insert(html, '<ul>')
            for _, achievementId in ipairs(pageAchievements) do
                -- Find the achievement details
                local found = false
                for _, achievement in ipairs(data.achievement_types) do
                    if achievement.id == achievementId then
                        table.insert(html, string.format(
                            '<li>ID: <b>%s</b>, Name: <b>%s</b>, Type: <b>%s</b></li>',
                            achievementId,
                            achievement.name or "nil",
                            achievement.type or "nil"
                        ))
                        found = true
                        break
                    end
                end
                
                if not found then
                    table.insert(html, string.format(
                        '<li>ID: <b>%s</b> (details not found in JSON)</li>',
                        achievementId
                    ))
                end
            end
            table.insert(html, '</ul>')
        else
            table.insert(html, '<p style="color: #F44336;"><b>No achievement definitions found in JSON</b></p>')
        end
    else
        table.insert(html, '<p><i>No achievements defined for this page ID in JSON</i></p>')
    end
    
    table.insert(html, '</div>')
    
    return table.concat(html)
end

return p