Module:SongData

local public = {} local private = {} local json = require("Module:JSON")

-- Load song data and pack order local titleobject = mw.title.new("User:Jono99/Song Data") local song_data = json.decode(titleobject:getContent)

-- Removes leading and trailing spaces from strings function private.trimWhitespace(str) local _start = 1 while (str:sub(_start, _start) == " ") do       _start = _start + 1 end local _end = str:len while (str:sub(_end, _end) == " ") do       _end = _end - 1 end return str:sub(_start, _end) end

-- Checks if a table has a specific key function private.hasKey(_table, key) return _table[key] ~= nil end

-- Gets a specific item from a table, with a fallback if the table doesn't have that key function private.getValue(_table, key, fallback) if private.hasKey(_table, key) then return _table[key] else return fallback end end

-- Converts artist string/table into string or link to that artist's section in Artists function private.getArtist(get, linkable) local artist = get if type(artist) == "table" then artist = get["format"] for i,v in ipairs(get) do           local artist_alias1, artist_alias2 = artist:find("<<<" .. i .. "|.->>>") if artist_alias1 then if linkable then artist = artist:sub(0, artist_alias1 - 1) .. "Artists" .. artist:sub(artist_alias2 + 1) else artist = artist:sub(0, artist_alias1 - 1) .. artist:sub(artist_alias1 + 4 + tostring(i):len, artist_alias2 - 3) .. artist:sub(artist_alias2 + 1) end else local replace = v               if linkable then replace = "" .. v .. "" end artist = artist:gsub("<<<" .. i .. ">>>", replace) end end elseif linkable then artist = "" .. artist .. "" end return artist end

function private.compareSongs(a, b)   if a["version"] == b["version"] then return a["position"] < b["position"] end return a["version"] < b["version"] end

function private.getTableRaw(_table, args) local pos = _table local no_key_response = "ERROR: This table doesn't have the key " for _, v in ipairs(args) do       local v = private.trimWhitespace(v) no_key_response = no_key_response .. '[' .. v .. ']'       if private.hasKey(pos, v) then pos = pos[v] else return no_key_response end end if type(pos) == "table" then local retval = "ERROR: the requested value is a table. Keys:" for k, v in pairs(pos) do           retval = retval .. " - " .. k .. ": " .. tostring(v) end return retval else return pos end end

-- Gets if a song has the artist specified function private.songHasArtist(song, artist) if private.hasKey(song, "artist") == false then return false end local song_artist = song["artist"] if type(song_artist) == "table" then for _, v in ipairs(song_artist) do			if v == artist then return true end end return false else return song_artist == artist end end

-- Debug function to make sure the process of loading the song data is working properly function public.getSongData return song_data end

-- Returns the full name of a pack, if it exists function public.getPackName(pack_id) if private.hasKey(pack_order, pack_id) then local pack = pack_order[pack_id] local retval = "" if private.hasKey(pack, "subtitle") then retval = pack["subtitle"] .. " - "       end retval = retval .. private.getValue(pack, "name", "") return retval end return nil end

-- Strips the decimal point off a number function public.truncateDecimal(num) local _type = type(num) if _type == "table" then return public.truncateDecimal(tonumber(private.trimWhitespace(num["args"][1]))) elseif _type == "number" then return math.floor(num) else return num end end

-- Returns an instance of the Song template, with all information filled in, taken from the Song Data JSON function public.Song(frame) if private.hasKey(frame.args, 1) then local song_key = private.trimWhitespace(frame.args[1]) if private.hasKey(song_data, song_key) then -- Get song data from JSON and build template arguments local song_template = "Song" local song_data = song_data[song_key] local tcp = {} -- Template call parameters tcp["title"] = song_data["title"] tcp["image"] = song_data["image"] if private.hasKey(song_data, "basic") then tcp["basic_level"] = song_data["basic"]["level"] tcp["basic_notes"] = song_data["basic"]["notes"] tcp["basic_charter"] = song_data["basic"]["charter"] end if private.hasKey(song_data, "normal") then tcp["normal_level"] = song_data["normal"]["level"] tcp["normal_notes"] = song_data["normal"]["notes"] tcp["normal_charter"] = song_data["normal"]["charter"] end if private.hasKey(song_data, "advanced") then tcp["advanced_level"] = song_data["advanced"]["level"] tcp["advanced_notes"] = song_data["advanced"]["notes"] tcp["advanced_charter"] = song_data["advanced"]["charter"] end if private.hasKey(song_data, "abyss") then tcp["abyss_level"] = song_data["abyss"]["level"] tcp["abyss_notes"] = song_data["abyss"]["notes"] tcp["abyss_charter"] = song_data["abyss"]["charter"] end if private.hasKey(song_data, "parallel") then tcp["parallel_level"] = song_data["parallel"]["level"] tcp["parallel_notes"] = song_data["parallel"]["notes"] tcp["parallel_charter"] = song_data["parallel"]["charter"] end if private.hasKey(song_data, "artist") then tcp["artist"] = private.getArtist(song_data["artist"], false) end tcp["length"] = song_data["length"] tcp["charter"] = song_data["charter"] tcp["illustration"] = song_data["illustration"] tcp["bpm"] = song_data["bpm"] if type(tcp["bpm"]) == "table" then local bpm = tcp["bpm"]["min"] if (bpm ~= tcp["bpm"]["max"]) then bpm = tostring(bpm) .. "~" .. tostring(tcp["bpm"]["max"]) end bpm = tostring(bpm) if private.getValue(tcp["bpm"], "unstable", false) then bpm = bpm .. "?"           	end tcp["bpm"] = bpm end tcp["genre"] = song_data["genre"] if type(tcp["genre"]) == "table" then local genres = "" for i, v in ipairs(tcp["genre"]) do           		if i > 1 then genres = genres .. ", "           		end genres = genres .. v           	end tcp["genre"] = genres end return frame:expandTemplate{title = song_template, args = tcp} else return " ERROR: Song data does not contain a song with id \"" .. song_key .. "\" " .. public.listSongs end else return " ERROR: No song id provided " .. public.listSongs end end

-- Returns the number of normal songs in the game, taken from the Song Data JSON function public.SongCount local song_count = 0 for k, v in pairs(song_data) do       if private.getValue(v, "display", true) then song_count = song_count + 1 end end return song_count end

-- Returns all the songs in a pack. Songs with no pack parameter are treated as being in the "free" pack function public.PackList(frame) if private.hasKey(frame.args, 1) then local pack = private.trimWhitespace(frame.args[1]) local songs = {} local parallel = false for k, v in pairs(song_data) do			if private.getValue(v, "pack", "free") == pack then local song = v				song["key"] = k				table.insert(songs, song) if private.hasKey(song, "parallel") then parallel = true end end end local retval = mw.html.create("table") retval :css("width", "100%") :addClass("wikitable") if parallel then retval :tag("tr") :tag("th") :wikitext("Song") :css("width", "25%") :done :tag("th") :wikitext("Artist") :css("width", "25%") :done :tag("th") :wikitext("Basic") :css("width", "10%") :done :tag("th") :wikitext("Normal") :css("width", "10%") :done :tag("th") :wikitext("Advanced") :css("width", "10%") :done :tag("th") :wikitext("Abyss") :css("width", "10%") :done :tag("th") :wikitext("Parallel") :css("width", "10%") :done :done for _, song in ipairs(songs) do				retval :tag("tr") :tag("td") :wikitext("" .. private.getValue(song, "title", song["key"]) .. "") :done :tag("td") :wikitext(private.getArtist(private.getValue(song, "artist", ""), false)) :done :tag("td") :wikitext(private.getValue(private.getValue(song, "basic", {}), "level", "")) :done :tag("td") :wikitext(private.getValue(private.getValue(song, "normal", {}), "level", "")) :done :tag("td") :wikitext(private.getValue(private.getValue(song, "advanced", {}), "level", "")) :done :tag("td") :wikitext(private.getValue(private.getValue(song, "abyss", {}), "level", "")) :done :tag("td") :wikitext(private.getValue(song, "parallel", {["level"] = "-"})["level"]) :done :done end else retval :tag("tr") :tag("th") :wikitext("Song") :css("width", "30%") :done :tag("th") :wikitext("Artist") :css("width", "30%") :done :tag("th") :wikitext("Basic") :css("width", "10%") :done :tag("th") :wikitext("Normal") :css("width", "10%") :done :tag("th") :wikitext("Advanced") :css("width", "10%") :done :tag("th") :wikitext("Abyss") :css("width", "10%") :done :done for _, song in ipairs(songs) do				retval :tag("tr") :tag("td") :wikitext("" .. private.getValue(song, "title", song["key"]) .. "") :done :tag("td") :wikitext(private.getArtist(private.getValue(song, "artist", ""), false)) :done :tag("td") :wikitext(private.getValue(private.getValue(song, "basic", {}), "level", "")) :done :tag("td") :wikitext(private.getValue(private.getValue(song, "normal", {}), "level", "")) :done :tag("td") :wikitext(private.getValue(private.getValue(song, "advanced", {}), "level", "")) :done :tag("td") :wikitext(private.getValue(private.getValue(song, "abyss", {}), "level", "")) :done :done end end retval:allDone return tostring(retval) else return "No pack provided" end end

-- Returns all the songs an artist worked on function public.ArtistSongs(frame) local args = frame:getParent.args if private.hasKey(args, 1) then local artist = private.trimWhitespace(args[1]) local retval = mw.html.create("table") retval :addClass("article-table") :css("width", "100%") :tag("tr") :tag("th") :wikitext("Song") :css("width", "40%") :done :tag("th") :wikitext("Duration") :css("width", "10%") :done :tag("th") :wikitext("Notes") :css("width", "50%") :done :done for k, song in pairs(song_data) do			if private.songHasArtist(song, artist) then retval :tag("tr") :tag("td") :wikitext(private.getValue(song, "title", k)) :done :tag("td") :wikitext(private.getValue(song, "length", "?:??")) :done :tag("td") :wikitext(private.getValue(args, k, "")) :done :done end end retval:allDone return tostring(retval) else return "No artist provided" end end

-- Lists all the artists that aren't mentioned in Artists function public.UndocumentedArtists local sanitised_characters = {"%", "(", ")", ".", "+", "-", "*", "?", "[", "^", "$"} local titleobject = mw.title.new("Artists") local artists_page = titleobject:getContent local undocumented_artists = {} -- Get all artists for k, v in pairs(song_data) do       if private.hasKey(v, "artist") then local artist = v["artist"] if type(artist) == "string" then undocumented_artists[artist] = k           elseif type(artist) == "table" then for i, v2 in ipairs(artist) do                   undocumented_artists[v2] = k                end end end end -- Remove mentioned artists for artist in pairs(undocumented_artists) do       local artist_sanitised = artist for _, chr in ipairs(sanitised_characters) do           artist_sanitised = artist_sanitised:gsub("%" .. chr, "%%" .. chr) end if artist_sanitised:sub(-1) == "%" then return artist_sanitised end if string.find(artists_page, "{{%s*ArtistSongs%s*|%s*" .. artist_sanitised .."%s*[|}]") ~= nil then undocumented_artists[artist] = nil end end -- Return result local retval = mw.html.create("span") for k, v in pairs(undocumented_artists) do       retval :wikitext("\"" .. k .. "\" - " .. v .. "") :tag("br") :done end retval:allDone return tostring(retval) end

return public