// ==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;
}
Wrap
Beautify