123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- function login_then_go_to(url) {
- const domain_name = document.location.hostname.substring(document.location.hostname.indexOf('.') + 1);
- document.cookie = `login_to=${encodeURIComponent(url)}; path=/; domain=${domain_name}; secure; SameSite=Strict; Max-Age=30000`;
- document.location = '/osu_login';
- }
- function ask_for_manual_lobby_creation(lobby_settings) {
- document.querySelector('main .loading-placeholder').replaceWith(
- document.querySelector('#manual-lobby-creation-template').content.cloneNode(true),
- );
- document.querySelector('main .create-lobby-with-ref-btn').addEventListener('click', (evt) => {
- evt.preventDefault();
- evt.stopPropagation();
- // Input shown if bot can't create any more lobbies and needs a user-made lobby
- const match_input = document.querySelector('main input[name="tournament-url"]');
- if (match_input.value) {
- lobby_settings.match_id = parseInt(match_input.value.split('/').reverse()[0], 10);
- }
- try_creating_lobby(lobby_settings);
- });
- }
- async function try_creating_lobby(lobby_settings) {
- document.querySelector('main').innerHTML = `
- <div class="loading-placeholder text-center mt-24">
- <p>Creating lobby...</p>
- <svg class="animate-spin m-auto my-4 h-10 w-10 text-orange-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
- </svg>
- </div>`;
- try {
- const res = await fetch('/api/create-lobby/', {
- body: JSON.stringify(lobby_settings),
- credentials: 'include',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- },
- method: 'POST',
- });
- const json_res = await res.json();
- if (json_res.error) {
- if (json_res.details == 'Cannot create any more matches.') {
- ask_for_manual_lobby_creation(lobby_settings);
- return;
- }
- throw new Error(json_res.details || json_res.error);
- }
- document.querySelector('main .loading-placeholder').replaceWith(
- document.querySelector('#lobby-created-template').content.cloneNode(true),
- );
- document.querySelector('.lobby').replaceWith(render_lobby(json_res.lobby));
- document.querySelector('.info').appendChild(document.querySelector('#command-list-template').content.cloneNode(true));
- } catch (err) {
- document.querySelector('main').innerHTML = `
- An error occurred while creating the lobby:
- <div class="error-msg mt-4"></div>`;
- document.querySelector('.error-msg').innerText = err.message;
- }
- }
- function render_lobby(lobby, user_has_lobby_open) {
- const lobby_div = document.createElement('div');
- let map_pool_info = 'All maps';
- if (lobby.map_pool == 'collection') {
- const pool = document.createElement('a');
- pool.href = 'https://osucollector.com/collections/' + lobby.collection_id;
- pool.target = '_blank';
- pool.innerText = lobby.collection_name;
- map_pool_info = pool.outerHTML;
- }
- let map_selection_info = 'Random';
- if (lobby.map_selection_algo == 'pp') {
- map_selection_info = 'Average player pp';
- } else if (lobby.map_selection_algo == 'elo') {
- map_selection_info = 'Average player elo';
- }
- if (lobby.filters.length > 0) {
- const filter_span = document.createElement('span');
- filter_span.classList = 'cursor-help underline underline-offset-2 decoration-1 decoration-dotted';
- filter_span.innerText = lobby.filters.length + ' filters';
- filter_span.title = '';
- for (const filter of lobby.filters) {
- filter_span.title += `${filter.name} between ${filter.min} and ${filter.max}\n`;
- }
- map_selection_info += ' (' + filter_span.outerHTML + ')';
- }
- let mods_info = 'None';
- if (lobby.mod_list.length > 0) {
- mods_info = lobby.mod_list.join(', ');
- }
- if (lobby.freemod) {
- mods_info += ' (freemod)';
- }
- let status = '';
- if (lobby.end_time) {
- function reltime(diff) {
- const rtf = new Intl.RelativeTimeFormat('en', {numeric: 'auto'});
- if (diff > -60) {
- return rtf.format(Math.round(diff), 'second');
- } else if (diff > -3600) {
- return rtf.format(Math.round(diff / 60), 'minute');
- } else if (diff > -86400) {
- return rtf.format(Math.round(diff / 3600), 'hour');
- } else {
- return rtf.format(Math.round(diff / 86400), 'day');
- }
- }
- const seconds_ago = reltime((lobby.end_time - Date.now()) / 1000);
- const closing_time = new Intl.DateTimeFormat(undefined, {
- year: 'numeric', month: 'numeric', day: 'numeric',
- hour: 'numeric', minute: 'numeric', second: 'numeric',
- }).format(new Date(lobby.end_time));
- status = `Closed <span title="${closing_time}">${seconds_ago}</span>`;
- } else {
- status = `${lobby.nb_players}/16 players`;
- }
- lobby_div.style = `border: solid ${lobby.color} 2px`;
- lobby_div.innerHTML += `
- <div class="lobby-info min-w-[28rem] p-2">
- <div class="lobby-title font-bold"></div>
- <div><strong>Map pool:</strong> ${map_pool_info}</div>
- <div><strong>Map selection:</strong> ${map_selection_info}</div>
- <div><strong>Mods:</strong> ${mods_info}</div>
- <div class="mania-keycounts hidden"><strong>Keys:</strong> ${lobby.key_counts.join(', ')}</div>
- <div>${status} · Created by <a href="/u/${lobby.creator_id}"><img class="h-5 text-bottom rounded-full inline" src="https://s.ppy.sh/a/${lobby.creator_id}" alt="Lobby creator"> ${lobby.creator_name}</a></div>
- </div>`;
- if (lobby.end_time) {
- lobby_div.className = 'flex-1 m-2 rounded-md';
- lobby_div.innerHTML += `
- <div class="mb-2" ${user_has_lobby_open ? 'hidden' : ''}>
- <button class="go-to-create-lobby text-white rounded-lg py-1.5 px-6">Reopen</button>
- </div>`;
- // TODO: make "Copy settings" buttons instead of too basic "reopen"
- lobby_div.querySelector('.go-to-create-lobby').style = `background-color: ${lobby.color}; margin: auto; display: block`;
- lobby_div.querySelector('.go-to-create-lobby').addEventListener('click', (evt) => {
- evt.preventDefault();
- evt.stopPropagation();
- if (window.logged_user_id) {
- document.location = `/reopen-lobby/${lobby.match_id}`;
- } else {
- return login_then_go_to(`${location.origin}/reopen-lobby/${lobby.match_id}`);
- }
- });
- } else {
- // Font Awesome Free 6.1.1 by @fontawesome - https://fontawesome.com
- // License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
- // Copyright 2022 Fonticons, Inc.
- const fa_open = '<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" height="0.75em" viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z"/></svg>';
- const fa_invite = '<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" height="0.75em" viewBox="0 0 512 512"><path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"/></svg>';
- lobby_div.className = 'flex m-2 rounded-md';
- lobby_div.innerHTML += `
- <div class="lobby-links flex flex-col justify-evenly" style="background-color:${lobby.color}">
- <div class="group relative text-center"><a class="!text-white text-2xl p-1.5 pl-2 flex" href="osu://mp/${lobby.bancho_id}">${fa_open}</a><span class="tooltip top-[-0.2rem]" style="background-color:${lobby.color}">Join</span></div>
- <div class="group relative text-center"><button class="!text-white text-2xl p-1.5 pl-2 get-invite-btn">${fa_invite}</button><span class="tooltip top-[-0.1rem]" style="background-color:${lobby.color}">Get invite</span></div>
- </div>`;
- lobby_div.querySelector('.get-invite-btn').addEventListener('click', (evt) => {
- evt.preventDefault();
- evt.stopPropagation();
- if (!window.logged_user_id) {
- return login_then_go_to(`${location.origin}/lobbies/`);
- }
- async function request_invite() {
- try {
- const res = await fetch(`/get-invite/${lobby.bancho_id}`, {
- credentials: 'include',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- },
- method: 'POST',
- });
- const json_res = await res.json();
- if (json_res.error) throw new Error(json_res.error);
- alert('An invite to the lobby has been sent. Check your in-game messages.');
- } catch (err) {
- alert(err.message);
- }
- }
- request_invite();
- });
- }
- lobby_div.querySelector('.lobby-title').innerText = lobby.name;
- // Mania: display keycounts
- if (window.selected_ruleset == 3) {
- lobby_div.querySelector('.mania-keycounts').classList.remove('hidden');
- }
- return lobby_div;
- }
- async function render_lobbies() {
- const template = document.querySelector('#lobbies-template').content.cloneNode(true);
- const res = await fetch(`/api/lobbies/`, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- },
- });
- const json = await res.json();
- let user_has_lobby_open = false;
- const open_lobbies = [];
- const closed_lobbies = [];
- for (const lobby of json) {
- if (lobby.end_time) {
- closed_lobbies.push(lobby);
- } else {
- open_lobbies.push(lobby);
- if (lobby.creator_id == window.logged_user_id) {
- // User already created a lobby: hide the "Create lobby" button
- template.querySelector('.lobby-creation-banner').hidden = true;
- user_has_lobby_open = true;
- }
- }
- }
- if (open_lobbies.length == 0) {
- template.querySelector('.lobby-creation-banner span').innerText = 'No lobbies are open right now.';
- } else {
- template.querySelector('.lobby-creation-banner span').innerText = 'Not satisfied?';
- }
- template.querySelector('.go-to-create-lobby').addEventListener('click', (evt) => {
- evt.preventDefault();
- evt.stopPropagation();
- if (window.logged_user_id) {
- document.location = '/create-lobby/';
- } else {
- return login_then_go_to(`${location.origin}/create-lobby/`);
- }
- });
- // Display open lobbies
- let list = template.querySelector('.open-lobby-list');
- for (const lobby of open_lobbies) {
- list.appendChild(render_lobby(lobby));
- }
- // Display closed lobbies
- list = template.querySelector('.closed-lobby-list');
- for (const lobby of closed_lobbies) {
- list.appendChild(render_lobby(lobby, user_has_lobby_open));
- }
- document.querySelector('main').replaceChildren(template);
- setTimeout(render_lobbies, 10000);
- }
- async function render_create_lobby() {
- const rulesets = ['osu', 'taiko', 'catch', 'mania'];
- const template = document.querySelector('#lobby-creation-template').content.cloneNode(true);
- template.querySelector('h1').innerText = `New ${rulesets[window.selected_ruleset]} lobby`;
- document.querySelector('main .loading-placeholder').replaceWith(template);
- const filter_list = document.querySelector('.filter-list');
- const render_filter = (options) => `
- <div class="filter flex flex-col border-2 border-orange-600 rounded-md m-1 max-w-sm">
- <div class="pl-2 bg-orange-600 text-white font-bold select-none">
- <input type="checkbox" name="${options.name}_filter" id="${options.name}_filter">
- <label class="inline-block w-80 cursor-pointer" for="${options.name}_filter">Limit ${options.label}</label>
- </div>
- <fieldset class="p-2 flex justify-evenly" disabled>
- <div><label for="min_${options.name}">Minimum </label><input class="ml-1 w-16 text-right" type="number" name="min_${options.name}" min="${options.min}" max="${options.max - options.step}" value="${options.default_min}" step="${options.step}" id="min_${options.name}"></div>
- <div><label for="max_${options.name}">Maximum </label><input class="ml-1 w-16 text-right" type="number" name="max_${options.name}" min="${options.min + options.step}" max="${options.max}" value="${options.default_max}" step="${options.step}" id="max_${options.name}"></div>
- </fieldset>
- </div>`;
- filter_list.innerHTML += render_filter({name: 'pp', label: 'performance points (PP)', min: 0, max: 2000, default_min: 150, default_max: 200, step: 25});
- filter_list.innerHTML += render_filter({name: 'sr', label: 'star rating (SR)', min: 0, max: 20, default_min: 5, default_max: 6, step: 0.5});
- filter_list.innerHTML += render_filter({name: 'ar', label: 'approach rate (AR)', min: 0, max: 11, default_min: 9, default_max: 9.5, step: 0.1});
- filter_list.innerHTML += render_filter({name: 'cs', label: 'circle size (CS)', min: 0, max: 10, default_min: 5, default_max: 6, step: 1});
- filter_list.innerHTML += render_filter({name: 'od', label: 'overall difficulty (OD)', min: 0, max: 10, default_min: 6, default_max: 8, step: 1});
- filter_list.innerHTML += render_filter({name: 'bpm', label: 'beats per minute (BPM)', min: 0, max: 1000, default_min: 160, default_max: 220, step: 10});
- filter_list.innerHTML += render_filter({name: 'length', label: 'length (in seconds)', min: 0, max: 3600, default_min: 30, default_max: 500, step: 30});
- document.querySelector('#length_filter').checked = true;
- document.querySelector('input[name="title"]').addEventListener('input', (evt) => {
- let title_preview = evt.target.value;
- const replacements = [
- ['$min_stars', '0'], ['$avg_stars', '5.5'], ['$max_stars', '11'], ['$stars', '0-11'],
- ['$min_elo', '1200'], ['$avg_elo', '1500'], ['$max_elo', '1800'], ['$elo', '1500'],
- ['$min_pp', '100'], ['$avg_pp', '150'], ['$max_pp', '200'], ['$pp', '150'],
- ];
- for (let i = 0; i < replacements.length; i++) {
- title_preview = title_preview.replaceAll(replacements[i][0], replacements[i][1]);
- }
- title_preview = title_preview.substring(0, 50);
- document.querySelector('.preview').innerText = title_preview;
- });
- document.querySelectorAll('.filter').forEach((filter) => {
- filter.querySelector('input[type="checkbox"]').addEventListener('change', function() {
- const fieldset = filter.querySelector('fieldset');
- fieldset.disabled = !fieldset.disabled;
- });
- });
- // Mania-specific mods
- if (window.selected_ruleset == 3) {
- document.querySelector('.mr').classList.remove('hidden');
- document.querySelector('.co').classList.remove('hidden');
- document.querySelector('.fi').classList.remove('hidden');
- let mods = '';
- for (let i = 1; i <= 9; i++) {
- mods += `
- <div class="mod-btn">
- <img class="h-8" src="/images/mod_${i}[email protected]" title="${i}K" alt="${i}K" />
- <div>${i}K</div>
- </div>`;
- }
- document.querySelector('.mania-keycount-settings').innerHTML = `
- <div class="flex mt-2 cursor-pointer select-none">
- <div class="collapser"></div>
- <h3 class="underline">Key count</h3>
- </div>
- <div class="hidden">${mods}</div>
- `;
- document.querySelectorAll('.mania-keycount-settings .mod-btn').forEach((btn) => btn.addEventListener('click', function() {
- this.classList.toggle('mod-btn-selected');
- }));
- }
- // Click to toggle collapse
- document.querySelectorAll('.collapser').forEach((collapser) => {
- collapser.parentElement.addEventListener('click', () => {
- collapser.classList.toggle('rotated');
- collapser.parentElement.nextElementSibling.classList.toggle('hidden');
- });
- });
- // Circle size does not apply for taiko/manio
- if (window.selected_ruleset == 1 || window.selected_ruleset == 3) {
- document.querySelector('#cs_filter').parentElement.parentElement.classList.add('hidden');
- }
- document.querySelectorAll('.mod-settings .mod-btn').forEach((btn) => btn.addEventListener('click', function() {
- const NM = document.querySelector('.nm');
- const DT = document.querySelector('.dt');
- const NC = document.querySelector('.nc');
- const HT = document.querySelector('.ht');
- const EZ = document.querySelector('.ez');
- const HR = document.querySelector('.hr');
- const mod = this.querySelector('div').innerText;
- this.classList.toggle('mod-btn-selected');
- let selected = this.classList.contains('mod-btn-selected');
- if (mod != 'NM' && mod != 'DT' && mod != 'NC' && mod != 'HT') {
- NM.classList.remove('mod-btn-selected');
- }
- if (mod == 'NM' && selected) {
- document.querySelectorAll('.mod-settings .mod-btn').forEach((btn) => {
- if (btn.classList.contains('dt') || btn.classList.contains('nc') || btn.classList.contains('ht')) return;
- btn.classList.remove('mod-btn-selected');
- });
- this.classList.add('mod-btn-selected');
- }
- if (mod == 'DT' && !selected) {
- DT.classList.toggle('hidden');
- NC.classList.toggle('hidden');
- NC.classList.add('mod-btn-selected');
- selected = true;
- }
- if (mod == 'NC') {
- DT.classList.toggle('hidden');
- NC.classList.toggle('hidden');
- }
- if (selected && (mod == 'DT' || mod == 'NC')) {
- HT.classList.remove('mod-btn-selected');
- }
- if (selected && mod == 'HT') {
- DT.classList.remove('mod-btn-selected');
- DT.classList.remove('hidden');
- NC.classList.remove('mod-btn-selected');
- NC.classList.add('hidden');
- }
- if (selected && mod == 'EZ') {
- HR.classList.remove('mod-btn-selected');
- }
- if (selected && mod == 'HR') {
- EZ.classList.remove('mod-btn-selected');
- }
- }));
- if (window.last_match_id) {
- const recreate_lobby_div = document.createElement('div');
- recreate_lobby_div.className = 'border-2 border-orange-600 rounded-lg p-3 text-center mx-2 mt-4';
- recreate_lobby_div.innerHTML = `
- <p>You have already created a lobby, but it has been closed due to inactivity.</p>
- <p class="mb-2">If you wish to keep the same settings, you can just reopen it:</p>
- <button class="reopen-lobby default-btn">Reopen last lobby</button>`;
- document.querySelector('main .lobby-settings').append(recreate_lobby_div);
- document.querySelector('.reopen-lobby').addEventListener('click', (evt) => {
- evt.preventDefault();
- evt.stopPropagation();
- if (window.logged_user_id) {
- document.location = `/reopen-lobby/${window.last_match_id}`;
- } else {
- return login_then_go_to(`${location.origin}/reopen-lobby/${window.last_match_id}`);
- }
- });
- }
- document.querySelector('main .create-lobby-btn').addEventListener('click', (evt) => {
- evt.preventDefault();
- evt.stopPropagation();
- const lobby_settings = {
- title: document.querySelector('input[name="title"]').value,
- map_selection_algo: document.querySelector('main input[name="map-selection-type"]:checked').value,
- map_pool: document.querySelector('main input[name="map-pool"]:checked').value,
- collection_id: null,
- mod_list: [],
- filters: [],
- key_count: [],
- };
- if (lobby_settings.map_pool == 'collection') {
- const collection_input = document.querySelector('main input[name="collection-url"]');
- lobby_settings.collection_id = parseInt(collection_input.value.split('/').reverse()[0], 10);
- }
- const selected_mods = document.querySelectorAll('.mod-settings .mod-btn-selected');
- for (const mod of selected_mods) {
- lobby_settings.mod_list.push(mod.innerText.trim());
- }
- const filters = document.querySelectorAll('.filter');
- for (const filter of filters) {
- const checkbox = filter.querySelector('input[type="checkbox"]');
- if (!checkbox.checked) continue;
- const name = checkbox.id.substring(0, checkbox.id.indexOf('_filter'));
- const min = document.querySelector(`input[name="min_${name}"]`).value;
- const max = document.querySelector(`input[name="max_${name}"]`).value;
- lobby_settings.filters.push({name, min, max});
- }
- if (window.selected_ruleset == 3) {
- const selected_keys = document.querySelectorAll('.mania-keycount-settings .mod-btn-selected');
- for (const key of selected_keys) {
- lobby_settings.key_count.push(parseInt(key.innerText, 10));
- }
- }
- try_creating_lobby(lobby_settings);
- });
- const radios = document.querySelectorAll('.radio-area');
- for (const area of radios) {
- area.addEventListener('click', function() {
- this.querySelector('input[type="radio"]').click();
- });
- }
- }
- let m;
- if (m = location.pathname.match(/\/create-lobby\//)) {
- render_create_lobby();
- } else if (m = location.pathname.match(/\/reopen-lobby\/(\d+)/)) {
- try_creating_lobby({old_match_id: parseInt(m[1], 10)});
- } else if (m = location.pathname.match(/\/lobbies\//)) {
- render_lobbies();
- }
|