Module:AchievementSystem: Difference between revisions
Appearance
// via Wikitext Extension for VSCode |
// via Wikitext Extension for VSCode |
||
| Line 28: | Line 28: | ||
-- We'll use MediaWiki's built-in JSON functions directly, no external module needed | -- We'll use MediaWiki's built-in JSON functions directly, no external module needed | ||
local function jsonDecode(jsonString) | local function jsonDecode(jsonString) | ||
if not jsonString then return nil end | if not jsonString then | ||
debugLog('ERROR: Empty JSON string provided') | |||
return nil | |||
end | |||
if mw.text and mw.text.jsonDecode then | if mw.text and mw.text.jsonDecode then | ||
| Line 36: | Line 39: | ||
if success and result then | if success and result then | ||
return result | -- Validate the structure has achievement_types array | ||
if type(result) == 'table' and result.achievement_types then | |||
debugLog('SUCCESS: JSON decode successful with ' .. #result.achievement_types .. ' achievement types') | |||
return result | |||
else | |||
debugLog('ERROR: JSON decode succeeded but missing achievement_types array or not a table') | |||
if type(result) == 'table' then | |||
for k, _ in pairs(result) do | |||
debugLog('Found key in result: ' .. k) | |||
end | |||
end | |||
end | |||
else | else | ||
debugLog('ERROR: JSON decode failed: ' .. tostring(result or 'unknown error')) | debugLog('ERROR: JSON decode failed: ' .. tostring(result or 'unknown error')) | ||
| Line 87: | Line 101: | ||
-- Normalizes achievement type to handle variants or legacy types | -- Normalizes achievement type to handle variants or legacy types | ||
local function normalizeAchievementType(achievementType) | local function normalizeAchievementType(achievementType) | ||
if not achievementType then return nil end | if not achievementType then | ||
debugLog("Normalize called with nil achievement type") | |||
return nil | |||
end | |||
-- Always log the normalization attempt for debugging | |||
debugLog("Normalizing achievement type: '" .. tostring(achievementType) .. "'") | |||
-- If it's already a standard type, return it directly | -- If it's already a standard type, return it directly | ||
| Line 93: | Line 113: | ||
achievementType == "ach1" or | achievementType == "ach1" or | ||
achievementType == "ach2" or | achievementType == "ach2" or | ||
achievementType == "ach3" then | achievementType == "ach3" or | ||
achievementType == "sponsor-role" then | |||
debugLog("Achievement type already standardized: " .. achievementType) | |||
return achievementType | return achievementType | ||
end | end | ||
-- Otherwise check the mapping table | -- Otherwise check the mapping table | ||
local mappedType = ACHIEVEMENT_TYPE_MAPPING[achievementType] or achievementType | |||
if mappedType ~= achievementType then | |||
debugLog("Mapped legacy achievement type '" .. achievementType .. "' to '" .. mappedType .. "'") | |||
end | |||
return mappedType | |||
end | end | ||
| Line 113: | Line 141: | ||
end | end | ||
local | local data = DEFAULT_DATA | ||
local jsonLoadingMethod = "none" | |||
-- SIMPLIFIED LOADING APPROACH - prioritizing mw.text.jsonDecode as it correctly processes achievement types | |||
mw.log("JSON-DEBUG: === SIMPLIFIED JSON LOADING START ===") | |||
-- First get the title and check if page exists | |||
local pageTitle = mw.title.new(ACHIEVEMENT_DATA_PAGE) | |||
if not pageTitle or not pageTitle.exists then | |||
mw.log("JSON-DEBUG: " .. ACHIEVEMENT_DATA_PAGE .. " does not exist or title creation failed") | |||
return DEFAULT_DATA | |||
end | |||
mw.log("JSON-DEBUG: Page exists with content model: " .. tostring(pageTitle.contentModel)) | |||
-- First attempt: Use direct raw content with mw.text.jsonDecode | |||
-- Based on diagnostic output, this is the most reliable method | |||
if mw.text and mw.text.jsonDecode then | |||
local content = nil | local content = nil | ||
local contentSuccess, contentResult = pcall(function() | local contentSuccess, contentResult = pcall(function() | ||
| Line 223: | Line 167: | ||
content = contentResult | content = contentResult | ||
mw.log("JSON-DEBUG: Successfully retrieved raw content, length: " .. #content) | mw.log("JSON-DEBUG: Successfully retrieved raw content, length: " .. #content) | ||
-- Remove any BOM or leading whitespace that might cause issues | -- Remove any BOM or leading whitespace that might cause issues | ||
| Line 235: | Line 175: | ||
end | end | ||
local jsonDecodeSuccess, jsonData = pcall(function() | |||
return mw.text.jsonDecode(content) | |||
mw. | end) | ||
-- | if jsonDecodeSuccess and jsonData and type(jsonData) == 'table' then | ||
if | -- Validate the structure | ||
if jsonData.achievement_types and #jsonData.achievement_types > 0 and jsonData.user_achievements then | |||
mw.log("JSON-DEBUG: ✓ Successfully decoded with mw.text.jsonDecode, found " .. | |||
#jsonData.achievement_types .. " achievement types") | |||
-- Log achievement types | |||
for i, typeData in ipairs(jsonData.achievement_types) do | |||
mw.log("JSON-DEBUG: Type[" .. i .. "]: id=" .. (typeData.id or "nil") .. | |||
", name=" .. (typeData.name or "nil") .. | |||
", type=" .. (typeData.type or "nil")) | |||
end | end | ||
data = jsonData | |||
jsonLoadingMethod = "mw.text.jsonDecode" | |||
else | else | ||
mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode | mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode result missing required fields or empty achievement_types") | ||
if jsonData.achievement_types then | |||
mw.log("JSON-DEBUG: Found achievement_types but it contains " .. | |||
#jsonData.achievement_types .. " items") | |||
end | |||
end | end | ||
else | else | ||
mw.log("JSON-DEBUG: ✗ | mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode failed: " .. | ||
tostring(jsonData or 'unknown error')) | |||
end | end | ||
else | else | ||
mw.log("JSON-DEBUG: ✗ Failed to get content: " .. tostring(contentResult or 'unknown error')) | mw.log("JSON-DEBUG: ✗ Failed to get raw content: " .. | ||
tostring(contentResult or 'unknown error')) | |||
end | end | ||
else | |||
mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode not available") | |||
end | |||
-- Second attempt: Only if first attempt failed, try mw.loadJsonData | |||
if jsonLoadingMethod == "none" and mw.loadJsonData then | |||
mw.log("JSON-DEBUG: Attempting mw.loadJsonData as fallback") | |||
mw. | local loadJsonSuccess, jsonData = pcall(function() | ||
return mw.loadJsonData(ACHIEVEMENT_DATA_PAGE) | |||
end) | |||
-- | if loadJsonSuccess and jsonData and type(jsonData) == 'table' then | ||
-- Validate the structure | |||
if jsonData.achievement_types and #jsonData.achievement_types > 0 and jsonData.user_achievements then | |||
mw.log("JSON-DEBUG: ✓ Successfully loaded with mw.loadJsonData, found " .. | |||
mw.log("JSON-DEBUG: | #jsonData.achievement_types .. " achievement types") | ||
data = jsonData | |||
jsonLoadingMethod = "mw.loadJsonData" | |||
else | |||
mw.log("JSON-DEBUG: ✗ mw.loadJsonData result missing required fields or empty achievement_types") | |||
if jsonData.achievement_types then | |||
mw.log("JSON-DEBUG: Found achievement_types but it contains " .. | |||
#jsonData.achievement_types .. " items") | |||
-- This is likely the issue identified in diagnostics - achievement_types exists but is empty | |||
-- We'll use the data anyway and log the issue | |||
if jsonData.user_achievements then | |||
mw.log("JSON-DEBUG: Using partial data from mw.loadJsonData despite empty achievement_types") | |||
data = jsonData | |||
jsonLoadingMethod = "mw.loadJsonData (partial)" | |||
end | |||
end | |||
end | |||
else | else | ||
mw.log("JSON-DEBUG: mw. | mw.log("JSON-DEBUG: ✗ mw.loadJsonData failed: " .. | ||
tostring(jsonData or 'unknown error')) | |||
end | end | ||
end | end | ||
-- | -- Log which method we used and validation | ||
mw.log("JSON-DEBUG: JSON loading complete using method: " .. jsonLoadingMethod) | |||
if data ~= DEFAULT_DATA then | if data ~= DEFAULT_DATA then | ||
mw.log("JSON-DEBUG: | local achievementTypeCount = data.achievement_types and #data.achievement_types or 0 | ||
local userCount = 0 | |||
if data.user_achievements then | |||
for _, _ in pairs(data.user_achievements) do | |||
userCount = userCount + 1 | |||
end | |||
end | |||
mw.log("JSON-DEBUG: Loaded data with " .. achievementTypeCount .. | |||
" achievement types and " .. userCount .. " users") | |||
-- | -- Validate achievement types have required fields | ||
if | if data.achievement_types then | ||
for i, | for i, typeData in ipairs(data.achievement_types) do | ||
mw.log("JSON-DEBUG: | if not typeData.id or not typeData.name or not typeData.type then | ||
mw.log("JSON-DEBUG: WARNING: Achievement type " .. i .. | |||
" missing required fields (id, name, or type)") | |||
end | |||
end | end | ||
end | end | ||
else | |||
mw.log("JSON-DEBUG: WARNING: Using default data due to loading failures") | |||
end | end | ||
mw.log("JSON-DEBUG: === SIMPLIFIED JSON LOADING END ===") | |||
dataCache = data | dataCache = data | ||
return data | return data | ||
| Line 364: | Line 331: | ||
end | end | ||
-- | -- Log achievement type count to help diagnose issues | ||
debugLog("Found " .. #data.achievement_types .. " achievement types in data") | |||
-- Try to match achievement ID | -- Try to match achievement ID | ||
for i, typeData in ipairs(data.achievement_types) do | for i, typeData in ipairs(data.achievement_types) do | ||
if typeData.id == achievementType then | if typeData.id == achievementType then | ||
debugLog("Found match for " .. achievementType .. " at index " .. i) | |||
if typeData.name and typeData.name ~= "" then | if typeData.name and typeData.name ~= "" then | ||
debugLog("Using name '" .. typeData.name .. "' for type '" .. achievementType .. "'") | |||
return typeData.name | return typeData.name | ||
else | else | ||
debugLog("Type " .. achievementType .. " has no name; using ID as fallback") | |||
return achievementType | return achievementType | ||
end | end | ||
| Line 386: | Line 349: | ||
end | end | ||
-- | -- If we reach here, no match was found - log all achievement types to help diagnose | ||
debugLog("No match found for '" .. achievementType .. "' - logging all available types") | |||
for i, typeData in ipairs(data.achievement_types) do | |||
debugLog("Available type[" .. i .. "]: id=" .. (typeData.id or "nil") .. | |||
", name=" .. (typeData.name or "nil") .. | |||
", type=" .. (typeData.type or "nil")) | |||
end | end | ||
debugLog("No achievement found with type '" .. achievementType .. "'; using ID fallback") | |||
return achievementType | return achievementType | ||
end | end | ||
| Line 427: | Line 378: | ||
local key = tostring(pageId) | local key = tostring(pageId) | ||
debugLog("Looking up achievements for ID: " .. key) | debugLog("Looking up achievements for page ID: " .. key) | ||
-- Try to fetch achievements for this pageId | -- Try to fetch achievements for this pageId | ||
local userAchievements = | local userAchievements = {} | ||
local userAchievementKey = key | |||
-- If no achievements found under normal ID, try alternative | -- Try the direct key first | ||
if data.user_achievements[key] and #data.user_achievements[key] > 0 then | |||
local | debugLog("Found achievements directly under key: " .. key) | ||
userAchievements = data.user_achievements[key] | |||
userAchievementKey = key | |||
-- If no achievements found under normal ID, try alternative formats | |||
elseif key:match("^%d+$") then | |||
local alternateKeys = { | |||
"n" .. key, -- n12345 format | |||
"page" .. key, -- page12345 format | |||
"user" .. key -- user12345 format | |||
} | |||
for _, altKey in ipairs(alternateKeys) do | |||
if data.user_achievements[altKey] and #data.user_achievements[altKey] > 0 then | |||
debugLog("Found achievements under alternate key: " .. altKey) | |||
userAchievements = data.user_achievements[altKey] | |||
userAchievementKey = altKey | |||
break | |||
end | |||
end | |||
end | end | ||
-- Log | -- Log achievement count and details | ||
if #userAchievements > 0 then | if #userAchievements > 0 then | ||
debugLog("Found " .. #userAchievements .. " achievements for | debugLog("Found " .. #userAchievements .. " achievements for page ID " .. key .. " under key " .. userAchievementKey) | ||
for i, ach in ipairs(userAchievements) do | for i, ach in ipairs(userAchievements) do | ||
debugLog(" Achievement " .. i .. ": type=" .. (ach.type or "nil")) | debugLog(" Achievement " .. i .. ": type=" .. (ach.type or "nil")) | ||
end | end | ||
else | else | ||
debugLog("No achievements found for | debugLog("No achievements found for page ID " .. key .. " under any key") | ||
return '', '' | return '', '' | ||
end | end | ||
-- Find the highest-tier achievement (lowest tier number) | |||
local highestTier = 999 | local highestTier = 999 | ||
local highestAchievement = nil | local highestAchievement = nil | ||
| Line 467: | Line 424: | ||
for _, achievement in ipairs(userAchievements) do | for _, achievement in ipairs(userAchievements) do | ||
local achType = achievement.type | local achType = achievement.type | ||
debugLog("Processing achievement type: " .. | if not achType then | ||
debugLog("Achievement missing type property - skipping") | |||
else | |||
debugLog("Processing achievement type: " .. achType) | |||
local tier = | |||
debugLog(" Found type '" .. | -- Find the achievement definition for this type | ||
local achDef = nil | |||
for _, typeData in ipairs(data.achievement_types) do | |||
if typeData.id == achType then | |||
achDef = typeData | |||
break | |||
end | |||
end | |||
if not achDef then | |||
debugLog("No definition found for achievement type: " .. achType) | |||
else | |||
local tier = achDef.tier or 999 | |||
debugLog(" Found type '" .. achDef.id .. "' with tier " .. tier .. | |||
", name '" .. (achDef.name or "nil") .. | |||
"', type '" .. (achDef.type or "nil") .. "'") | |||
if tier < highestTier then | if tier < highestTier then | ||
highestTier = tier | highestTier = tier | ||
highestAchievement = | highestAchievement = achDef | ||
debugLog(" New highest tier achievement: " .. | debugLog(" New highest tier achievement: " .. achDef.id) | ||
end | end | ||
end | end | ||
| Line 483: | Line 456: | ||
if not highestAchievement or not highestAchievement.id then | if not highestAchievement or not highestAchievement.id then | ||
debugLog("No valid top-tier achievement found for | debugLog("No valid top-tier achievement found for page ID " .. key) | ||
return '', '' | return '', '' | ||
end | end | ||
| Line 709: | Line 682: | ||
local key = tostring(pageId) | local key = tostring(pageId) | ||
debugLog("Looking up title achievements for ID: " .. key) | debugLog("Looking up title achievements for page ID: " .. key) | ||
-- Try to fetch achievements for this pageId | -- Try to fetch achievements for this pageId, checking multiple possible key formats | ||
local userAchievements = | local userAchievements = {} | ||
local userAchievementKey = key | |||
-- If no achievements found under normal ID, try alternative | -- Try the direct key first | ||
if data.user_achievements[key] and #data.user_achievements[key] > 0 then | |||
local | debugLog("Found achievements directly under key: " .. key) | ||
userAchievements = data.user_achievements[key] | |||
userAchievementKey = key | |||
-- If no achievements found under normal ID, try alternative formats | |||
elseif key:match("^%d+$") then | |||
local alternateKeys = { | |||
"n" .. key, -- n12345 format | |||
"page" .. key, -- page12345 format | |||
"user" .. key -- user12345 format | |||
} | |||
for _, altKey in ipairs(alternateKeys) do | |||
if data.user_achievements[altKey] and #data.user_achievements[altKey] > 0 then | |||
debugLog("Found achievements under alternate key: " .. altKey) | |||
userAchievements = data.user_achievements[altKey] | |||
userAchievementKey = altKey | |||
break | |||
end | |||
end | |||
end | end | ||
-- Log | -- Log achievement count and details for all pages consistently | ||
if #userAchievements > 0 then | if #userAchievements > 0 then | ||
debugLog("Found " .. #userAchievements .. " achievements for | debugLog("Found " .. #userAchievements .. " achievements for page ID " .. key .. " under key " .. userAchievementKey) | ||
for i, ach in ipairs(userAchievements) do | |||
debugLog(" Achievement " .. i .. ": type=" .. (ach.type or "nil")) | |||
end | end | ||
else | else | ||
debugLog("No achievements found for | debugLog("No achievements found for page ID " .. key .. " under any key") | ||
return '', '', '' | return '', '', '' | ||
end | end | ||
| Line 743: | Line 726: | ||
for _, typeData in ipairs(data.achievement_types) do | for _, typeData in ipairs(data.achievement_types) do | ||
typeDefinitions[typeData.id] = typeData | typeDefinitions[typeData.id] = typeData | ||
debugLog("Loaded definition for type '" .. typeData.id .. | |||
"': type=" .. (typeData.type or "nil") .. | |||
", name=" .. (typeData.name or "nil")) | |||
end | end | ||
-- | -- Find title achievements only by strictly checking the "type" field | ||
local highestTier = 999 | local highestTier = 999 | ||
local titleAchievement = nil | local titleAchievement = nil | ||
| Line 763: | Line 737: | ||
for i, achievement in ipairs(userAchievements) do | for i, achievement in ipairs(userAchievements) do | ||
local achType = achievement.type | local achType = achievement.type | ||
if achType then | if not achType then | ||
debugLog("Achievement " .. i .. " missing type property - skipping") | |||
else | |||
local typeData = typeDefinitions[achType] | local typeData = typeDefinitions[achType] | ||
if typeData then | if not typeData then | ||
-- Check if it's a title type by examining "type" property | debugLog("No definition found for achievement type: " .. achType) | ||
else | |||
-- Check if it's a title type ONLY by examining the "type" property | |||
local achDefType = typeData.type | local achDefType = typeData.type | ||
debugLog("Checking achievement " .. i .. ": id=" .. achType .. | |||
", definition type=" .. (achDefType or "nil")) | |||
-- Only consider achievements with type="title" | -- Only consider achievements with type="title" | ||
if achDefType == "title" then | if achDefType == "title" then | ||
debugLog("Found title achievement: " .. achType) | |||
local tier = typeData.tier or 999 | local tier = typeData.tier or 999 | ||
if tier < highestTier then | if tier < highestTier then | ||
highestTier = tier | highestTier = tier | ||
titleAchievement = typeData | titleAchievement = typeData | ||
debugLog("Using as highest tier title: " .. typeData.id .. " (tier " .. tier .. ")") | |||
end | end | ||
else | |||
debugLog("Skipping non-title achievement: " .. achType .. " (type=" .. (achDefType or "nil") .. ")") | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
if not titleAchievement or not titleAchievement.id then | if not titleAchievement or not titleAchievement.id then | ||
debugLog("No valid title achievement found for | debugLog("No valid title achievement found for page ID " .. key) | ||
return '', '', '' | return '', '', '' | ||
end | end | ||
Revision as of 20:13, 1 April 2025
Documentation for this module may be created at Module:AchievementSystem/doc
-- Module:AchievementSystem
-- Achievement system that loads data from MediaWiki:AchievementData.json.
-- STYLING NOTE: All achievement styling is defined in CSS/Templates.css, not in the JSON.
-- This module only assigns CSS classes based on achievement IDs in the format:
-- .person-template .template-title.achievement-{id}::after {}
--
-- The module does not use any styling information from the JSON data structure.
local Achievements = {}
-- Debug configuration
local DEBUG_MODE = true
local function debugLog(message)
if not DEBUG_MODE then return end
pcall(function()
mw.logObject({
system = "achievement_simple",
message = message,
timestamp = os.date('%H:%M:%S')
})
end)
mw.log("ACHIEVEMENT-DEBUG: " .. message)
end
--------------------------------------------------------------------------------
-- JSON Handling
--------------------------------------------------------------------------------
-- We'll use MediaWiki's built-in JSON functions directly, no external module needed
local function jsonDecode(jsonString)
if not jsonString then
debugLog('ERROR: Empty JSON string provided')
return nil
end
if mw.text and mw.text.jsonDecode then
local success, result = pcall(function()
return mw.text.jsonDecode(jsonString)
end)
if success and result then
-- Validate the structure has achievement_types array
if type(result) == 'table' and result.achievement_types then
debugLog('SUCCESS: JSON decode successful with ' .. #result.achievement_types .. ' achievement types')
return result
else
debugLog('ERROR: JSON decode succeeded but missing achievement_types array or not a table')
if type(result) == 'table' then
for k, _ in pairs(result) do
debugLog('Found key in result: ' .. k)
end
end
end
else
debugLog('ERROR: JSON decode failed: ' .. tostring(result or 'unknown error'))
end
end
debugLog('CRITICAL ERROR: mw.text.jsonDecode not available!')
return nil
end
-- Simple HTML encode fallback
local function htmlEncode(str)
if mw.text and mw.text.htmlEncode then
return mw.text.htmlEncode(str or '')
else
return (str or '')
:gsub('&', '&')
:gsub('<', '<')
:gsub('>', '>')
:gsub('"', '"')
end
end
--------------------------------------------------------------------------------
-- Configuration, Default Data, and Cache
--------------------------------------------------------------------------------
local ACHIEVEMENT_DATA_PAGE = 'MediaWiki:AchievementData.json'
local dataCache = nil
local DEFAULT_DATA = {
schema_version = 1,
last_updated = os.date('!%Y-%m-%dT%H:%M:%SZ'),
achievement_types = {},
user_achievements = {},
cache_control = { version = 0 }
}
--------------------------------------------------------------------------------
-- Configuration
--------------------------------------------------------------------------------
-- This array maps legacy achievement IDs to standardized ones
local ACHIEVEMENT_TYPE_MAPPING = {
["title-test"] = "dev-role",
["jedi"] = "ach1",
["champion"] = "ach2",
["sponsor"] = "ach3"
}
-- Normalizes achievement type to handle variants or legacy types
local function normalizeAchievementType(achievementType)
if not achievementType then
debugLog("Normalize called with nil achievement type")
return nil
end
-- Always log the normalization attempt for debugging
debugLog("Normalizing achievement type: '" .. tostring(achievementType) .. "'")
-- If it's already a standard type, return it directly
if achievementType == "dev-role" or
achievementType == "ach1" or
achievementType == "ach2" or
achievementType == "ach3" or
achievementType == "sponsor-role" then
debugLog("Achievement type already standardized: " .. achievementType)
return achievementType
end
-- Otherwise check the mapping table
local mappedType = ACHIEVEMENT_TYPE_MAPPING[achievementType] or achievementType
if mappedType ~= achievementType then
debugLog("Mapped legacy achievement type '" .. achievementType .. "' to '" .. mappedType .. "'")
end
return mappedType
end
--------------------------------------------------------------------------------
-- Load achievement data from the JSON page
--------------------------------------------------------------------------------
function Achievements.loadData()
mw.log("JSON-DEBUG: Starting to load achievement data")
-- Use the request-level cache if we already loaded data once
if dataCache then
mw.log("JSON-DEBUG: Using request-level cached data")
return dataCache
end
local data = DEFAULT_DATA
local jsonLoadingMethod = "none"
-- SIMPLIFIED LOADING APPROACH - prioritizing mw.text.jsonDecode as it correctly processes achievement types
mw.log("JSON-DEBUG: === SIMPLIFIED JSON LOADING START ===")
-- First get the title and check if page exists
local pageTitle = mw.title.new(ACHIEVEMENT_DATA_PAGE)
if not pageTitle or not pageTitle.exists then
mw.log("JSON-DEBUG: " .. ACHIEVEMENT_DATA_PAGE .. " does not exist or title creation failed")
return DEFAULT_DATA
end
mw.log("JSON-DEBUG: Page exists with content model: " .. tostring(pageTitle.contentModel))
-- First attempt: Use direct raw content with mw.text.jsonDecode
-- Based on diagnostic output, this is the most reliable method
if mw.text and mw.text.jsonDecode then
local content = nil
local contentSuccess, contentResult = pcall(function()
return pageTitle:getContent()
end)
if contentSuccess and contentResult and contentResult ~= "" then
content = contentResult
mw.log("JSON-DEBUG: Successfully retrieved raw content, length: " .. #content)
-- 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
local jsonDecodeSuccess, jsonData = pcall(function()
return mw.text.jsonDecode(content)
end)
if jsonDecodeSuccess and jsonData and type(jsonData) == 'table' then
-- Validate the structure
if jsonData.achievement_types and #jsonData.achievement_types > 0 and jsonData.user_achievements then
mw.log("JSON-DEBUG: ✓ Successfully decoded with mw.text.jsonDecode, found " ..
#jsonData.achievement_types .. " achievement types")
-- Log achievement types
for i, typeData in ipairs(jsonData.achievement_types) do
mw.log("JSON-DEBUG: Type[" .. i .. "]: id=" .. (typeData.id or "nil") ..
", name=" .. (typeData.name or "nil") ..
", type=" .. (typeData.type or "nil"))
end
data = jsonData
jsonLoadingMethod = "mw.text.jsonDecode"
else
mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode result missing required fields or empty achievement_types")
if jsonData.achievement_types then
mw.log("JSON-DEBUG: Found achievement_types but it contains " ..
#jsonData.achievement_types .. " items")
end
end
else
mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode failed: " ..
tostring(jsonData or 'unknown error'))
end
else
mw.log("JSON-DEBUG: ✗ Failed to get raw content: " ..
tostring(contentResult or 'unknown error'))
end
else
mw.log("JSON-DEBUG: ✗ mw.text.jsonDecode not available")
end
-- Second attempt: Only if first attempt failed, try mw.loadJsonData
if jsonLoadingMethod == "none" and mw.loadJsonData then
mw.log("JSON-DEBUG: Attempting mw.loadJsonData as fallback")
local loadJsonSuccess, jsonData = pcall(function()
return mw.loadJsonData(ACHIEVEMENT_DATA_PAGE)
end)
if loadJsonSuccess and jsonData and type(jsonData) == 'table' then
-- Validate the structure
if jsonData.achievement_types and #jsonData.achievement_types > 0 and jsonData.user_achievements then
mw.log("JSON-DEBUG: ✓ Successfully loaded with mw.loadJsonData, found " ..
#jsonData.achievement_types .. " achievement types")
data = jsonData
jsonLoadingMethod = "mw.loadJsonData"
else
mw.log("JSON-DEBUG: ✗ mw.loadJsonData result missing required fields or empty achievement_types")
if jsonData.achievement_types then
mw.log("JSON-DEBUG: Found achievement_types but it contains " ..
#jsonData.achievement_types .. " items")
-- This is likely the issue identified in diagnostics - achievement_types exists but is empty
-- We'll use the data anyway and log the issue
if jsonData.user_achievements then
mw.log("JSON-DEBUG: Using partial data from mw.loadJsonData despite empty achievement_types")
data = jsonData
jsonLoadingMethod = "mw.loadJsonData (partial)"
end
end
end
else
mw.log("JSON-DEBUG: ✗ mw.loadJsonData failed: " ..
tostring(jsonData or 'unknown error'))
end
end
-- Log which method we used and validation
mw.log("JSON-DEBUG: JSON loading complete using method: " .. jsonLoadingMethod)
if data ~= DEFAULT_DATA then
local achievementTypeCount = data.achievement_types and #data.achievement_types or 0
local userCount = 0
if data.user_achievements then
for _, _ in pairs(data.user_achievements) do
userCount = userCount + 1
end
end
mw.log("JSON-DEBUG: Loaded data with " .. achievementTypeCount ..
" achievement types and " .. userCount .. " users")
-- Validate achievement types have required fields
if data.achievement_types then
for i, typeData in ipairs(data.achievement_types) do
if not typeData.id or not typeData.name or not typeData.type then
mw.log("JSON-DEBUG: WARNING: Achievement type " .. i ..
" missing required fields (id, name, or type)")
end
end
end
else
mw.log("JSON-DEBUG: WARNING: Using default data due to loading failures")
end
mw.log("JSON-DEBUG: === SIMPLIFIED JSON LOADING END ===")
dataCache = data
return data
end
--------------------------------------------------------------------------------
-- Check if a page/user has any achievements
--------------------------------------------------------------------------------
function Achievements.hasAchievements(pageId)
if not pageId or pageId == '' then
return false
end
local data = Achievements.loadData()
if not data or not data.user_achievements then
return false
end
local key = tostring(pageId)
if data.user_achievements[key] and #data.user_achievements[key] > 0 then
return true
end
-- Check for legacy "n123" style
if key:match("^%d+$") then
local alt = "n" .. key
if data.user_achievements[alt] and #data.user_achievements[alt] > 0 then
return true
end
end
-- We removed the forced "true" for test pages to avoid dev-role injection
return false
end
--------------------------------------------------------------------------------
-- Get a user-friendly name for a given achievement type
--------------------------------------------------------------------------------
function Achievements.getAchievementName(achievementType)
if not achievementType or achievementType == '' then
debugLog("Empty achievement type provided to getAchievementName")
mw.log("ACHIEVEMENT-NAME-ERROR: Received empty achievementType")
return 'Unknown'
end
debugLog("Looking up achievement name for type: '" .. tostring(achievementType) .. "'")
local data = Achievements.loadData()
if not data or not data.achievement_types then
mw.log("ACHIEVEMENT-NAME-ERROR: No achievement data or achievement_types missing")
return achievementType
end
-- Log achievement type count to help diagnose issues
debugLog("Found " .. #data.achievement_types .. " achievement types in data")
-- Try to match achievement ID
for i, typeData in ipairs(data.achievement_types) do
if typeData.id == achievementType then
debugLog("Found match for " .. achievementType .. " at index " .. i)
if typeData.name and typeData.name ~= "" then
debugLog("Using name '" .. typeData.name .. "' for type '" .. achievementType .. "'")
return typeData.name
else
debugLog("Type " .. achievementType .. " has no name; using ID as fallback")
return achievementType
end
end
end
-- If we reach here, no match was found - log all achievement types to help diagnose
debugLog("No match found for '" .. achievementType .. "' - logging all available types")
for i, typeData in ipairs(data.achievement_types) do
debugLog("Available type[" .. i .. "]: id=" .. (typeData.id or "nil") ..
", name=" .. (typeData.name or "nil") ..
", type=" .. (typeData.type or "nil"))
end
debugLog("No achievement found with type '" .. achievementType .. "'; using ID fallback")
return achievementType
end
--------------------------------------------------------------------------------
-- Find the top-tier achievement for the user (lowest tier number)
-- Return the CSS class and the readable achievement name
--------------------------------------------------------------------------------
function Achievements.getTitleClass(pageId)
if not pageId or pageId == '' then
debugLog("Empty page ID provided to getTitleClass")
return '', ''
end
local data = Achievements.loadData()
if not data or not data.user_achievements then
debugLog("No achievement data available in getTitleClass")
return '', ''
end
local key = tostring(pageId)
debugLog("Looking up achievements for page ID: " .. key)
-- Try to fetch achievements for this pageId
local userAchievements = {}
local userAchievementKey = key
-- Try the direct key first
if data.user_achievements[key] and #data.user_achievements[key] > 0 then
debugLog("Found achievements directly under key: " .. key)
userAchievements = data.user_achievements[key]
userAchievementKey = key
-- If no achievements found under normal ID, try alternative formats
elseif key:match("^%d+$") then
local alternateKeys = {
"n" .. key, -- n12345 format
"page" .. key, -- page12345 format
"user" .. key -- user12345 format
}
for _, altKey in ipairs(alternateKeys) do
if data.user_achievements[altKey] and #data.user_achievements[altKey] > 0 then
debugLog("Found achievements under alternate key: " .. altKey)
userAchievements = data.user_achievements[altKey]
userAchievementKey = altKey
break
end
end
end
-- Log achievement count and details
if #userAchievements > 0 then
debugLog("Found " .. #userAchievements .. " achievements for page ID " .. key .. " under key " .. userAchievementKey)
for i, ach in ipairs(userAchievements) do
debugLog(" Achievement " .. i .. ": type=" .. (ach.type or "nil"))
end
else
debugLog("No achievements found for page ID " .. key .. " under any key")
return '', ''
end
-- Find the highest-tier achievement (lowest tier number)
local highestTier = 999
local highestAchievement = nil
for _, achievement in ipairs(userAchievements) do
local achType = achievement.type
if not achType then
debugLog("Achievement missing type property - skipping")
else
debugLog("Processing achievement type: " .. achType)
-- Find the achievement definition for this type
local achDef = nil
for _, typeData in ipairs(data.achievement_types) do
if typeData.id == achType then
achDef = typeData
break
end
end
if not achDef then
debugLog("No definition found for achievement type: " .. achType)
else
local tier = achDef.tier or 999
debugLog(" Found type '" .. achDef.id .. "' with tier " .. tier ..
", name '" .. (achDef.name or "nil") ..
"', type '" .. (achDef.type or "nil") .. "'")
if tier < highestTier then
highestTier = tier
highestAchievement = achDef
debugLog(" New highest tier achievement: " .. achDef.id)
end
end
end
end
if not highestAchievement or not highestAchievement.id then
debugLog("No valid top-tier achievement found for page ID " .. key)
return '', ''
end
local cssClass = "achievement-" .. highestAchievement.id
local displayName = highestAchievement.name or highestAchievement.id or "Award"
debugLog("Using top-tier achievement: " .. cssClass .. " with name: " .. displayName)
return cssClass, displayName
end
--------------------------------------------------------------------------------
-- Renders a simple "box" with the top-tier achievement for the user
--------------------------------------------------------------------------------
function Achievements.renderAchievementBox(pageId)
if not pageId or pageId == '' then
return ''
end
local data = Achievements.loadData()
if not data or not data.user_achievements then
return ''
end
local key = tostring(pageId)
local userAchievements = data.user_achievements[key]
if (not userAchievements or #userAchievements == 0) and key:match("^%d+$") then
userAchievements = data.user_achievements["n" .. key]
end
if not userAchievements or #userAchievements == 0 then
return ''
end
-- Build a lookup table for achievement type definitions
local typeDefinitions = {}
if data and data.achievement_types then
for _, typeData in ipairs(data.achievement_types) do
if typeData.id and typeData.name then
typeDefinitions[typeData.id] = {
name = typeData.name,
tier = typeData.tier or 999
}
end
end
end
-- Look for the highest-tier achievement (lowest tier number)
local highestTier = 999
local topAchType = nil
for _, achievement in ipairs(userAchievements) do
local achType = achievement.type
if typeDefinitions[achType] and typeDefinitions[achType].tier < highestTier then
highestTier = typeDefinitions[achType].tier
topAchType = achType
end
end
-- If we found an achievement, render it
if topAchType and typeDefinitions[topAchType] then
local achName = typeDefinitions[topAchType].name or topAchType
return string.format(
'<div class="achievement-box-simple" data-achievement-type="%s">%s</div>',
topAchType,
htmlEncode(achName)
)
end
return ''
end
--------------------------------------------------------------------------------
-- Simple pass-through to track pages (for future expansions)
--------------------------------------------------------------------------------
function Achievements.trackPage(pageId, pageName)
return true
end
--------------------------------------------------------------------------------
-- Retrieve a specific achievement if present, by type
--------------------------------------------------------------------------------
function Achievements.getSpecificAchievement(pageId, achievementType)
debugLog("ACHIEVEMENT-DEBUG: Looking for '" .. tostring(achievementType) ..
"' in page ID: " .. tostring(pageId))
if not pageId or not achievementType or pageId == '' then
debugLog("ACHIEVEMENT-DEBUG: Invalid arguments for getSpecificAchievement")
return nil
end
local data = Achievements.loadData()
if not data or not data.user_achievements then
debugLog("ACHIEVEMENT-DEBUG: No achievement data loaded")
return nil
end
local key = tostring(pageId)
local userAchievements = data.user_achievements[key] or {}
-- If no achievements found under normal ID, try alternative format
if #userAchievements == 0 and key:match("^%d+$") then
local altKey = "n" .. key
debugLog("No achievements under ID '" .. key .. "', trying alternative ID: '" .. altKey .. "'")
userAchievements = data.user_achievements[altKey] or {}
end
-- Direct lookup for the requested achievement type
for _, achievement in ipairs(userAchievements) do
if achievement.type == achievementType then
debugLog("FOUND ACHIEVEMENT: " .. achievementType .. " for user " .. key)
return achievement
end
end
debugLog("ACHIEVEMENT-DEBUG: No match found for achievement type: " .. achievementType)
return nil
end
--------------------------------------------------------------------------------
-- Get achievement definition directly from JSON data
--------------------------------------------------------------------------------
function Achievements.getAchievementDefinition(achievementType)
if not achievementType or achievementType == '' then
debugLog("ACHIEVEMENT-DEF: Empty achievement type")
return nil
end
local data = Achievements.loadData()
if not data or not data.achievement_types then
debugLog("ACHIEVEMENT-DEF: No achievement data loaded")
return nil
end
-- Direct lookup in achievement_types array
for _, typeData in ipairs(data.achievement_types) do
if typeData.id == achievementType then
debugLog("ACHIEVEMENT-DEF: Found definition for " .. achievementType)
return typeData
end
end
debugLog("ACHIEVEMENT-DEF: No definition found for " .. achievementType)
return nil
end
--------------------------------------------------------------------------------
-- Diagnostic Function: List all badges for a page with details
--------------------------------------------------------------------------------
function Achievements.debugBadgesForPage(pageId)
if not pageId or pageId == '' then
mw.log("BADGE-DEBUG: Empty page ID")
return "ERROR: No page ID provided"
end
local data = Achievements.loadData()
if not data then
mw.log("BADGE-DEBUG: Failed to load achievement data")
return "ERROR: Failed to load achievement data"
end
local key = tostring(pageId)
local userAchievements = data.user_achievements[key] or {}
-- Check alternate keys if needed
if #userAchievements == 0 and key:match("^%d+$") then
local altKey = "n" .. key
userAchievements = data.user_achievements[altKey] or {}
if #userAchievements > 0 then
mw.log("BADGE-DEBUG: Found achievements under alternate key: " .. altKey)
end
end
if #userAchievements == 0 then
mw.log("BADGE-DEBUG: No achievements found for page ID " .. pageId)
return "No achievements found for page ID " .. pageId
end
-- Build debug report
local output = {}
table.insert(output, "=== BADGE DEBUG FOR PAGE " .. pageId .. " ===")
table.insert(output, "Found " .. #userAchievements .. " achievements")
-- Check each achievement in detail
for i, achievement in ipairs(userAchievements) do
local achType = achievement.type or "nil"
local typeDef = Achievements.getAchievementDefinition(achType)
table.insert(output, "\nACHIEVEMENT " .. i .. ":")
table.insert(output, " Type: " .. achType)
if typeDef then
table.insert(output, " Name: " .. (typeDef.name or "unnamed"))
table.insert(output, " Definition Type: " .. (typeDef.type or "unspecified"))
table.insert(output, " Description: " .. (typeDef.description or "none"))
if typeDef.tier then
table.insert(output, " Tier: " .. typeDef.tier)
end
else
table.insert(output, " WARNING: No type definition found!")
end
end
return table.concat(output, "\n")
end
--------------------------------------------------------------------------------
-- Find and return title achievement for the user if one exists
-- This specifically looks for achievements with type="title"
-- Return the CSS class, readable achievement name, and achievement ID (or empty strings if none found)
--------------------------------------------------------------------------------
function Achievements.getTitleAchievement(pageId)
if not pageId or pageId == '' then
debugLog("Empty page ID provided to getTitleAchievement")
return '', '', ''
end
local data = Achievements.loadData()
if not data or not data.user_achievements then
debugLog("No achievement data available in getTitleAchievement")
return '', '', ''
end
local key = tostring(pageId)
debugLog("Looking up title achievements for page ID: " .. key)
-- Try to fetch achievements for this pageId, checking multiple possible key formats
local userAchievements = {}
local userAchievementKey = key
-- Try the direct key first
if data.user_achievements[key] and #data.user_achievements[key] > 0 then
debugLog("Found achievements directly under key: " .. key)
userAchievements = data.user_achievements[key]
userAchievementKey = key
-- If no achievements found under normal ID, try alternative formats
elseif key:match("^%d+$") then
local alternateKeys = {
"n" .. key, -- n12345 format
"page" .. key, -- page12345 format
"user" .. key -- user12345 format
}
for _, altKey in ipairs(alternateKeys) do
if data.user_achievements[altKey] and #data.user_achievements[altKey] > 0 then
debugLog("Found achievements under alternate key: " .. altKey)
userAchievements = data.user_achievements[altKey]
userAchievementKey = altKey
break
end
end
end
-- Log achievement count and details for all pages consistently
if #userAchievements > 0 then
debugLog("Found " .. #userAchievements .. " achievements for page ID " .. key .. " under key " .. userAchievementKey)
for i, ach in ipairs(userAchievements) do
debugLog(" Achievement " .. i .. ": type=" .. (ach.type or "nil"))
end
else
debugLog("No achievements found for page ID " .. key .. " under any key")
return '', '', ''
end
-- Build a table of achievement definitions for quick lookup
local typeDefinitions = {}
for _, typeData in ipairs(data.achievement_types) do
typeDefinitions[typeData.id] = typeData
debugLog("Loaded definition for type '" .. typeData.id ..
"': type=" .. (typeData.type or "nil") ..
", name=" .. (typeData.name or "nil"))
end
-- Find title achievements only by strictly checking the "type" field
local highestTier = 999
local titleAchievement = nil
for i, achievement in ipairs(userAchievements) do
local achType = achievement.type
if not achType then
debugLog("Achievement " .. i .. " missing type property - skipping")
else
local typeData = typeDefinitions[achType]
if not typeData then
debugLog("No definition found for achievement type: " .. achType)
else
-- Check if it's a title type ONLY by examining the "type" property
local achDefType = typeData.type
debugLog("Checking achievement " .. i .. ": id=" .. achType ..
", definition type=" .. (achDefType or "nil"))
-- Only consider achievements with type="title"
if achDefType == "title" then
debugLog("Found title achievement: " .. achType)
local tier = typeData.tier or 999
if tier < highestTier then
highestTier = tier
titleAchievement = typeData
debugLog("Using as highest tier title: " .. typeData.id .. " (tier " .. tier .. ")")
end
else
debugLog("Skipping non-title achievement: " .. achType .. " (type=" .. (achDefType or "nil") .. ")")
end
end
end
end
if not titleAchievement or not titleAchievement.id then
debugLog("No valid title achievement found for page ID " .. key)
return '', '', ''
end
local cssClass = "achievement-" .. titleAchievement.id
local displayName = titleAchievement.name or titleAchievement.id or "Award"
local achievementId = titleAchievement.id
debugLog("Using title achievement: " .. cssClass .. " with name: " .. displayName)
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
-- We don't use Module:JSON anymore, just note that we're using built-in functions
table.insert(output, "ℹ️ Using MediaWiki's built-in JSON functions")
-- 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
return Achievements