Module:TroubleshooterAchievements
Appearance
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