Module:AchievementSystem: Difference between revisions
Appearance
// via Wikitext Extension for VSCode |
minor rev // via Wikitext Extension for VSCode |
||
| Line 1: | Line 1: | ||
-- | -- Module:AchievementSystem | ||
Implements the ICANNWiki Achievement System. It provides functions to: | -- Implements the ICANNWiki Achievement System. It provides functions to: 1) Load and cache achievement data from MediaWiki:AchievementData.json; 2) Retrieve achievement information for users; 3) Render achievement displays for Person templates; 4) Track pages using achievements for cache invalidation | ||
local Achievements = {} | local Achievements = {} | ||
Revision as of 21:58, 28 March 2025
Documentation for this module may be created at Module:AchievementSystem/doc
-- Module:AchievementSystem
-- Implements the ICANNWiki Achievement System. It provides functions to: 1) Load and cache achievement data from MediaWiki:AchievementData.json; 2) Retrieve achievement information for users; 3) Render achievement displays for Person templates; 4) Track pages using achievements for cache invalidation
local Achievements = {}
local json = require('Module:JSON')
-- Constants
local ACHIEVEMENT_DATA_PAGE = 'MediaWiki:AchievementData.json'
local CACHE_VERSION_KEY = 'achievement_cache_version'
local DEFAULT_CACHE_VERSION = 0
-- Cache for achievement data (within request)
local dataCache = nil
local cacheVersion = nil
-- Safely attempts to get page content, returns nil on error or if page doesn't exist
local function safeGetPageContent(pageName)
local success, page = pcall(function() return mw.title.new(pageName) end)
if not success or not page or not page.exists then
return nil
end
local content = page:getContent()
if not content or content == '' then
return nil
end
return content
end
-- Safely attempts to parse JSON, returns nil on error
local function safeParseJSON(jsonString)
if not jsonString then return nil end
local success, data = pcall(function() return json.decode(jsonString) end)
if not success or not data then
return nil
end
return data
end
-- Returns a default empty achievement data structure
local function getDefaultData()
return {
schema_version = 1,
last_updated = os.date('!%Y-%m-%dT%H:%M:%SZ'),
achievement_types = {},
user_achievements = {},
cache_control = { version = DEFAULT_CACHE_VERSION }
}
end
-- Loads achievement data from MediaWiki:AchievementData.json. Uses caching to avoid repeated loading within the same page render. Defaults empty structure on failure.
function Achievements.loadData()
-- First, check the in-module request cache
if dataCache then
return dataCache
end
-- Try to load from mw.loadData cache (parser cache)
local cacheSuccess, cachedData = pcall(function()
return mw.loadData('Module:AchievementData')
end)
-- If cached data was loaded successfully, validate cache version
if cacheSuccess and cachedData and cachedData.cache_control then
local currentVersion = cachedData.cache_control.version or DEFAULT_CACHE_VERSION
-- Use cached data if version hasn't changed
if cacheVersion == nil then
-- Initialize cacheVersion by checking the real data
local content = safeGetPageContent(ACHIEVEMENT_DATA_PAGE)
local freshData = safeParseJSON(content)
if freshData and freshData.cache_control then
cacheVersion = freshData.cache_control.version or DEFAULT_CACHE_VERSION
else
cacheVersion = DEFAULT_CACHE_VERSION
end
end
-- If versions match, use the cached data
if currentVersion == cacheVersion then
dataCache = cachedData
return dataCache
end
end
-- Load data directly from the wiki page if cache is invalid
local content = safeGetPageContent(ACHIEVEMENT_DATA_PAGE)
local data = safeParseJSON(content)
-- If something went wrong, use default empty data
if not data then
mw.log('Error loading achievement data - using default empty structure')
data = getDefaultData()
end
-- Update cache variables
dataCache = data
cacheVersion = data.cache_control and data.cache_control.version or DEFAULT_CACHE_VERSION
return data
end
--[[
Checks if a user has any achievements:
@param username string to check (with or without "User:" prefix)
@return boolean True if the user has any achievements, false otherwise
]]
function Achievements.hasAchievements(username)
if not username or username == '' then return false end
-- Normalize username (ensure it has User: prefix)
if not username:match('^User:') then
username = 'User:' .. username
end
local data = Achievements.loadData()
local userAchievements = data.user_achievements[username]
return userAchievements and #userAchievements > 0
end
--[[
Gets the highest tier achievement for a user:
@param username string The username to check (with or without "User:" prefix)
@return table|nil The achievement type data or nil if user has no achievements
]]
function Achievements.getHighestAchievement(username)
if not username or username == '' then return nil end
-- Normalize username (ensure it has User: prefix)
if not username:match('^User:') then
username = 'User:' .. username
end
local data = Achievements.loadData()
local userAchievements = data.user_achievements[username]
if not userAchievements or #userAchievements == 0 then return nil end
-- Find achievement with lowest tier number (highest importance)
local highestAchievement = nil
local highestTier = 999
for _, achievement in ipairs(userAchievements) do
local achievementType = achievement.type
for _, typeData in ipairs(data.achievement_types) do
if typeData.id == achievementType and (typeData.tier or 999) < highestTier then
highestAchievement = typeData
highestTier = typeData.tier or 999
end
end
end
return highestAchievement
end
--[[
Gets the CSS class for the highest achievement to be applied to the template title:
@param username string The username to check
@return string The CSS class name or empty string if no achievement
]]
function Achievements.getTitleClass(username)
local achievement = Achievements.getHighestAchievement(username)
if not achievement then return '' end
return 'achievement-' .. achievement.id
end
--[[
Gets all achievements for a user, formatted for display:
@param username string The username to check
@return table Array of achievement data objects for display
]]
function Achievements.getUserAchievements(username)
if not username or username == '' then return {} end
-- Normalize username (ensure it has User: prefix)
if not username:match('^User:') then
username = 'User:' .. username
end
local data = Achievements.loadData()
local userAchievements = data.user_achievements[username] or {}
local result = {}
for _, achievement in ipairs(userAchievements) do
local achievementType = achievement.type
for _, typeData in ipairs(data.achievement_types) do
if typeData.id == achievementType then
table.insert(result, {
id = typeData.id,
name = typeData.name,
description = typeData.description,
icon = typeData.display.icon,
color = typeData.display.color,
background = typeData.display.background,
granted_date = achievement.granted_date
})
end
end
end
-- Sort by tier
table.sort(result, function(a, b)
local tierA = 999
local tierB = 999
for _, typeData in ipairs(data.achievement_types) do
if typeData.id == a.id then tierA = typeData.tier or 999 end
if typeData.id == b.id then tierB = typeData.tier or 999 end
end
return tierA < tierB
end)
return result
end
--[[
Renders HTML for an achievement box to display in templates:
@param username string The username to render achievements for
@return string HTML for the achievement box or empty string if no achievements
]]
function Achievements.renderAchievementBox(username)
local achievements = Achievements.getUserAchievements(username)
if #achievements == 0 then return '' end
local html = '<div class="achievement-box">'
html = html .. '<div class="achievement-box-title">Achievements</div>'
html = html .. '<div class="achievement-badges">'
for _, achievement in ipairs(achievements) do
html = html .. string.format(
'<div class="achievement-badge" style="color: %s; background-color: %s;" title="%s">%s %s</div>',
achievement.color,
achievement.background,
mw.text.htmlEncode(achievement.description or ''),
achievement.icon or '',
mw.text.htmlEncode(achievement.name or '')
)
end
html = html .. '</div></div>'
return html
end
--[[
Tracks a page that displays achievements for cache purging
Note: This would ideally update the JSON with page references, but
for now we rely on the cache version mechanism for invalidation
@param pageName string The page name to track
@return boolean Always returns true (for future expansion)
]]
function Achievements.trackPage(pageName)
-- This would ideally be implemented to write back to the JSON
-- For now, we just return true and rely on version-based cache invalidation
return true
end
-- Return the module
return Achievements