Module:AchievementSystem: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
Line 106: Line 106:
         -- Try using the specialized JSON data loader - this is the correct MediaWiki way
         -- Try using the specialized JSON data loader - this is the correct MediaWiki way
         -- to load JSON from wiki pages
         -- to load JSON from wiki pages
        mw.log("JSON-DEBUG: ===== LOAD JSON DATA ATTEMPT START =====")
        mw.log("JSON-DEBUG: Attempting to use mw.loadJsonData for " .. ACHIEVEMENT_DATA_PAGE)
       
        -- Check if mw.loadJsonData is available
        if not mw.loadJsonData then
            mw.log("JSON-DEBUG: CRITICAL ERROR: mw.loadJsonData function does not exist!")
            mw.log("JSON-DEBUG: This indicates the MediaWiki version may be too old or misconfigured")
        else
            mw.log("JSON-DEBUG: mw.loadJsonData function exists, attempting to use it")
        end
       
         local loadJsonSuccess, jsonData = pcall(function()
         local loadJsonSuccess, jsonData = pcall(function()
            mw.log("JSON-DEBUG: Attempting to use mw.loadJsonData for " .. ACHIEVEMENT_DATA_PAGE)
             return mw.loadJsonData(ACHIEVEMENT_DATA_PAGE)
             return mw.loadJsonData(ACHIEVEMENT_DATA_PAGE)
         end)
         end)
Line 113: Line 123:
         if loadJsonSuccess and jsonData then
         if loadJsonSuccess and jsonData then
             mw.log("JSON-DEBUG: Successfully loaded data with mw.loadJsonData")
             mw.log("JSON-DEBUG: Successfully loaded data with mw.loadJsonData")
            mw.log("JSON-DEBUG: JSON data type: " .. type(jsonData))
           
            -- Check if we got a table as expected
            if type(jsonData) == 'table' then
                -- Check some expected fields to confirm it's valid
                if jsonData.achievement_types then
                    mw.log("JSON-DEBUG: ✓ Found achievement_types array with " ..
                          #(jsonData.achievement_types or {}) .. " entries")
                else
                    mw.log("JSON-DEBUG: ✗ Missing achievement_types array in loaded data!")
                end
               
                if jsonData.user_achievements then
                    local userCount = 0
                    for _, _ in pairs(jsonData.user_achievements) do
                        userCount = userCount + 1
                    end
                    mw.log("JSON-DEBUG: ✓ Found user_achievements for " .. userCount .. " users")
                else
                    mw.log("JSON-DEBUG: ✗ Missing user_achievements in loaded data!")
                end
            end
           
             return jsonData
             return jsonData
         else
         else
             mw.log("JSON-DEBUG: mw.loadJsonData failed: " .. tostring(jsonData or 'unknown error'))
             mw.log("JSON-DEBUG: mw.loadJsonData failed: " .. tostring(jsonData or 'unknown error'))
           
            -- Try to categorize the error
            local errorMsg = tostring(jsonData or '')
            if errorMsg:match("content model") then
                mw.log("JSON-DEBUG: ERROR CAUSE: Content model issue - page must be set to 'json' model")
            elseif errorMsg:match("JSON decode") or errorMsg:match("syntax") then
                mw.log("JSON-DEBUG: ERROR CAUSE: JSON syntax error - check for invalid JSON formatting")
            elseif errorMsg:match("permission") or errorMsg:match("access") then
                mw.log("JSON-DEBUG: ERROR CAUSE: Permission/access error - check page permissions")
            end
         end
         end
          
          
         -- Try parser cache next
         -- Try to fetch the raw content to see what's being received
         local loadDataSuccess, cachedData = pcall(function()
         local rawContent = nil
             mw.log("JSON-DEBUG: Attempting mw.loadData fallback")
        pcall(function()
             return mw.loadData('Module:AchievementSystem')
             local title = mw.title.new(ACHIEVEMENT_DATA_PAGE)
            if title and title.exists then
                rawContent = title:getContent()
                if rawContent and #rawContent > 0 then
                    mw.log("JSON-DEBUG: Raw content first 100 chars: " ..
                          rawContent:sub(1, 100):gsub("\n", "\\n"):gsub("\t", "\\t"))
                         
                    -- Check if it starts with a curly brace
                    if rawContent:match("^%s*{") then
                        mw.log("JSON-DEBUG: ✓ Content starts with { (possibly with whitespace)")
                    else
                        mw.log("JSON-DEBUG: ✗ Content does NOT start with {")
                    end
                   
                    -- Check for common issues
                    if rawContent:match("^<!DOCTYPE") or rawContent:match("^<[Hh][Tt][Mm][Ll]") then
                        mw.log("JSON-DEBUG: ERROR: Content appears to be HTML, not JSON!")
                    elseif rawContent:match("^%s*<") then
                        mw.log("JSON-DEBUG: ERROR: Content appears to have XML/HTML markup!")
                    end
                else
                    mw.log("JSON-DEBUG: Could not get raw content or content is empty")
                end
             else
                mw.log("JSON-DEBUG: Could not create title object or page doesn't exist")
            end
         end)
         end)
 
       
         if loadDataSuccess and cachedData then
         mw.log("JSON-DEBUG: ===== LOAD JSON DATA ATTEMPT END =====")
            mw.log("JSON-DEBUG: Using mw.loadData cached data")
       
            return cachedData
         -- GET RAW CONTENT DIRECTLY APPROACH - likely the most reliable method
         else
        mw.log("JSON-DEBUG: ===== DIRECT RAW CONTENT METHOD START =====")
            mw.log("JSON-DEBUG: mw.loadData failed, proceeding to direct page load")
          
         end
         -- Try to fetch the raw content directly with proper error handling
 
         -- Try direct page content as last resort
         local pageTitle = mw.title.new(ACHIEVEMENT_DATA_PAGE)
         local pageTitle = mw.title.new(ACHIEVEMENT_DATA_PAGE)
         if not pageTitle or not pageTitle.exists then
         if not pageTitle or not pageTitle.exists then
Line 137: Line 203:
             return DEFAULT_DATA
             return DEFAULT_DATA
         end
         end
        local content = pageTitle:getContent()
        if not content or content == '' then
            mw.log("JSON-DEBUG: Page content is empty")
            return DEFAULT_DATA
        end
        mw.log("JSON-DEBUG: Raw JSON content length: " .. #content)
          
          
         -- Try mw.text.jsonDecode if available (preferred MediaWiki method)
         -- Get raw content from the wiki page
         if mw.text and mw.text.jsonDecode then
         local content = nil
            local jsonDecodeSuccess, jsonData = pcall(function()
        local contentSuccess, contentResult = pcall(function()
                mw.log("JSON-DEBUG: Using mw.text.jsonDecode")
            return pageTitle:getContent()
                return mw.text.jsonDecode(content)
        end)
            end)
       
        if contentSuccess and contentResult and contentResult ~= "" then
            content = contentResult
            mw.log("JSON-DEBUG: Successfully retrieved raw content, length: " .. #content)
              
              
             if jsonDecodeSuccess and jsonData then
             -- Log the first part of content for debugging
                mw.log("JSON-DEBUG: Successfully decoded with mw.text.jsonDecode")
            local contentPreview = content:sub(1, 100):gsub("\n", "\\n"):gsub("\t", "\\t")
                 return jsonData
            mw.log("JSON-DEBUG: Content preview: " .. contentPreview)
           
            -- Remove any BOM or leading whitespace that might cause issues
            content = content:gsub("^%s+", "")
            if content:byte(1) == 239 and content:byte(2) == 187 and content:byte(3) == 191 then
                mw.log("JSON-DEBUG: Removing UTF-8 BOM from content")
                content = content:sub(4)
            end
           
            -- Check if content starts with { or [
            if content:match("^%s*[{%[]") then
                mw.log("JSON-DEBUG: Content appears to be valid JSON")
               
                -- Use mw.text.jsonDecode for parsing
                if mw.text and mw.text.jsonDecode then
                    local jsonDecodeSuccess, jsonData = pcall(function()
                        mw.log("JSON-DEBUG: Using mw.text.jsonDecode on raw content")
                        return mw.text.jsonDecode(content)
                    end)
                   
                    if jsonDecodeSuccess and jsonData then
                        mw.log("JSON-DEBUG: Successfully decoded content with mw.text.jsonDecode")
                       
                        -- Validate the structure
                        if jsonData.achievement_types and jsonData.user_achievements then
                            mw.log("JSON-DEBUG: ✓ Parsed JSON has required fields")
                            mw.log("JSON-DEBUG: ===== DIRECT RAW CONTENT METHOD SUCCESS =====")
                            return jsonData
                        else
                            mw.log("JSON-DEBUG: ✗ Parsed JSON missing critical fields")
                        end
                    else
                        mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode failed: " .. tostring(jsonData or 'unknown error'))
                    end
                else
                    mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode not available")
                end
               
                -- Fallback to Module:JSON if available
                 if json and json.decode then
                    local parseSuccess, parsedData = pcall(function()
                        mw.log("JSON-DEBUG: Using Module:JSON's json.decode as fallback")
                        return json.decode(content)
                    end)
                   
                    if parseSuccess and parsedData then
                        mw.log("JSON-DEBUG: ✓ Successfully decoded with Module:JSON")
                        mw.log("JSON-DEBUG: ===== DIRECT RAW CONTENT METHOD SUCCESS =====")
                        return parsedData
                    else
                        mw.log("JSON-DEBUG: ✗ Module:JSON decode failed: " .. tostring(parsedData or 'unknown error'))
                    end
                end
             else
             else
                 mw.log("JSON-DEBUG: mw.text.jsonDecode failed: " .. tostring(jsonData or 'unknown error'))
                 mw.log("JSON-DEBUG: ✗ Content does not appear to be valid JSON")
                mw.log("JSON-DEBUG: First 10 characters: " .. content:sub(1, 10):gsub("\n", "\\n"):gsub("\t", "\\t"))
             end
             end
        else
            mw.log("JSON-DEBUG: ✗ Failed to get content: " .. tostring(contentResult or 'unknown error'))
         end
         end
          
          
         -- Fall back to Module:JSON if all else fails
        mw.log("JSON-DEBUG: ===== DIRECT RAW CONTENT METHOD FAILED =====")
         local parseSuccess, parsedData = pcall(function()
       
             mw.log("JSON-DEBUG: Using json.decode fallback")
         -- FALLBACK SAFETY APPROACHES
             return json.decode(content)
       
        -- Try parser cache as fallback
         local loadDataSuccess, cachedData = pcall(function()
             mw.log("JSON-DEBUG: Attempting mw.loadData fallback")
             return mw.loadData('Module:AchievementSystem')
         end)
         end)


         if not parseSuccess or not parsedData then
         if loadDataSuccess and cachedData then
             mw.log("JSON-DEBUG: JSON parse failed: " .. tostring(parsedData or 'unknown error'))
             mw.log("JSON-DEBUG: Using mw.loadData cached data")
             return DEFAULT_DATA
             return cachedData
        else
            mw.log("JSON-DEBUG: mw.loadData failed, using default data")
         end
         end
 
       
         mw.log("JSON-DEBUG: Successfully loaded achievement data via fallback method")
        -- As absolute last resort, use local default data
        return parsedData
         mw.log("JSON-DEBUG: All JSON loading approaches failed, using default data")
     end)
     end)


Line 695: Line 817:
      
      
     return cssClass, displayName, achievementId
     return cssClass, displayName, achievementId
end
--------------------------------------------------------------------------------
-- Diagnostic function that can be directly called to troubleshoot JSON loading
--------------------------------------------------------------------------------
function Achievements.diagnoseJsonLoading()
    local output = {}
    table.insert(output, "===== ACHIEVEMENT SYSTEM JSON DIAGNOSTICS =====")
   
    -- Check MediaWiki version and capabilities
    table.insert(output, "\n== MEDIAWIKI CAPABILITIES ==")
    if mw.loadJsonData then
        table.insert(output, "✓ mw.loadJsonData: Available")
    else
        table.insert(output, "❌ mw.loadJsonData: Not available! MediaWiki may be too old or misconfigured")
    end
   
    if mw.text and mw.text.jsonDecode then
        table.insert(output, "✓ mw.text.jsonDecode: Available")
    else
        table.insert(output, "❌ mw.text.jsonDecode: Not available! This is a critical function")
    end
   
    if json and json.decode then
        table.insert(output, "✓ Module:JSON: Available")
    else
        table.insert(output, "❌ Module:JSON: Not available or failed to load")
    end
   
    -- Check the page configuration
    table.insert(output, "\n== JSON PAGE STATUS ==")
    local pageTitle = mw.title.new(ACHIEVEMENT_DATA_PAGE)
    if not pageTitle then
        table.insert(output, "❌ Could not create title object for: " .. ACHIEVEMENT_DATA_PAGE)
        return table.concat(output, "\n")
    end
   
    if not pageTitle.exists then
        table.insert(output, "❌ Page does not exist: " .. ACHIEVEMENT_DATA_PAGE)
        return table.concat(output, "\n")
    end
   
    table.insert(output, "✓ Page exists: " .. ACHIEVEMENT_DATA_PAGE)
   
    -- Check the content model
    if pageTitle.contentModel then
        table.insert(output, "Content model: " .. pageTitle.contentModel)
        if pageTitle.contentModel == "json" then
            table.insert(output, "✓ Page has correct content model: 'json'")
        else
            table.insert(output, "❌ Page has INCORRECT content model: '" .. pageTitle.contentModel .. "' (should be 'json')")
        end
    else
        table.insert(output, "⚠ Could not determine content model")
    end
   
    -- Try to fetch page content
    local content = nil
    local contentSuccess, contentResult = pcall(function()
        return pageTitle:getContent()
    end)
   
    if not contentSuccess or not contentResult or contentResult == "" then
        table.insert(output, "❌ Failed to get page content: " .. tostring(contentResult or "Unknown error"))
        return table.concat(output, "\n")
    end
   
    table.insert(output, "✓ Got page content, length: " .. #contentResult)
   
    -- Check page content
    content = contentResult
    local contentPreview = content:sub(1, 50):gsub("\n", "\\n"):gsub("\t", "\\t")
    table.insert(output, "Content preview: \"" .. contentPreview .. "...\"")
   
    if content:match("^%s*{") then
        table.insert(output, "✓ Content starts with { (correct)")
    else
        table.insert(output, "❌ Content does NOT start with { (INCORRECT)")
    end
   
    -- Check for common issues
    if content:match("^<!DOCTYPE") or content:match("^<[Hh][Tt][Mm][Ll]") then
        table.insert(output, "❌ CRITICAL ERROR: Content appears to be HTML, not JSON!")
    elseif content:match("^%s*<") then
        table.insert(output, "❌ CRITICAL ERROR: Content appears to have XML/HTML markup!")
    end
   
    -- Try mw.loadJsonData
    table.insert(output, "\n== TESTING mw.loadJsonData ==")
    local loadJsonSuccess, loadJsonResult = pcall(function()
        return mw.loadJsonData(ACHIEVEMENT_DATA_PAGE)
    end)
   
    if not loadJsonSuccess then
        table.insert(output, "❌ mw.loadJsonData failed: " .. tostring(loadJsonResult or "Unknown error"))
       
        -- Analyze error
        local errorMsg = tostring(loadJsonResult or "")
        if errorMsg:match("content model") then
            table.insert(output, "ERROR CAUSE: Content model issue - page must be set to 'json' model")
        elseif errorMsg:match("JSON decode") or errorMsg:match("syntax") then
            table.insert(output, "ERROR CAUSE: JSON syntax error - check for invalid JSON formatting")
        elseif errorMsg:match("permission") or errorMsg:match("access") then
            table.insert(output, "ERROR CAUSE: Permission/access error - check page permissions")
        end
    else
        table.insert(output, "✓ mw.loadJsonData SUCCESSFUL!")
        table.insert(output, "Data type: " .. type(loadJsonResult))
       
        if type(loadJsonResult) == "table" then
            local keysFound = {}
            for k, _ in pairs(loadJsonResult) do
                table.insert(keysFound, k)
            end
            table.insert(output, "Top-level keys: " .. table.concat(keysFound, ", "))
           
            if loadJsonResult.achievement_types then
                table.insert(output, "✓ Found " .. #loadJsonResult.achievement_types .. " achievement types")
            else
                table.insert(output, "❌ Missing 'achievement_types' array!")
            end
           
            if loadJsonResult.user_achievements then
                local userCount = 0
                for _, _ in pairs(loadJsonResult.user_achievements) do
                    userCount = userCount + 1
                end
                table.insert(output, "✓ Found user achievements for " .. userCount .. " users")
            else
                table.insert(output, "❌ Missing 'user_achievements' object!")
            end
        end
    end
   
    -- Try direct JSON decoding
    table.insert(output, "\n== TESTING DIRECT JSON DECODING ==")
   
    -- Use mw.text.jsonDecode
    if mw.text and mw.text.jsonDecode then
        local jsonDecodeSuccess, jsonData = pcall(function()
            return mw.text.jsonDecode(content)
        end)
       
        if jsonDecodeSuccess and jsonData then
            table.insert(output, "✓ mw.text.jsonDecode SUCCESSFUL!")
           
            if jsonData.achievement_types then
                table.insert(output, "✓ Found " .. #jsonData.achievement_types .. " achievement types")
            else
                table.insert(output, "❌ Missing 'achievement_types' array in decoded data!")
            end
        else
            table.insert(output, "❌ mw.text.jsonDecode failed: " .. tostring(jsonData or "Unknown error"))
        end
    else
        table.insert(output, "⚠ Cannot test mw.text.jsonDecode (not available)")
    end
   
    -- Use Module:JSON if available
    if json and json.decode then
        local parseSuccess, parsedData = pcall(function()
            return json.decode(content)
        end)
       
        if parseSuccess and parsedData then
            table.insert(output, "✓ Module:JSON's json.decode SUCCESSFUL!")
           
            if parsedData.achievement_types then
                table.insert(output, "✓ Found " .. #parsedData.achievement_types .. " achievement types")
            else
                table.insert(output, "❌ Missing 'achievement_types' array in decoded data!")
            end
        else
            table.insert(output, "❌ Module:JSON's json.decode failed: " .. tostring(parsedData or "Unknown error"))
        end
    else
        table.insert(output, "⚠ Cannot test Module:JSON (not available)")
    end
   
    -- Add recommendations
    table.insert(output, "\n== RECOMMENDATIONS ==")
    if not loadJsonSuccess then
        table.insert(output, "1. Ensure '" .. ACHIEVEMENT_DATA_PAGE .. "' has content model set to 'json'")
        table.insert(output, "2. Verify the JSON is valid with no syntax errors")
        table.insert(output, "3. Check the page starts with { with no leading whitespace or comments")
    else
        table.insert(output, "✓ JSON loading appears to be working correctly!")
    end
   
    return table.concat(output, "\n")
end
end


return Achievements
return Achievements