diff --git a/main.go b/main.go index 92087b9..fa00147 100644 --- a/main.go +++ b/main.go @@ -283,6 +283,8 @@ func generateEngine() *gin.Engine { r.GET("/c/:cid", clanPage) r.GET("/u/:user", userProfile) + r.GET("/rx/u/:user", relaxProfile) + r.GET("/ap/u/:user", autoProfile) r.GET("/b/:bid", beatmapInfo) r.POST("/pwreset", passwordReset) diff --git a/profile.go b/profile.go index 0b2c86e..de309ca 100644 --- a/profile.go +++ b/profile.go @@ -73,3 +73,122 @@ func userProfile(c *gin.Context) { data.DisableHH = true data.Scripts = append(data.Scripts, "/static/profile.js") } +func relaxProfile(c *gin.Context) { + var ( + userID int + username string + privileges uint64 + ) + + ctx := getContext(c) + + u := c.Param("user") + if _, err := strconv.Atoi(u); err != nil { + err := db.QueryRow("SELECT id, username, privileges FROM users WHERE username = ? AND "+ctx.OnlyUserPublic()+" LIMIT 1", u).Scan(&userID, &username, &privileges) + if err != nil && err != sql.ErrNoRows { + c.Error(err) + } + } else { + err := db.QueryRow(`SELECT id, username, privileges FROM users WHERE id = ? AND `+ctx.OnlyUserPublic()+` LIMIT 1`, u).Scan(&userID, &username, &privileges) + switch { + case err == nil: + case err == sql.ErrNoRows: + err := db.QueryRow(`SELECT id, username, privileges FROM users WHERE username = ? AND `+ctx.OnlyUserPublic()+` LIMIT 1`, u).Scan(&userID, &username, &privileges) + if err != nil && err != sql.ErrNoRows { + c.Error(err) + } + default: + c.Error(err) + } + } + + data := new(profileData) + data.UserID = userID + + defer resp(c, 200, "profile_relax.html", data) + + if data.UserID == 0 { + data.TitleBar = "User not found" + data.Messages = append(data.Messages, warningMessage{T(c, "That user could not be found.")}) + return + } + + if common.UserPrivileges(privileges)&common.UserPrivilegeDonor > 0 { + var profileBackground struct { + Type int + Value string + } + db.Get(&profileBackground, "SELECT type, value FROM profile_backgrounds WHERE uid = ?", data.UserID) + switch profileBackground.Type { + case 1: + data.KyutGrill = "/static/profbackgrounds/" + profileBackground.Value + data.KyutGrillAbsolute = true + case 2: + data.SolidColour = profileBackground.Value + } + } + + data.TitleBar = T(c, "%s's profile", username) + data.DisableHH = true + data.Scripts = append(data.Scripts, "/static/profile_relax.js") +} + +func autoProfile(c *gin.Context) { + var ( + userID int + username string + privileges uint64 + ) + + ctx := getContext(c) + + u := c.Param("user") + if _, err := strconv.Atoi(u); err != nil { + err := db.QueryRow("SELECT id, username, privileges FROM users WHERE username = ? AND "+ctx.OnlyUserPublic()+" LIMIT 1", u).Scan(&userID, &username, &privileges) + if err != nil && err != sql.ErrNoRows { + c.Error(err) + } + } else { + err := db.QueryRow(`SELECT id, username, privileges FROM users WHERE id = ? AND `+ctx.OnlyUserPublic()+` LIMIT 1`, u).Scan(&userID, &username, &privileges) + switch { + case err == nil: + case err == sql.ErrNoRows: + err := db.QueryRow(`SELECT id, username, privileges FROM users WHERE username = ? AND `+ctx.OnlyUserPublic()+` LIMIT 1`, u).Scan(&userID, &username, &privileges) + if err != nil && err != sql.ErrNoRows { + c.Error(err) + } + default: + c.Error(err) + } + } + + data := new(profileData) + data.UserID = userID + + defer resp(c, 200, "profile_auto.html", data) + + if data.UserID == 0 { + data.TitleBar = "User not found" + data.Messages = append(data.Messages, warningMessage{T(c, "That user could not be found.")}) + return + } + + if common.UserPrivileges(privileges)&common.UserPrivilegeDonor > 0 { + var profileBackground struct { + Type int + Value string + } + db.Get(&profileBackground, "SELECT type, value FROM profile_backgrounds WHERE uid = ?", data.UserID) + switch profileBackground.Type { + case 1: + data.KyutGrill = "/static/profbackgrounds/" + profileBackground.Value + data.KyutGrillAbsolute = true + case 2: + data.SolidColour = profileBackground.Value + } + } + + data.TitleBar = T(c, "%s's profile", username) + data.DisableHH = true + data.Scripts = append(data.Scripts, "/static/profile_auto.js") +} diff --git a/static/profile_auto.js b/static/profile_auto.js new file mode 100644 index 0000000..94eb47a --- /dev/null +++ b/static/profile_auto.js @@ -0,0 +1,442 @@ +// code that is executed on every user profile +$(document).ready(function() { + var wl = window.location; + var newPathName = wl.pathname; + // userID is defined in profile.html + if (newPathName.split("/")[2] != userID) { + newPathName = "/u/" + userID; + } + // if there's no mode parameter in the querystring, add it + if (wl.search.indexOf("mode=") === -1) + window.history.replaceState('', document.title, newPathName + "?mode=" + favouriteMode + wl.hash); + else if (wl.pathname != newPathName) + window.history.replaceState('', document.title, newPathName + wl.search + wl.hash); + setDefaultScoreTable(); + // when an item in the mode menu is clicked, it means we should change the mode. + $("#mode-menu>.item").click(function(e) { + e.preventDefault(); + if ($(this).hasClass("active")) + return; + var m = $(this).data("mode"); + $("[data-mode]:not(.item):not([hidden])").attr("hidden", ""); + $("[data-mode=" + m + "]:not(.item)").removeAttr("hidden"); + $("#mode-menu>.active.item").removeClass("active"); + var needsLoad = $("#scores-zone>[data-mode=" + m + "][data-loaded=0]"); + if (needsLoad.length > 0) + initialiseScores(needsLoad, m); + $(this).addClass("active"); + window.history.replaceState('', document.title, wl.pathname + "?mode=" + m + wl.hash); + }); + initialiseAchievements(); + initialiseFriends(); + // load scores page for the current favourite mode + var i = function(){initialiseScores($("#scores-zone>div[data-mode=" + favouriteMode + "]"), favouriteMode)}; + if (i18nLoaded) + i(); + else + i18next.on("loaded", function() { + i(); + }); +}); + +function initialiseAchievements() { + api('users/achievements' + (currentUserID == userID ? '?all' : ''), + {id: userID}, function (resp) { + var achievements = resp.achievements; + // no achievements -- show default message + if (achievements.length === 0) { + $("#achievements") + .append($("
") + .text(T("Nothing here. Yet."))); + $("#load-more-achievements").remove(); + return; + } + + var displayAchievements = function(limit, achievedOnly) { + var $ach = $("#achievements").empty(); + limit = limit < 0 ? achievements.length : limit; + var shown = 0; + for (var i = 0; i < achievements.length; i++) { + var ach = achievements[i]; + if (shown >= limit || (achievedOnly && !ach.achieved)) { + continue; + } + shown++; + $ach.append( + $("
").append( + $("" + ach.name +
+							"").popup({ + title: ach.name, + content: ach.description, + position: "bottom center", + distanceAway: 10 + }) + ) + ); + } + // if we've shown nothing, and achievedOnly is enabled, try again + // this time disabling it. + if (shown == 0 && achievedOnly) { + displayAchievements(limit, false); + } + }; + + // only 8 achievements - we can remove the button completely, because + // it won't be used (no more achievements). + // otherwise, we simply remove the disabled class and add the click handler + // to activate it. + if (achievements.length <= 8) { + $("#load-more-achievements").remove(); + } else { + $("#load-more-achievements") + .removeClass("disabled") + .click(function() { + $(this).remove(); + displayAchievements(-1, false); + }); + } + displayAchievements(8, true); + }); +} + +function initialiseFriends() { + var b = $("#add-friend-button"); + if (b.length == 0) return; + api('friends/with', {id: userID}, setFriendOnResponse); + b.click(friendClick); +} +function setFriendOnResponse(r) { + var x = 0; + if (r.friend) x++; + if (r.mutual) x++; + setFriend(x); +} +function setFriend(i) { + var b = $("#add-friend-button"); + b.removeClass("loading green blue red"); + switch (i) { + case 0: + b + .addClass("blue") + .attr("title", T("Add friend")) + .html(""); + break; + case 1: + b + .addClass("green") + .attr("title", T("Remove friend")) + .html(""); + break; + case 2: + b + .addClass("red") + .attr("title", T("Unmutual friend")) + .html(""); + break; + } + b.attr("data-friends", i > 0 ? 1 : 0) +} +function friendClick() { + var t = $(this); + if (t.hasClass("loading")) return; + t.addClass("loading"); + api("friends/" + (t.attr("data-friends") == 1 ? "del" : "add"), {user: userID}, setFriendOnResponse, true); +} + +var defaultScoreTable; +function setDefaultScoreTable() { + defaultScoreTable = $("") + .append( + $("").append( + $("").append( + $(""), + $("") + ) + ) + ) + .append( + $("") + ) + .append( + $("").append( + $("").append( + $("").append( + $( + "" + ), + $("") + )); + }); + $(".new.timeago").timeago().removeClass("new"); + $(".new.score-row").click(viewScoreInfo).removeClass("new"); + $(".new.downloadstar").click(function(e) { + e.stopPropagation(); + }).removeClass("new"); + var enable = true; + if (r.scores.length != 20) + enable = false; + disableLoadMoreButton(type, mode, enable); + }); +} +function downloadStar(id) { + return "" + T("Download") + ""; +} +function weightedPP(type, page, idx, pp) { + if (type != "best" || pp == 0) + return ""; + var perc = Math.pow(0.95, ((page - 1) * 20) + idx); + var wpp = pp * perc; + return "(" + wpp.toFixed(2) + "pp)"; +} +function disableLoadMoreButton(type, mode, enable) { + var button = $("#scores-zone div[data-mode=" + mode + "] table[data-type=" + type + "] .load-more-button"); + if (enable) button.removeClass("disabled"); + else button.addClass("disabled"); +} +function viewScoreInfo() { + var scoreid = $(this).data("scoreid"); + if (!scoreid && scoreid !== 0) return; + var s = scoreStore[scoreid]; + if (s === undefined) return; + + // data to be displayed in the table. + var data = { + "Points": addCommas(s.score), + "PP": addCommas(s.pp), + "Beatmap": "" + escapeHTML(s.beatmap.song_name) + "", + "Accuracy": s.accuracy + "%", + "Max combo": addCommas(s.max_combo) + "/" + addCommas(s.beatmap.max_combo) + + (s.full_combo ? " " + T("(full combo)") : ""), + "Difficulty": T("{{ stars }} star", { + stars: s.beatmap.difficulty2[modesShort[s.play_mode]], + count: Math.round(s.beatmap.difficulty2[modesShort[s.play_mode]]), + }), + "Mods": getScoreMods(s.mods, true), + }; + + // hits data + var hd = {}; + var trans = modeTranslations[s.play_mode]; + [ + s.count_300, + s.count_100, + s.count_50, + s.count_geki, + s.count_katu, + s.count_miss, + ].forEach(function(val, i) { + hd[trans[i]] = val; + }); + + data = $.extend(data, hd, { + "Ranked?": T(s.completed == 3 ? "Yes" : "No"), + "Achieved": s.time, + "Mode": modes[s.play_mode], + }); + + var els = []; + $.each(data, function(key, value) { + els.push( + $("").append( + $(""), + $("") + ) + ); + }); + + $("#score-data-table tr").remove(); + $("#score-data-table").append(els); + $(".ui.modal").modal("show"); +} + +var modeTranslations = [ + [ + "300s", + "100s", + "50s", + "Gekis", + "Katus", + "Misses" + ], + [ + "GREATs", + "GOODs", + "50s", + "GREATs (Gekis)", + "GOODs (Katus)", + "Misses" + ], + [ + "Fruits (300s)", + "Ticks (100s)", + "Droplets", + "Gekis", + "Droplet misses", + "Misses" + ], + [ + "300s", + "200s", + "50s", + "Max 300s", + "100s", + "Misses" + ] +]; + +function getRank(gameMode, mods, acc, c300, c100, c50, cmiss) { + var total = c300+c100+c50+cmiss; + + // Hidden | Flashlight | FadeIn + var hdfl = (mods & (1049608)) > 0; + + var ss = hdfl ? "SSHD" : "SS"; + var s = hdfl ? "SHD" : "S"; + + switch(gameMode) { + case 0: + case 1: + var ratio300 = c300 / total; + var ratio50 = c50 / total; + + if (ratio300 == 1) + return ss; + + if (ratio300 > 0.9 && ratio50 <= 0.01 && cmiss == 0) + return s; + + if ((ratio300 > 0.8 && cmiss == 0) || (ratio300 > 0.9)) + return "A"; + + if ((ratio300 > 0.7 && cmiss == 0) || (ratio300 > 0.8)) + return "B"; + + if (ratio300 > 0.6) + return "C"; + + return "D"; + + case 2: + if (acc == 100) + return ss; + + if (acc > 98) + return s; + + if (acc > 94) + return "A"; + + if (acc > 90) + return "B"; + + if (acc > 85) + return "C"; + + return "D"; + + case 3: + if (acc == 100) + return ss; + + if (acc > 95) + return s; + + if (acc > 90) + return "A"; + + if (acc > 80) + return "B"; + + if (acc > 70) + return "C"; + + return "D"; + } +} + +function ppOrScore(pp, score) { + if (pp != 0) + return addCommas(pp.toFixed(2)) + "pp"; + return addCommas(score); +} + +function beatmapLink(type, id) { + if (type == "s") + return "" + id + ''; + return "" + id + ''; +} diff --git a/static/profile_relax.js b/static/profile_relax.js new file mode 100644 index 0000000..6f8ca2c --- /dev/null +++ b/static/profile_relax.js @@ -0,0 +1,442 @@ +// code that is executed on every user profile +$(document).ready(function() { + var wl = window.location; + var newPathName = wl.pathname; + // userID is defined in profile.html + if (newPathName.split("/")[2] != userID) { + newPathName = "/u/" + userID; + } + // if there's no mode parameter in the querystring, add it + if (wl.search.indexOf("mode=") === -1) + window.history.replaceState('', document.title, newPathName + "?mode=" + favouriteMode + wl.hash); + else if (wl.pathname != newPathName) + window.history.replaceState('', document.title, newPathName + wl.search + wl.hash); + setDefaultScoreTable(); + // when an item in the mode menu is clicked, it means we should change the mode. + $("#mode-menu>.item").click(function(e) { + e.preventDefault(); + if ($(this).hasClass("active")) + return; + var m = $(this).data("mode"); + $("[data-mode]:not(.item):not([hidden])").attr("hidden", ""); + $("[data-mode=" + m + "]:not(.item)").removeAttr("hidden"); + $("#mode-menu>.active.item").removeClass("active"); + var needsLoad = $("#scores-zone>[data-mode=" + m + "][data-loaded=0]"); + if (needsLoad.length > 0) + initialiseScores(needsLoad, m); + $(this).addClass("active"); + window.history.replaceState('', document.title, wl.pathname + "?mode=" + m + wl.hash); + }); + initialiseAchievements(); + initialiseFriends(); + // load scores page for the current favourite mode + var i = function(){initialiseScores($("#scores-zone>div[data-mode=" + favouriteMode + "]"), favouriteMode)}; + if (i18nLoaded) + i(); + else + i18next.on("loaded", function() { + i(); + }); +}); + +function initialiseAchievements() { + api('users/achievements' + (currentUserID == userID ? '?all' : ''), + {id: userID}, function (resp) { + var achievements = resp.achievements; + // no achievements -- show default message + if (achievements.length === 0) { + $("#achievements") + .append($("
") + .text(T("Nothing here. Yet."))); + $("#load-more-achievements").remove(); + return; + } + + var displayAchievements = function(limit, achievedOnly) { + var $ach = $("#achievements").empty(); + limit = limit < 0 ? achievements.length : limit; + var shown = 0; + for (var i = 0; i < achievements.length; i++) { + var ach = achievements[i]; + if (shown >= limit || (achievedOnly && !ach.achieved)) { + continue; + } + shown++; + $ach.append( + $("
").append( + $("" + ach.name +
+							"").popup({ + title: ach.name, + content: ach.description, + position: "bottom center", + distanceAway: 10 + }) + ) + ); + } + // if we've shown nothing, and achievedOnly is enabled, try again + // this time disabling it. + if (shown == 0 && achievedOnly) { + displayAchievements(limit, false); + } + }; + + // only 8 achievements - we can remove the button completely, because + // it won't be used (no more achievements). + // otherwise, we simply remove the disabled class and add the click handler + // to activate it. + if (achievements.length <= 8) { + $("#load-more-achievements").remove(); + } else { + $("#load-more-achievements") + .removeClass("disabled") + .click(function() { + $(this).remove(); + displayAchievements(-1, false); + }); + } + displayAchievements(8, true); + }); +} + +function initialiseFriends() { + var b = $("#add-friend-button"); + if (b.length == 0) return; + api('friends/with', {id: userID}, setFriendOnResponse); + b.click(friendClick); +} +function setFriendOnResponse(r) { + var x = 0; + if (r.friend) x++; + if (r.mutual) x++; + setFriend(x); +} +function setFriend(i) { + var b = $("#add-friend-button"); + b.removeClass("loading green blue red"); + switch (i) { + case 0: + b + .addClass("blue") + .attr("title", T("Add friend")) + .html(""); + break; + case 1: + b + .addClass("green") + .attr("title", T("Remove friend")) + .html(""); + break; + case 2: + b + .addClass("red") + .attr("title", T("Unmutual friend")) + .html(""); + break; + } + b.attr("data-friends", i > 0 ? 1 : 0) +} +function friendClick() { + var t = $(this); + if (t.hasClass("loading")) return; + t.addClass("loading"); + api("friends/" + (t.attr("data-friends") == 1 ? "del" : "add"), {user: userID}, setFriendOnResponse, true); +} + +var defaultScoreTable; +function setDefaultScoreTable() { + defaultScoreTable = $("
" + T("General info") + ""+ T("Score") + "
").append( + $("
" + scoreRank + " " + + escapeHTML(v.beatmap.song_name) + " " + getScoreMods(v.mods) + " (" + v.accuracy.toFixed(2) + "%)
" + + "
" + ppOrScore(v.pp, v.score) + " " + weightedPP(type, page, idx, v.pp) + (v.completed == 3 ? "
" + downloadStar(v.id) : "") + "
" + T(key) + "" + value + "
") + .append( + $("").append( + $("").append( + $(""), + $("") + ) + ) + ) + .append( + $("") + ) + .append( + $("").append( + $("").append( + $("").append( + $( + "" + ), + $("") + )); + }); + $(".new.timeago").timeago().removeClass("new"); + $(".new.score-row").click(viewScoreInfo).removeClass("new"); + $(".new.downloadstar").click(function(e) { + e.stopPropagation(); + }).removeClass("new"); + var enable = true; + if (r.scores.length != 20) + enable = false; + disableLoadMoreButton(type, mode, enable); + }); +} +function downloadStar(id) { + return "" + T("Download") + ""; +} +function weightedPP(type, page, idx, pp) { + if (type != "best" || pp == 0) + return ""; + var perc = Math.pow(0.95, ((page - 1) * 20) + idx); + var wpp = pp * perc; + return "(" + wpp.toFixed(2) + "pp)"; +} +function disableLoadMoreButton(type, mode, enable) { + var button = $("#scores-zone div[data-mode=" + mode + "] table[data-type=" + type + "] .load-more-button"); + if (enable) button.removeClass("disabled"); + else button.addClass("disabled"); +} +function viewScoreInfo() { + var scoreid = $(this).data("scoreid"); + if (!scoreid && scoreid !== 0) return; + var s = scoreStore[scoreid]; + if (s === undefined) return; + + // data to be displayed in the table. + var data = { + "Points": addCommas(s.score), + "PP": addCommas(s.pp), + "Beatmap": "" + escapeHTML(s.beatmap.song_name) + "", + "Accuracy": s.accuracy + "%", + "Max combo": addCommas(s.max_combo) + "/" + addCommas(s.beatmap.max_combo) + + (s.full_combo ? " " + T("(full combo)") : ""), + "Difficulty": T("{{ stars }} star", { + stars: s.beatmap.difficulty2[modesShort[s.play_mode]], + count: Math.round(s.beatmap.difficulty2[modesShort[s.play_mode]]), + }), + "Mods": getScoreMods(s.mods, true), + }; + + // hits data + var hd = {}; + var trans = modeTranslations[s.play_mode]; + [ + s.count_300, + s.count_100, + s.count_50, + s.count_geki, + s.count_katu, + s.count_miss, + ].forEach(function(val, i) { + hd[trans[i]] = val; + }); + + data = $.extend(data, hd, { + "Ranked?": T(s.completed == 3 ? "Yes" : "No"), + "Achieved": s.time, + "Mode": modes[s.play_mode], + }); + + var els = []; + $.each(data, function(key, value) { + els.push( + $("").append( + $(""), + $("") + ) + ); + }); + + $("#score-data-table tr").remove(); + $("#score-data-table").append(els); + $(".ui.modal").modal("show"); +} + +var modeTranslations = [ + [ + "300s", + "100s", + "50s", + "Gekis", + "Katus", + "Misses" + ], + [ + "GREATs", + "GOODs", + "50s", + "GREATs (Gekis)", + "GOODs (Katus)", + "Misses" + ], + [ + "Fruits (300s)", + "Ticks (100s)", + "Droplets", + "Gekis", + "Droplet misses", + "Misses" + ], + [ + "300s", + "200s", + "50s", + "Max 300s", + "100s", + "Misses" + ] +]; + +function getRank(gameMode, mods, acc, c300, c100, c50, cmiss) { + var total = c300+c100+c50+cmiss; + + // Hidden | Flashlight | FadeIn + var hdfl = (mods & (1049608)) > 0; + + var ss = hdfl ? "SSHD" : "SS"; + var s = hdfl ? "SHD" : "S"; + + switch(gameMode) { + case 0: + case 1: + var ratio300 = c300 / total; + var ratio50 = c50 / total; + + if (ratio300 == 1) + return ss; + + if (ratio300 > 0.9 && ratio50 <= 0.01 && cmiss == 0) + return s; + + if ((ratio300 > 0.8 && cmiss == 0) || (ratio300 > 0.9)) + return "A"; + + if ((ratio300 > 0.7 && cmiss == 0) || (ratio300 > 0.8)) + return "B"; + + if (ratio300 > 0.6) + return "C"; + + return "D"; + + case 2: + if (acc == 100) + return ss; + + if (acc > 98) + return s; + + if (acc > 94) + return "A"; + + if (acc > 90) + return "B"; + + if (acc > 85) + return "C"; + + return "D"; + + case 3: + if (acc == 100) + return ss; + + if (acc > 95) + return s; + + if (acc > 90) + return "A"; + + if (acc > 80) + return "B"; + + if (acc > 70) + return "C"; + + return "D"; + } +} + +function ppOrScore(pp, score) { + if (pp != 0) + return addCommas(pp.toFixed(2)) + "pp"; + return addCommas(score); +} + +function beatmapLink(type, id) { + if (type == "s") + return "" + id + ''; + return "" + id + ''; +} diff --git a/templates/clanmembers.html b/templates/clanmembers.html index 215ce3d..bcfa3d0 100644 --- a/templates/clanmembers.html +++ b/templates/clanmembers.html @@ -1,7 +1,7 @@ {{/*### NoCompile=true -*/}} {{ define "clanMembers" }} +*/}} { with . }}
{{ $teamJSON := teamJSON }} diff --git a/templates/profile_auto.html b/templates/profile_auto.html new file mode 100644 index 0000000..a2fb14e --- /dev/null +++ b/templates/profile_auto.html @@ -0,0 +1,264 @@ +{{ define "tpl" }} +
+ {{ if .UserID }} + {{ $gqm := atoi (.Gin.Query "mode") }} + {{ $global := . }} + {{ with (.Get "users/full?id=%d" .UserID) }} + {{ $favouritemode := _or $gqm .favourite_mode }} + + {{ if after .silence_info.end }} +
{{ $global.T "User is silenced for %s, expires %s." (.silence_info.reason | htmlescaper) (time .silence_info.end) | html }}
+ {{ end }} + + {{ $sarah := has .id 1193 }} + {{ $alicia := has .id 1000 }} + {{ $catherine := has .id 999 }} + + {{ $super := has .privileges 7340031 }} + {{ $dev := has .privileges 11534335 }} + {{ $donor := has .privileges 7 }} + {{ $admin := has .privileges 3049983 }} + {{ $chatmod := has .privileges 786763 }} + {{ $bn := has .privileges 267 }} + + {{ if hasAdmin $global.Context.User.Privileges }} + {{ $restr := not (has .privileges 1) }} + {{ $disab := not (has .privileges 2) }} + {{ $pend := has .privileges 1048576 }} + {{ if and $disab $restr }} + {{ if $pend }} +
{{ $global.T "User is %s" "pending verification" | html }}.
+ {{ else }} +
{{ $global.T "User is %s" "banned" | html }}.
+ {{ end }} + {{ else if $restr }} +
{{ $global.T "User is %s" "restricted" | html }}.
+ {{ else if $disab }} +
{{ $global.T "User is %s" "locked" | html }}.
+ {{ end }} + {{ end }} + {{ with $global.Get "users/userpage?id=%.0f" .id }} + {{ if .userpage }} + {{ with parseUserpage .userpage }} +
+ {{ html . }} +
+ {{ end }} + {{ end }} + {{ end }} +
+
+
+ {{ if eq $global.UserID $global.Context.User.ID }} + + {{ end }} + avatar + {{ if eq $global.UserID $global.Context.User.ID }} + + {{ end }} +
+
+

+ {{ if $super }} +
+ {{ .username }} +
+ {{else if $donor}} +
+ {{ .username }} +
+ {{ else }} + {{ .username }} + {{ end }} +

+ {{ if .username_aka }} +
+ {{ $global.T "(aka %s)" (.username_aka | htmlescaper) | html }} +
+ {{ end }} + {{ with bget "isOnline?id=%.0f" .id }} +
+ + {{ if .result }}{{ $global.T "Online" }}{{ else }}{{ $global.T "Offline" }}{{ end }} +
+ {{ end }} +
+
+
+
+ {{ range $k, $v := (slice .std .taiko .ctb .mania) }} +

{{ with and $v $v.global_leaderboard_rank }}#{{ . }}{{ else }}{{ $global.T "Unknown" }}{{ end }}

+ {{ end }} +
+ {{ if and (ne $global.Context.User.ID $global.UserID) (ne $global.Context.User.ID 0) }} + + {{ end }} + {{ if eq $global.Context.User.ID $global.UserID }} + + + + {{ end }} + {{ if hasAdmin $global.Context.User.Privileges }} + + + + {{ end }} +
+
+
+
+ {{ $user := . }} + +
+
+
+
+ {{if $super }} + {{ $global.T "%s " .username | html }} + is an Owner + {{ $global.T " from %s." (country .country true) | html }} + {{else if $dev}} + {{ $global.T "%s " .username | html }} + is a Developer + {{ $global.T " from %s." (country .country true) | html }} + {{else if $admin}} + {{ $global.T "%s " .username | html }} + is an Administrator + {{ $global.T " from %s." (country .country true) | html }} + {{else if $chatmod}} + {{ $global.T "%s " .username | html }} + is a Chat Mod + {{ $global.T " from %s." (country .country true) | html }} + {{else if $bn}} + {{ $global.T "%s " .username | html }} + is a Nominator + {{ $global.T " from %s." (country .country true) | html }} + {{else if $donor }} + {{ $global.T "%s " .username | html }} + is a Supporter + {{ $global.T " from %s." (country .country true) | html }} + {{ else }} + {{ $global.T "%s is a player from %s." .username (country .country true) | html }} + {{ end }} + +
{{ $global.T "They signed up on Yozora %s." (time $user.registered_on) | html }} +
{{ $global.T "Last seen: %s." (time $user.latest_activity) | html }} +
{{ with playstyle .play_style $global }}{{ $global.T "They play with %s." . }}{{ end }} +
+
+ {{ if and (not .badges) (not .custom_badge) }} + {{ $global.T "This user hasn't got any badges!" }} + {{ else }} +
+ {{ range .badges }} +
+
+ {{ .name }} +
+ {{ end }} + {{ with .custom_badge }} +
+
+ {{ .name }} +
+ {{ end }} +
+ {{ end }} +
+
+ {{ range $k, $v := (slice .std .taiko .ctb .mania) }} +
+
" + T("General info") + ""+ T("Score") + "
").append( + $("
" + scoreRank + " " + + escapeHTML(v.beatmap.song_name) + " " + getScoreMods(v.mods) + " (" + v.accuracy.toFixed(2) + "%)
" + + "
" + ppOrScore(v.pp, v.score) + " " + weightedPP(type, page, idx, v.pp) + (v.completed == 3 ? "
" + downloadStar(v.id) : "") + "
" + T(key) + "" + value + "
+ + {{ with .global_leaderboard_rank }} + + + + + {{ end }} + {{ with .country_leaderboard_rank }} + + + + + {{ end }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ $global.T "Global rank" }}#{{ . }}
{{ $global.T "Country rank" }}  {{ country $user.country false }}#{{ . }}
{{ $global.T "PP" }}{{ humanize .pp }}
{{ $global.T "Ranked score" }}{{ humanize .ranked_score }}
{{ $global.T "Total score" }}{{ humanize .total_score }}
{{ $global.T "Playcount" }}{{ humanize .playcount }}
{{ $global.T "Replays watched" }}{{ humanize .replays_watched }}
{{ $global.T "Total hits" }}{{ humanize .total_hits }}
{{ $global.T "Accuracy" }}{{ printf "%.2f" .accuracy }}%
+
+
+
{{ levelPercent .level }}%
+
+
{{ $global.T "Level %s" (level .level) }}
+
+
+ {{ end }} +
+ + + +
+ {{ range _range 4 }} +
+
+ {{ end }} +
+ +
+

{{ $global.T "Achievements" }}

+
+
+
+ +
+
+ + {{ end }} + {{ end }} + +{{ end }} diff --git a/templates/profile_relax.html b/templates/profile_relax.html new file mode 100644 index 0000000..a2fb14e --- /dev/null +++ b/templates/profile_relax.html @@ -0,0 +1,264 @@ +{{ define "tpl" }} +
+ {{ if .UserID }} + {{ $gqm := atoi (.Gin.Query "mode") }} + {{ $global := . }} + {{ with (.Get "users/full?id=%d" .UserID) }} + {{ $favouritemode := _or $gqm .favourite_mode }} + + {{ if after .silence_info.end }} +
{{ $global.T "User is silenced for %s, expires %s." (.silence_info.reason | htmlescaper) (time .silence_info.end) | html }}
+ {{ end }} + + {{ $sarah := has .id 1193 }} + {{ $alicia := has .id 1000 }} + {{ $catherine := has .id 999 }} + + {{ $super := has .privileges 7340031 }} + {{ $dev := has .privileges 11534335 }} + {{ $donor := has .privileges 7 }} + {{ $admin := has .privileges 3049983 }} + {{ $chatmod := has .privileges 786763 }} + {{ $bn := has .privileges 267 }} + + {{ if hasAdmin $global.Context.User.Privileges }} + {{ $restr := not (has .privileges 1) }} + {{ $disab := not (has .privileges 2) }} + {{ $pend := has .privileges 1048576 }} + {{ if and $disab $restr }} + {{ if $pend }} +
{{ $global.T "User is %s" "pending verification" | html }}.
+ {{ else }} +
{{ $global.T "User is %s" "banned" | html }}.
+ {{ end }} + {{ else if $restr }} +
{{ $global.T "User is %s" "restricted" | html }}.
+ {{ else if $disab }} +
{{ $global.T "User is %s" "locked" | html }}.
+ {{ end }} + {{ end }} + {{ with $global.Get "users/userpage?id=%.0f" .id }} + {{ if .userpage }} + {{ with parseUserpage .userpage }} +
+ {{ html . }} +
+ {{ end }} + {{ end }} + {{ end }} +
+
+
+ {{ if eq $global.UserID $global.Context.User.ID }} + + {{ end }} + avatar + {{ if eq $global.UserID $global.Context.User.ID }} + + {{ end }} +
+
+

+ {{ if $super }} +
+ {{ .username }} +
+ {{else if $donor}} +
+ {{ .username }} +
+ {{ else }} + {{ .username }} + {{ end }} +

+ {{ if .username_aka }} +
+ {{ $global.T "(aka %s)" (.username_aka | htmlescaper) | html }} +
+ {{ end }} + {{ with bget "isOnline?id=%.0f" .id }} +
+ + {{ if .result }}{{ $global.T "Online" }}{{ else }}{{ $global.T "Offline" }}{{ end }} +
+ {{ end }} +
+
+
+
+ {{ range $k, $v := (slice .std .taiko .ctb .mania) }} +

{{ with and $v $v.global_leaderboard_rank }}#{{ . }}{{ else }}{{ $global.T "Unknown" }}{{ end }}

+ {{ end }} +
+ {{ if and (ne $global.Context.User.ID $global.UserID) (ne $global.Context.User.ID 0) }} + + {{ end }} + {{ if eq $global.Context.User.ID $global.UserID }} + + + + {{ end }} + {{ if hasAdmin $global.Context.User.Privileges }} + + + + {{ end }} +
+
+
+
+ {{ $user := . }} + +
+
+
+
+ {{if $super }} + {{ $global.T "%s " .username | html }} + is an Owner + {{ $global.T " from %s." (country .country true) | html }} + {{else if $dev}} + {{ $global.T "%s " .username | html }} + is a Developer + {{ $global.T " from %s." (country .country true) | html }} + {{else if $admin}} + {{ $global.T "%s " .username | html }} + is an Administrator + {{ $global.T " from %s." (country .country true) | html }} + {{else if $chatmod}} + {{ $global.T "%s " .username | html }} + is a Chat Mod + {{ $global.T " from %s." (country .country true) | html }} + {{else if $bn}} + {{ $global.T "%s " .username | html }} + is a Nominator + {{ $global.T " from %s." (country .country true) | html }} + {{else if $donor }} + {{ $global.T "%s " .username | html }} + is a Supporter + {{ $global.T " from %s." (country .country true) | html }} + {{ else }} + {{ $global.T "%s is a player from %s." .username (country .country true) | html }} + {{ end }} + +
{{ $global.T "They signed up on Yozora %s." (time $user.registered_on) | html }} +
{{ $global.T "Last seen: %s." (time $user.latest_activity) | html }} +
{{ with playstyle .play_style $global }}{{ $global.T "They play with %s." . }}{{ end }} +
+
+ {{ if and (not .badges) (not .custom_badge) }} + {{ $global.T "This user hasn't got any badges!" }} + {{ else }} +
+ {{ range .badges }} +
+
+ {{ .name }} +
+ {{ end }} + {{ with .custom_badge }} +
+
+ {{ .name }} +
+ {{ end }} +
+ {{ end }} +
+
+ {{ range $k, $v := (slice .std .taiko .ctb .mania) }} +
+ + + {{ with .global_leaderboard_rank }} + + + + + {{ end }} + {{ with .country_leaderboard_rank }} + + + + + {{ end }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ $global.T "Global rank" }}#{{ . }}
{{ $global.T "Country rank" }}  {{ country $user.country false }}#{{ . }}
{{ $global.T "PP" }}{{ humanize .pp }}
{{ $global.T "Ranked score" }}{{ humanize .ranked_score }}
{{ $global.T "Total score" }}{{ humanize .total_score }}
{{ $global.T "Playcount" }}{{ humanize .playcount }}
{{ $global.T "Replays watched" }}{{ humanize .replays_watched }}
{{ $global.T "Total hits" }}{{ humanize .total_hits }}
{{ $global.T "Accuracy" }}{{ printf "%.2f" .accuracy }}%
+
+
+
{{ levelPercent .level }}%
+
+
{{ $global.T "Level %s" (level .level) }}
+
+
+ {{ end }} +
+
+
+
+
+ {{ range _range 4 }} +
+
+ {{ end }} +
+ +
+

{{ $global.T "Achievements" }}

+
+
+
+ +
+
+ + {{ end }} + {{ end }} +
+{{ end }}