NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name GeoGuessr Liked Maps List Enhanced // @namespace kommu // @description Display all your liked maps in a simple table. // @version 1.0.0 // @include 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/likes") || 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 = 100) { fetch("https://www.geoguessr.com/api/v3/likes?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."; }); } getMaps(async function (maps) { if (!location.pathname.startsWith("/me/likes") || location.pathname.endsWith("#")) { return false; } document.getElementById("gmle_count").innerHTML = maps.length + " maps loaded" 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\"><tr><th>Updated Recently</th><th>Name</th><th>Creator</th><th>Created At</th><th>Updated At</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 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 = ''; for (let obj in data) { var map = data[obj]; let recent_update = false; let day_ago = ''; let updatedAt = ''; if (map.updatedAt !== null) { let updated = Date.parse(map.updatedAt); if ((Date.now() - 2592000000) < updated) { recent_update = true; day_ago = timeSince(updated); } updatedAt = new Date(Date.parse(map.updatedAt)).toLocaleString(); } let created = new Date(Date.parse(map.createdAt)).toLocaleString(); output += '<tr class="map-name visible" mapname="' + map.name + '" mapdescription="' + ((map.description !== null) ? map.description : '') + ' ' + ((map.creator !== null) ? map.creator.nick.replace(/[^a-z\d\s]+/gi, "").toLowerCase() : '') + '">' + '<td>' + ((recent_update) ? '✅<br>' + day_ago + ' ago' : day_ago) + '</td>' + '<td>' + map.name + '</td>' + '<td>' + ((map.creator == null) ? 'GeoGuessr' : map.creator.nick) + '</td>' + '<td>' + created + '</td>' + '<td>' + updatedAt + '</td>' + '<td style="width: 200px; font-size: 10px;">' + map.description + '</td>' + '<td>' + map.coordinateCount + '</td>' + '<td>' + map.likes + '</td>' + '<td>' + map.numFinishedGames + '</td>' + '<td style="min-width: 160px;">' + ' <a target="_blank" class="button button--small button--secondary" href="' + map.url + '"><span class="button__animation"></span><span class="button__label">Open</span></a>' + ' <a target="_blank" class="button button--small button--primary" href="' + map.playUrl + '"><span class="button__animation"></span><span class="button__label">Play</span></a>' + '</td></tr>'; } return output; } function timeSince(date) { let seconds = Math.floor((new Date() - date) / 1000); let interval = seconds / 31536000; if (interval > 1) { return Math.floor(interval) + " years"; } interval = seconds / 2592000; if (interval > 1) { return Math.floor(interval) + " months"; } interval = seconds / 86400; if (interval > 1) { return Math.floor(interval) + " days"; } interval = seconds / 3600; if (interval > 1) { return Math.floor(interval) + " hours"; } interval = seconds / 60; if (interval > 1) { return Math.floor(interval) + " minutes"; } return Math.floor(seconds) + " seconds"; }