/*! * ripple.js * Copyright (C) 2016-2018 Morgan Bazalgette and Giuseppe Guerra * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ // this object contains tiny snippets that were deemed too small to be worth // their own file. var singlePageSnippets = { "/2fa_gateway" : function() { $('#telegram-code') .on('input', function() { if ($(this).val().length >= 6) { $.get("/2fa_gateway/verify", { token : $(this).val().trim().substr(0, 8), }, function(resp) { switch (resp) { case "0": $("#telegram-code").closest(".field").addClass("success"); redir = redir ? redir : "/"; window.location.href = redir; break; case "1": $("#telegram-code").closest(".field").addClass("error"); break; } }); } else { $("#telegram-code").closest(".field").removeClass("error"); } }); }, "/leaderboard" : function() { page = page === 0 ? 1 : page; function loadLeaderboard() { var wl = window.location; window.history.replaceState( '', document.title, wl.pathname + "?mode=" + favouriteMode + "&p=" + page + (country != "" ? "&country=" + encodeURI(country) : "") + wl.hash); api("leaderboard", { mode : favouriteMode, p : page, l : 50, country : country, }, function(data) { var tb = $(".ui.table tbody"); tb.find("tr").remove(); if (data.users == null) { disableSimplepagButtons(true); data.users = []; } var i = 0; data.users.forEach(function(v) { tb.append($("").append( $("").text("#" + ((page - 1) * 50 + (++i))), $("").html("" + escapeHTML(v.username) + ""), $("").html( scoreOrPP(v.chosen_mode.ranked_score, v.chosen_mode.pp)), $("").text(v.chosen_mode.accuracy.toFixed(2) + "%"), // bonus points if you get the undertale joke $("").html(addCommas(v.chosen_mode.playcount) + " (lv. " + v.chosen_mode.level.toFixed(0) + ")"))); }); disableSimplepagButtons(data.users.length < 50); }); } function scoreOrPP(s, pp) { if (pp === 0) return "" + addCommas(s) + ""; return "" + addCommas(pp) + "pp (" + addCommas(s) + ")" } // country stuff $("#country-chooser-modal") .click(function() { $(".ui.modal").modal("show"); }); $(".lb-country") .click(function() { country = $(this).data("country"); page = 1; $(".ui.modal").modal("hide"); loadLeaderboard(); }); loadLeaderboard(); setupSimplepag(loadLeaderboard); $("#mode-menu .item") .click(function(e) { e.preventDefault(); $("#mode-menu .active.item").removeClass("active"); $(this).addClass("active"); favouriteMode = $(this).data("mode"); country = ""; page = 1; loadLeaderboard(); }); }, "/friends" : function() { $(".smalltext.button") .click(function() { var t = $(this); var delAdd = t.data("deleted") === "1" ? "add" : "del"; console.log(delAdd); t.addClass("disabled"); api("friends/" + delAdd, {user : +t.data("userid")}, function(data) { t.removeClass("disabled"); t.data("deleted", data.friend ? "0" : "1"); t.removeClass("green red blue"); t.addClass(data.friend ? (data.mutual ? "red" : "green") : "blue"); t.find(".icon") .removeClass("minus plus heart") .addClass(data.friend ? (data.mutual ? "heart" : "minus") : "plus"); t.find("span").text(data.friend ? (data.mutual ? T("Mutual") : T("Remove")) : t("Add")); }, true); }); }, "/team" : function() { $("#everyone").click(function() { $(".ui.modal").modal("show"); }); }, "/register/verify" : function() { var qu = query("u"); setInterval(function() { $.getJSON(hanayoConf.banchoAPI + "/api/v1/verifiedStatus?u=" + qu, function(data) { if (data.result >= 0) { window.location.href = "/register/welcome?u=" + qu; } }) }, 5000) }, "/settings" : function() { $("input[name='custom_badge.icon']") .on("input", function() { $("#badge-icon") .attr("class", "circular big icon " + escapeHTML($(this).val())); }); $("input[name='custom_badge.name']") .on("input", function() { $("#badge-name").html(escapeHTML($(this).val())); }); $("input[name='custom_badge.show']") .change(function() { if ($(this).is(":checked")) $("#custom-badge-fields").slideDown(); else $("#custom-badge-fields").slideUp(); }); var isDark = $("#dark-site").is(":checked"); $("form") .submit(function(e) { e.preventDefault(); var darkSetting = $("#dark-site").is(":checked") if (darkSetting != isDark) { var cflags = document.cookie.replace(/(?:(?:^|.*;\s*)cflags\s*\=\s*([^;]*).*$)|^.*$/, "$1"); cflags = darkSetting ? +cflags | 1 : +cflags & ~1; document.cookie = "cflags=" + cflags + ";path=/;max-age=31536000"; } var obj = formToObject($(this)); var ps = 0; $(this) .find("input[data-sv]") .each(function(_, el) { el = $(el); if (el.is(":checked")) { ps |= el.data("sv"); } }); obj.play_style = ps; var f = $(this); api("users/self/settings", obj, function(data) { if (darkSetting != isDark) { window.location.reload(); return; } showMessage("success", "Your new settings have been saved."); f.removeClass("loading"); }, true); return false; }); }, "/settings/userpage" : function() { var lastTimeout = null; $("textarea[name='data']") .on('input', function() { if (lastTimeout !== null) { clearTimeout(lastTimeout); } var v = $(this).val(); lastTimeout = setTimeout(function() { $("#userpage-content").addClass("loading"); $.post( "/settings/userpage/parse", $("textarea[name='data']").val(), function(data) { var e = $("#userpage-content").removeClass("loading").html(data); if (typeof twemoji !== "undefined") { twemoji.parse(e[0]); } }, "text"); }, 800); }); $("form") .submit(function(e) { e.preventDefault(); var obj = formToObject($(this)); var f = $(this); api("users/self/userpage", obj, function(data) { showMessage("success", "Your userpage has been saved."); f.removeClass("loading"); }, true); return false; }); }, "/donate" : function() { var sl = $("#months-slider")[0]; noUiSlider.create(sl, { start : [ 1 ], step : 1, connect : [ true, false ], range : { min : [ 1 ], max : [ 24 ], } }); var rates = {}; var us = sl.noUiSlider; $.getJSON("/donate/rates", function(data) { rates = data; us.on('update', function() { var months = us.get(); var priceEUR = Math.pow(months * 30 * 0.2, 0.70); var priceBTC = priceEUR / rates.EUR; var priceUSD = priceBTC * rates.USD; $("#cost") .html(T("{{ months }} month costs € {{ eur }}", { count : Math.round(+months), months : (+months).toFixed(0), eur : priceEUR.toFixed(2), }) + "
" + T("($ {{ usd }} / BTC {{ btc }})", { usd : priceUSD.toFixed(2), btc : priceBTC.toFixed(10), })); $("input[name='os0']") .attr("value", (+months).toFixed(0) + " month" + (months == 1 ? "" : "s")); $("#bitcoin-amt").text(priceBTC.toFixed(6)); $("#paypal-amt").val(priceEUR.toFixed(2)); }); }); $("#username-input").on("input", function() { $("#ipn-username").attr("value", "username=" + $(this).val()); }); }, "/settings/avatar" : function() { $("#file") .change(function(e) { var f = e.target.files; if (f.length < 1) { return; } var u = window.URL.createObjectURL(f[0]); var i = $("#avatar-img")[0]; i.src = u; i.onload = function() { window.URL.revokeObjectURL(this.src); }; }); }, "/beatmaps/rank_request" : function() { function updateRankRequestPage(data) { $("#queue-info").html(data.submitted + "/" + data.queue_size); if (data.submitted_by_user == 0) $("#by-you").attr("hidden", "hidden"); else $("#by-you").removeAttr("hidden"); $("#submitted-by-user").text(data.submitted_by_user); $("#max-per-user").text(data.max_per_user); var perc = (data.submitted / data.queue_size * 100).toFixed(0); $("#progressbar .progress").text(perc + "%"); $("#progressbar") .progress({ percent : perc, }); if (data.can_submit) $("#b-form .input, #b-form .button").removeClass("disabled"); else $("#b-form .input, #b-form .button").addClass("disabled"); } setInterval(function() { api("beatmaps/rank_requests/status", {}, updateRankRequestPage); }, 10000); var re = /^https?:\/\/osu.ppy.sh\/(s|b)\/(\d+)$/gi; $("#b-form") .submit(function(e) { e.preventDefault(); var v = $("#beatmap").val().trim(); var reData = re.exec(v); re.exec(); // apparently this is always null, idk console.log(v, reData); if (reData === null) { showMessage( "error", "Please provide a valid link, in the form " + "of either https://osu.ppy.sh/s/<ID> or https://osu.ppy.sh/b/<ID>."); $(this).removeClass("loading"); return false; } var postData = {}; if (reData[1] == "s") postData.set_id = +reData[2]; else postData.id = +reData[2]; var t = $(this); api("beatmaps/rank_requests", postData, function(data) { t.removeClass("loading"); showMessage("success", "Beatmap rank request has been submitted."); updateRankRequestPage(data); }, function(data) { t.removeClass("loading"); if (data.code == 406) showMessage("warning", "That beatmap is already ranked!"); }, true); return false; }); }, "/settings/profbackground" : function() { $("#colorpicker") .minicolors({ inline : true, }); $("#background-type") .change(function() { $("[data-type]:not([hidden])").attr("hidden", "hidden"); $("[data-type=" + $(this).val() + "]").removeAttr("hidden"); }); $("#file") .change(function(e) { var f = e.target.files; if (f.length < 1) { return; } var u = window.URL.createObjectURL(f[0]); var i = document.createElement("img"); i.src = u; i.onload = function() { window.URL.revokeObjectURL(this.src); }; $("#image-background").empty().append(i); }); }, "/dev/tokens" : function() { $("#privileges-number") .on("input", function() { $("#privileges-text").text(privilegesToString($(this).val())); }); } }; $(document) .ready(function() { // semantic stuff $('.message .close').on('click', closeClosestMessage); $('.ui.checkbox').checkbox(); $('.ui.dropdown').dropdown(); $('.ui.progress').progress(); $('.ui.form') .submit(function(e) { var t = $(this); if (t.hasClass("loading") || t.hasClass("disabled")) { e.preventDefault(); return false; } t.addClass("loading"); var f = t.attr("id"); $("[form='" + f + "']").addClass("loading"); }); // emojis! if (typeof twemoji !== "undefined") { $(".twemoji").each(function(k, v) { twemoji.parse(v); }); } // ripple stuff var f = singlePageSnippets[window.location.pathname]; if (typeof f === 'function') f(); if (typeof deferredToPageLoad === "function") deferredToPageLoad(); // setup user search $("#user-search") .search({ onSelect : function(val) { window.location.href = val.url; return false; }, apiSettings : { url : "/api/v1/users/lookup?name={query}", onResponse : function(resp) { var r = { results : [], }; $.each(resp.users, function(index, item) { r.results.push({ title : item.username, url : "/u/" + item.id, image : hanayoConf.avatars + "/" + item.id, }); }); return r; }, }, }); $("#user-search-input") .keypress(function(e) { if (e.which == 13) { window.location.pathname = "/u/" + $(this).val(); } }); $(document) .keydown(function(e) { var activeElement = $(document.activeElement); var isInput = activeElement.is(":input,[contenteditable]"); if ((e.which === 83 || e.which === 115) && !isInput) { $("#user-search-input").focus(); e.preventDefault(); } if (e.which === 27 && isInput) { activeElement.blur(); } }); // setup timeago $.timeago.settings.allowFuture = true; $("time.timeago").timeago(); $("#language-selector .item") .click(function() { var lang = $(this).data("lang"); document.cookie = "language=" + lang + ";path=/;max-age=31536000"; window.location.reload(); }); }); function closeClosestMessage() { $(this).closest('.message').fadeOut(300, function() { $(this).remove(); }); }; function showMessage(type, message) { var newEl = $(''); newEl.find(".close.icon").click(closeClosestMessage); $("#messages-container").append(newEl); newEl.slideDown(300); }; // function for all api calls function api(endpoint, data, success, failure, post) { if (typeof data == "function") { success = data; data = null; } if (typeof failure == "boolean") { post = failure; failure = undefined; } var errorMessage = "An error occurred while contacting the Ripple API. Please report this to a Ripple developer."; $.ajax({ method : (post ? "POST" : "GET"), dataType : "json", url : hanayoConf.baseAPI + "/api/v1/" + endpoint, data : (post ? JSON.stringify(data) : data), contentType : (post ? "application/json; charset=utf-8" : ""), success : function(data) { if (data.code != 200) { if ((data.code >= 400 && data.code < 500) && typeof failure == "function") { failure(data); return; } console.warn(data); showMessage("error", errorMessage); } success(data); }, error : function(jqXHR, textStatus, errorThrown) { if ((jqXHR.status >= 400 && jqXHR.status < 500) && typeof failure == "function") { failure(jqXHR.responseJSON); return; } console.warn(jqXHR, textStatus, errorThrown); showMessage("error", errorMessage); }, }); }; var modes = { 0 : "osu! standard", 1 : "Taiko", 2 : "Catch the Beat", 3 : "osu!mania", }; var modesShort = { 0 : "std", 1 : "taiko", 2 : "ctb", 3 : "mania", }; var entityMap = { "&" : "&", "<" : "<", ">" : ">", '"' : '"', "'" : ''', "/" : '/', }; function escapeHTML(str) { return String(str).replace(/[&<>"'\/]/g, function(s) { return entityMap[s]; }); } function setupSimplepag(callback) { var el = $(".simplepag"); el.find(".left.floated .item").click(function() { if ($(this).hasClass("disabled")) return false; page--; callback(); }); el.find(".right.floated .item").click(function() { if ($(this).hasClass("disabled")) return false; page++; callback(); }); } function disableSimplepagButtons(right) { var el = $(".simplepag"); if (page <= 1) el.find(".left.floated .item").addClass("disabled"); else el.find(".left.floated .item").removeClass("disabled"); if (right) el.find(".right.floated .item").addClass("disabled"); else el.find(".right.floated .item").removeClass("disabled"); } window.URL = window.URL || window.webkitURL; // thank mr stackoverflow function addCommas(nStr) { nStr += ''; x = nStr.split('.'); x1 = x[0]; x2 = x.length > 1 ? '.' + x[1] : ''; var rgx = /(\d+)(\d{3})/; while (rgx.test(x1)) { x1 = x1.replace(rgx, '$1' + ',' + '$2'); } return x1 + x2; } // helper functions copied from user.js in old-frontend function getScoreMods(m, noplus) { var r = []; // has nc => remove dt if ((m & 512) == 512) m = m & ~64; // has pf => remove sd if ((m & 16384) == 16384) m = m & ~32; modsString.forEach(function(v, idx) { var val = 1 << idx; if ((m & val) > 0) r.push(v); }); if (r.length > 0) { return (noplus ? "" : "+ ") + r.join(", "); } else { return (noplus ? T('None') : ''); } } var modsString = [ "NF", "EZ", "NV", "HD", "HR", "SD", "DT", "RX", "HT", "NC", "FL", "AU", // Auto. "SO", "AP", // Autopilot. "PF", "K4", "K5", "K6", "K7", "K8", "K9", "RN", // Random "LM", // LastMod. Cinema? "K9", "K0", "K1", "K3", "K2", ]; // time format (seconds -> hh:mm:ss notation) function timeFormat(t) { var h = Math.floor(t / 3600); t %= 3600; var m = Math.floor(t / 60); var s = t % 60; var c = ""; if (h > 0) { c += h + ":"; if (m < 10) { c += "0"; } c += m + ":"; } else { c += m + ":"; } if (s < 10) { c += "0"; } c += s; return c; } // http://stackoverflow.com/a/901144/5328069 function query(name, url) { if (!url) { url = window.location.href; } name = name.replace(/[\[\]]/g, "\\$&"); var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, " ")); } // Useful for forms contacting the Ripple API function formToObject(form) { var inputs = form.find("input, textarea, select"); var obj = {}; inputs.each(function(_, el) { el = $(el); if (el.attr("name") === undefined) { return; } var parts = el.attr("name").split("."); var value; switch (el.attr("type")) { case "checkbox": value = el.is(":checked"); break; default: switch (el.data("cast")) { case "int": value = +el.val(); break; default: value = el.val(); break; } break; } obj = modifyObjectDynamically(obj, parts, value); }); return obj; } // > modifyObjectDynamically({}, ["nice", "meme", "dude"], "lol") // { nice: { meme: { dude: 'lol' } } } function modifyObjectDynamically(obj, inds, set) { if (inds.length === 1) { obj[inds[0]] = set; } else if (inds.length > 1) { if (typeof obj[inds[0]] !== "object") obj[inds[0]] = {}; obj[inds[0]] = modifyObjectDynamically(obj[inds[0]], inds.slice(1), set); } return obj; } var langWhitelist = [ "de", "it", "ko", "es", "ru", "pl", "fr", "nl", "sv", "fi", "ro", "ko", "vi" ]; i18next.use(i18nextXHRBackend).init({ nsSeparator : false, keySeparator : false, fallbackLng : false, lng : hanayoConf.language, whitelist : langWhitelist, load : "currentOnly", backend : {loadPath : "/static/locale/{{lng}}.json"} }); var i18nLoaded = $.inArray(hanayoConf.language, langWhitelist) === -1; i18next.on("loaded", function() { i18nLoaded = true }); function T(s, settings) { if (typeof settings !== "undefined" && typeof settings.count !== "undefined" && $.inArray(hanayoConf.language, langWhitelist) === -1 && settings.count !== 1) s = keyPlurals[s]; return i18next.t(s, settings); } var apiPrivileges = [ "ReadConfidential", "Write", "ManageBadges", "BetaKeys", "ManageSettings", "ViewUserAdvanced", "ManageUser", "ManageRoles", "ManageAPIKeys", "Blog", "APIMeta", "Beatmap" ]; function privilegesToString(privs) { var privList = []; apiPrivileges.forEach(function(value, index) { if ((privs & (1 << (index + 1))) != 0) privList.push(value); }); return privList.join(", "); }