// ==UserScript==
// @name wplace - simple map browser
// @namespace http://pawing.cv
// @version v1.2
// @description use openstreetmap to navigate wplace faster! press M to pull up the map, and right click to visit that place
// @author cv
// @match https://wplace.live/*
// @grant GM_openInTab
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
// i promise the comment hiding the actual html is eye candy and not done with malicious intent 😇 (i mean, you can see the exact code it's inserting below)
function openmap() {
let popupcode = `<!--wplace.live-map----------------------------------------------------------------------------------->
<!DOCTYPE html>
<html lang="en">
<head>
<title>OpenStreetMap</title>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;">
<style>
@import url("https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap");
html, body {
height: 100%; line-height: 1; margin: 0; padding: 0;
font-family: "Geist", ui-sans-serif, system-ui, sans-serif !important;
}
#loading {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
background: #f2f7fe; padding: 20px; border-radius: 32px;
font-size: 16px; font-weight: 500; color: #394e6a;
box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); z-index: 1000;
}
.map {
height: 100%; width: 100%; display: none;
}
a, span, p, li, input, ul {
font-family: Geist, ui-sans-serif, system-ui, sans-serif !important;
user-select: none;
}
.coords, .small {user-select: all !important}
.info {
display: flex; flex-direction: row; position: fixed;
justify-content: center; align-items: center;
top: 1em; left: 50%; transform: translateX(-50%);
background: #f2f7fe; padding: 10px; border-radius: 32px;
font-size: 13.5px; font-weight: 700; z-index: 1000;
box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
pointer-events: none; user-select: none; display: none;
}
.info svg {
padding-left: 3px; padding-right: 2px; width: 15px;
}
.coords {
display: flex; flex-direction: column; position: fixed; width: 300px;
bottom: 1em; left: 50%; transform: translateX(-50%);
background: #f2f7fe; display: none;
padding: 20px; border-radius: 32px;
font-size: 18px; z-index: 1000; font-weight: 500;
box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
}
.small {
font-size: 13px; color: #394e6a; opacity: 0.5;
}
/* Leaflet control styling */
div.leaflet-control-container > div.leaflet-top.leaflet-left > div {
border: none;
}
div.leaflet-control-container > div.leaflet-top.leaflet-left > div > a {
display: flex; flex-direction: column; background: #f2f7fe;
border-radius: 32px; margin-bottom: 3px; font-size: 13px;
box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
}
div.leaflet-top.leaflet-right * { border: none; }
div.leaflet-top.leaflet-right > div > button,
div.leaflet-top.leaflet-right > div {
display: flex; flex-direction: column; background: #f2f7fe;
border-radius: 32px; margin-bottom: 3px; color: #394e6a;
}
div.leaflet-top.leaflet-right > div > button { display: none; }
div.leaflet-top.leaflet-right > div > div.leaflet-control-geocoder-form {
display: inline-block; width: 200px; padding: 10px;
box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1);
}
div.leaflet-top.leaflet-right > div > .leaflet-control-geocoder-alternatives {
border-radius: 1px; padding: 10px;
}
div.leaflet-top.leaflet-right > div:has(.leaflet-control-geocoder-alternatives:not([style*="display: none"])) > .leaflet-control-geocoder-form {
box-shadow: none !important;
}
body > div.map.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom > div.leaflet-control-container > div.leaflet-bottom.leaflet-right {
display: none !important;
}
.leaflet-control-geocoder-form-no-error {
text-align: center; color: rgb(179, 97, 97) !important;
padding-bottom: 10px;
}
</style>
</head>
<body>
<div id="loading">Loading map...</div>
<div class="info">Choose a location with <svg alt="Right Click" fill="#394e6a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><path d="M38 0h4v6h-4V0Zm9 1 4 2c-2 4-2 4-7 5 1-5 1-5 3-7ZM36 5c8 6 9 12 9 22v6c-2 10-5 14-14 19-10 1-16 1-23-6-4-7-5-11-5-19v-6C5 4 21-3 36 5ZM9 17l-1 9h14V7c-7 0-10 5-13 10ZM8 30c2 9 3 13 11 17 11 0 14-1 20-10l1-7H8Zm38-20h6v4h-6v-4Z"/></svg>!</div>
<div class="coords"></div>
<div class="map" style="width:100%;height:100%"></div>
<script>
function newcss(url) {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
}
function newjs(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
async function bootmap() {
try {
await newcss('https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css');
await newjs('https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js');
await newcss('https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css');
await newjs('https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js');
document.getElementById('loading').style.display = 'none';
document.querySelector('.map').style.display = 'block';
document.querySelector('.info').style.display = 'flex';
document.addEventListener("contextmenu", function(e) {e.preventDefault()});
var map = L.map(document.querySelector(".map"), {
worldCopyJump: false,
continuousWorld: false
}).setView([0, 0], 1);
map.setMaxBounds([[-85.05112878, -180], [85.05112878, 180]]);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {noWrap: true}).addTo(map);
map.on('drag', function() {
if (!map.getBounds().intersects(map.options.maxBounds)) {
map.panInsideBounds(map.options.maxBounds, { animate: false });
}
});
L.Control.geocoder({
geocoder: L.Control.Geocoder.nominatim()
}).addTo(map);
var lastlatlng = null;
map.on("mousemove", function(e) {
lastlatlng = e.latlng;
var lat = e.latlng.lat;
var lng = e.latlng.lng;
var zoom = map.getZoom();
var tile_count = 2048;
var tile_size = 1000;
var pixel_count = tile_count * tile_size;
var x = (lng + 180) / 360;
var sinlat = Math.sin(lat * Math.PI / 180);
var y = 0.5 - Math.log((1 + sinlat) / (1 - sinlat)) / (4 * Math.PI);
x = Math.min(1, Math.max(0, x));
y = Math.min(1, Math.max(0, y));
var px = x * (pixel_count - 1);
var py = y * (pixel_count - 1);
var t_x = Math.floor(px / tile_size);
var t_y = Math.floor(py / tile_size);
var p_x = Math.floor(px % tile_size);
var p_y = Math.floor(py % tile_size);
document.querySelector(".coords").innerHTML =
lat.toFixed(6) + ", " + lng.toFixed(6) + " [x" + zoom + "] " +
"<br><span class='small'>" + t_x + ", " + t_y + " // " + p_x + ", " + p_y + "</span>";
var coords = document.querySelector(".coords");
if (coords.innerHTML.trim() !== "") {
coords.style.display = "flex";
} else {
coords.style.display = "none";
}
});
document.querySelector(".map").addEventListener("contextmenu", function(ev) {
ev.preventDefault();
if (lastlatlng) {
var osmzoom = map.getZoom();
var wplacezoom = Math.max(1, Math.min(19, osmzoom));
var wplaceUrl = 'https://wplace.live/?lat=' + lastlatlng.lat + '&lng=' + lastlatlng.lng + '&zoom=' + wplacezoom;
window.open(wplaceUrl, '_blank');
window.close();
}
});
} catch (error) {
document.getElementById('loading').textContent = 'Error loading map: ' + error.message;
console.error('map load error!', error);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bootmap);
} else {bootmap()}
</script>
</body>
</html>`;
// the ENTIRE page has to fit inside a data url, since:
// - blob:https://wplace.live inherits wplace.live's csp
// - about:blank, uh, doesn't work either, fuck
if (typeof GM_openInTab !== 'undefined') {
const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(popupcode);
GM_openInTab(dataUrl, { active: true });
} else {
const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(popupcode);
const newTab = window.open(dataUrl, '_blank');
if (!newTab) {
alert("please allow popups for wplace.live..");
}
}
}
// that isn't an absurd workaround, is it? no? thank god
function cleanlink() {
const cleanlink = new URL(window.location.href);
cleanlink.searchParams.delete('lat');
cleanlink.searchParams.delete('lng');
cleanlink.searchParams.delete('zoom');
window.history.replaceState({}, '', cleanlink.pathname + cleanlink.search + cleanlink.hash);
}
window.addEventListener('DOMContentLoaded', function() {
const url = new URL(window.location.href);
if (url.searchParams.has('lat') || url.searchParams.has('lng') || url.searchParams.has('zoom')) {
cleanlink();
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'M' || e.key === 'm') {
openmap();
}
});
})();