NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name GeoGuessr Maps List Enhanced // @namespace kommu // @description Display all your maps in a simple table. Thx to Nicolas for recent updates. // @version 1.0.1.0 // @match https://www.geoguessr.com/* // @run-at document-start // @license MIT // ==/UserScript== /*jshint esversion: 6 */ var oldHref = document.location.href; if (localStorage.gmle_dark_mode === undefined) { localStorage.gmle_dark_mode = "true"; } if (localStorage.gmle_search_mode === undefined) { localStorage.gmle_search_mode = "classic"; } window.addEventListener("load", (event) => { mapsBehavior(); var bodyList = document.querySelector("body"), observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { if (oldHref != document.location.href) { oldHref = document.location.href; mapsBehavior(); } }); }); var config = { childList: true, subtree: true }; observer.observe(bodyList, config); }); function mapsBehavior() { if (!location.pathname.startsWith("/me/maps") || location.pathname.endsWith("#")) { return false; } var style_general = document.createElement("style"); document.head.appendChild(style_general); style_general.sheet.insertRule("table.custom-table {\n" + " border: 1px solid transparent;\n" + " width: 100%;\n" + " border-collapse: collapse;\n" + " margin-bottom: 20px;\n" + " font-size: 12px;\n" + " border-radius: 10px;\n" + "}") style_general.sheet.insertRule("table.custom-table td, table.custom-table th {\n" + " border: 1px solid #00000026;\n" + " background: #ffffff1a;\n" + " padding: 10px;\n" + "}") style_general.sheet.insertRule("table.custom-table th {\n" + " text-transform: uppercase;\n" + " font-size: 10px;\n" + "}") style_general.sheet.insertRule("table.custom-table td:nth-child(3) {\n" + " font-size: 10px;\n" + " width: 300px!important;\n" + " max-width: 500px;\n" + " word-break: break-word;\n" + "}") style_general.sheet.insertRule(".table-filter input {\n" + " width: 100%;\n" + "}") style_general.sheet.insertRule(".map-name.visible {\n" + " transition-duration: 0.2s;\n" + " display: table-row;\n" + " background: transparent;\n" + "}") style_general.sheet.insertRule(".map-name.visible:hover {\n" + " background: #1e913c47;\n" + " box-shadow: 0 0 20px 7px #095e202b;\n" + "}") style_general.sheet.insertRule(".map-name {\n" + " display: none;\n" + "}") style_general.sheet.insertRule("table.custom-table td:nth-child(1),\n" + "table.custom-table td:nth-child(4),\n" + "table.custom-table td:nth-child(5),\n" + "table.custom-table td:nth-child(6),\n" + "table.custom-table td:nth-child(7) {\n" + " text-align: center;\n" + "}") style_general.sheet.insertRule("div[class^='maps_backButton'] {\n" + " display: none;\n" + "}") style_general.sheet.insertRule("a.delete--button.button.button--small.button--secondary {\n" + " background: #f44336;\n" + " color: white;\n" + "}") style_general.sheet.insertRule("a.publish--button.button.button--small.button--primary {\n" + " background: transparent;\n" + " color: #568209;\n" + " border: 1px solid #568209;\n" + "}") style_general.sheet.insertRule("a.unpublish--button.button.button--small.button--secondary {\n" + " background: transparent;\n" + " color: #e95656;\n" + " border: 1px solid #e95656;\n" + "}") style_general.sheet.insertRule("td.table-row-actions {\n" + " display: flex;\n" + " justify-content: center;\n" + " gap: 10px;\n" + " border: none;\n" + "}") var style_light_mode = document.createElement("style"); style_light_mode.id = "gmle_style_light_mode" document.head.appendChild(style_light_mode); style_light_mode.sheet.insertRule("table.custom-table {\n" + " background: #ffffff;\n" + " color: black!important;\n" + "}") style_light_mode.disabled = localStorage.gmle_dark_mode === "true" var divParent = document.createElement('div'); divParent.id = "gmle"; var elements = document.querySelector("[class^=container_content]"); elements.parentNode.insertBefore(divParent, elements); var header = "<div class=\"table-filter\" style=\"background: #0000001f;margin: 40px;padding: 20px 40px;border-radius: 10px;\"><p><a target=\"_blank\" class=\"button button--small button--primary\" href=\"/map-maker/\">Create a new map</a> <a class=\"button button--small button--secondary\" id=\"gmle_button_switch_light_dark_mode\">Switch Light/Dark mode</a> <a class=\"button button--small button--secondary\" id=\"button_switch_search_mode\">Switch search mode : Classic</a><span style=\"float: right;\" id=\"gmle_count\"></span></p><p><input id=\"filter-map-name\" type=\"text\" placeholder=\"Filter on the map name or description\"></p></div>" var div = document.createElement('div'); div.id = "gmle_header"; div.innerHTML = header divParent.appendChild(div) document.getElementById("gmle_button_switch_light_dark_mode").addEventListener("click", function () { document.getElementById("gmle_style_light_mode").disabled = !document.getElementById("gmle_style_light_mode").disabled localStorage.gmle_dark_mode = document.getElementById("gmle_style_light_mode").disabled }) document.getElementById("button_switch_search_mode").addEventListener("click", function () { if (localStorage.gmle_search_mode === "classic") { localStorage.gmle_search_mode = "exact" } else if (localStorage.gmle_search_mode === "exact") { localStorage.gmle_search_mode = "regex" } else { localStorage.gmle_search_mode = "classic" } document.getElementById("button_switch_search_mode").innerHTML = { "classic": "Switch search mode : Classic", "exact": "Switch search mode : Exact", "regex": "Switch search mode : Regex" }[localStorage.gmle_search_mode] document.getElementById("filter-map-name").dispatchEvent(new Event("input")) }) document.getElementById("button_switch_search_mode").innerHTML = { "classic": "Switch search mode : Classic", "exact": "Switch search mode : Exact", "regex": "Switch search mode : Regex" }[localStorage.gmle_search_mode] div = document.createElement('div'); div.id = "gmle_table"; div.innerHTML = "<div class=\"table-filter\" style=\"background: #0000001f;margin: 40px;padding: 20px 40px;border-radius: 10px;\">Loading maps ...</div>" divParent.appendChild(div) function getMaps(sendResponse, maps = null, page = 0, n = 30) { fetch("https://www.geoguessr.com/api/v4/user-maps/maps?page=" + page + "&count=" + n, { method: 'GET', async: true, contentType: 'json'}) .then((response) => response.json()) .then((response) => { if (maps === null) { maps = response } else { maps = maps.concat(response) } if (response.length == 0) { sendResponse(maps); } else { getMaps(sendResponse, maps, page + 1, n) } }) .catch(function (err) { document.getElementById("gmle_table").innerHTML = "An error occured, while getting maps."; }); } async function getDrafts() { const response = await fetch('https://www.geoguessr.com/api/v4/user-maps/dangling-drafts', { method: 'GET', async: false, contentType: 'json'}); const json = await response.json(); return json; } getMaps(async function (maps) { if (!location.pathname.startsWith("/me/maps") || location.pathname.endsWith("#")) { return false; } document.getElementById("gmle_count").innerHTML = maps.length + " maps loaded" console.log('test'); let drafts = await getDrafts(); if (drafts !== null && drafts.length > 0) { maps = maps.concat(drafts); } maps.sort(function (a, b) { return a.name.localeCompare(b.name); }); var table = "<div style=\"background: #0000001f;margin: 40px;border-radius: 10px;\"><table class=\"custom-table\"><thead><tr><th>published</th><th>Name</th><th>Description</th><th>Nb coordinates</th><th>Likes</th><th>Nb games</th><th>Actions</th></tr></thead><tbody>"; table += getHtml(maps); table += "</tbody></table></div>"; document.getElementById("gmle_table").innerHTML = table document.addEventListener('click', function (event) { if (event.target.matches('.publish--button')) { event.preventDefault(); let slug = event.target.getAttribute('data-target') const requestOptions = {method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({})}; fetch('https://www.geoguessr.com/api/v4/user-maps/drafts/' + slug + '/publish', requestOptions).then(response => response.json()).then(data => window.location.reload()); } if (event.target.matches('.unpublish--button')) { event.preventDefault(); let slug = event.target.getAttribute('data-target') const requestOptions = {method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({published: false})}; fetch('https://www.geoguessr.com/api/v3/profiles/maps/' + slug, requestOptions).then(response => response.json()).then(data => window.location.reload()); } if (event.target.matches('.delete--button')) { event.preventDefault(); var confirmation = confirm('Are you sure you want to delete this map?'); if (!confirmation) { event.stopPropagation(); } let slug = event.target.getAttribute('data-target') const requestOptions = {method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({})}; fetch('https://www.geoguessr.com/api/v4/user-maps/' + slug, requestOptions).then(response => response.json()).then(data => window.location.reload()); } }, false); var rows = document.getElementsByClassName("map-name"); var filter_map_name = document.getElementById("filter-map-name"); filter_map_name.addEventListener('input', function (evt) { var filter_value = filter_map_name.value; if (filter_value != "" && filter_value.length > 0) { let visible = 0; let search_mode = localStorage.gmle_search_mode for (var row_index = 0; row_index < rows.length; row_index++) { if (typeof rows[row_index] != 'undefined') { if (search_mode === "classic" && (rows[row_index].getAttribute('mapname').replace(/[^a-z\d\s]+/gi, "").toLowerCase().includes(filter_value.toLowerCase()) || rows[row_index].getAttribute('mapdescription').replace(/[^a-z\d\s]+/gi, "").toLowerCase().includes(filter_value.toLowerCase()))) { rows[row_index].classList.add("visible"); visible++; } else if (search_mode === "exact" && (rows[row_index].getAttribute('mapname').includes(filter_value) || rows[row_index].getAttribute('mapdescription').includes(filter_value))) { rows[row_index].classList.add("visible"); visible++; } else if (search_mode === "regex" && (rows[row_index].getAttribute('mapname').match(filter_value) || rows[row_index].getAttribute('mapdescription').match(filter_value))) { rows[row_index].classList.add("visible"); visible++; } else { rows[row_index].classList.remove("visible"); } } } document.getElementById("gmle_count").innerHTML = visible + "/" + rows.length } else { for (var row_index_2 = 0; row_index_2 < rows.length; row_index_2++) { if (typeof rows[row_index_2] != 'undefined') { rows[row_index_2].classList.add("visible"); } } document.getElementById("gmle_count").innerHTML = rows.length + "/" + rows.length } }); }) } function getHtml(data) { var output = ''; var already_added = []; for (let obj in data) { var map = data[obj]; if (already_added.includes((map.hasOwnProperty('id')) ? map.id : map.slug)) { continue; } already_added.push((map.hasOwnProperty('id')) ? map.id : map.slug); output += '<tr class="map-name visible" mapname="' + map.name + '" mapdescription="' + ((map.description !== null) ? map.description : '') + '">' + '<td>' + ((!map.hasOwnProperty('published')) ? '🟥' : ((map.published) ? '✅' : '')) + '</td>' + '<td>' + map.name + '</td>' + '<td style="width: 200px">' + ((map.description !== null) ? map.description : '') + '</td>' + '<td>' + ((map.hasOwnProperty('coordinateCount')) ? map.coordinateCount : '0') + '</td>' + '<td>' + ((map.hasOwnProperty('likes')) ? map.likes : '0') + '</td>' + '<td>' + ((map.hasOwnProperty('numFinishedGames')) ? map.numFinishedGames : '0') + '</td>' + '<td class="table-row-actions">' + '<a target="_blank" class="button button--small button--secondary" href="/map-maker/' + ((map.hasOwnProperty('id')) ? map.id : map.slug) + '"><span class="button__animation"></span><span class="button__label">Edit</span></a>' + ((map.hasOwnProperty('published')) ? '<a target="_blank" class="button button--small button--primary" href="/maps/' + ((map.hasOwnProperty('id')) ? map.id : map.slug) + '"><span class="button__animation"></span><span class="button__label">Open</span></a>' : '') + ((!map.hasOwnProperty('id') || (map.hasOwnProperty('published') && !map.published)) ? '<a class="publish--button button button--small button--primary" data-target="' + ((map.hasOwnProperty('id')) ? map.id : map.slug) + '"><span data-target="' + ((map.hasOwnProperty('id')) ? map.id : map.slug) + '" class="publish--button button__label">Publish</span></a>' : '') + ((map.hasOwnProperty('published') && map.published) ? '<a class="unpublish--button button button--small button--secondary" data-target="' + ((map.hasOwnProperty('id')) ? map.id : map.slug) + '"><span data-target="' + ((map.hasOwnProperty('id')) ? map.id : map.slug) + '" class="unpublish--button button__label">Unpublish</span></a>' : '') + '<a class="delete--button button button--small button--secondary" data-target="' + ((map.hasOwnProperty('id')) ? map.id : map.slug) + '"><span data-target="' + ((map.hasOwnProperty('id')) ? map.id : map.slug) + '" class="delete--button button__label">Delete</span></a>' + '</td>' + '</tr>'; } return output; }