Greasy Fork is available in English.
Shows you all your already unlocked regions on the Komoot world map
- // ==UserScript==// @name My Komoot Regions// @name:de Meine Komoot Regionen// @description Shows you all your already unlocked regions on the Komoot world map// @description:de Zeigt dir alle deine bereits freigeschalteten Regionen auf der Komoot Weltkarte an// @namespace https://github.com/tadwohlrapp// @author Tad Wohlrapp// @version 0.1.2// @license MIT// @homepageURL https://github.com/tadwohlrapp/my-komoot-regions// @supportURL https://github.com/tadwohlrapp/my-komoot-regions/issues// @icon https://github.com/tadwohlrapp/my-komoot-regions/raw/main/icon.png// @include https://www.komoot.com/*product/regions*// @grant GM_xmlhttpRequest// ==/UserScript==(function () {'use strict'unsafeWindow.komootMap = nulllet unlockedRegions = []let features = []let processedCount = 0let getMapTries = 0const lang = document.documentElement.langfunction findObjects(object, maxTries, stopAtPrefix) {let tries = 0const visited = []const queue = [{object: object,path: [],}]while (queue.length > 0) {const next = queue.shift()if (!next.object || visited.includes(next.object)) {continue}if (next.object._mapId) {return next.object}visited.push(next.object)for (const property of Object.getOwnPropertyNames(next.object)) {if (stopAtPrefix && property.startsWith(stopAtPrefix)) {return next.object[property];}queue.push({object: next.object[property],path: [...next.path, property],})}if (tries++ > maxTries) {return null}}return null}function getMap() {if (unsafeWindow.komootMap) returnconst elements = document.getElementsByTagName('*')for (const el of elements) {if ((el.className && el.className.toString().toLowerCase().includes("map"))) {const react = findObjects(el, 5000, '__reactInternal')if (react) {const map = findObjects(react, 25000)if (map && map instanceof Object) {if (!unsafeWindow.komootMap) {console.log('Found map!')unsafeWindow.komootMap = mapwaitForGlobal()}break} else if (getMapTries < 10) {getMapTries++console.log(`Looking for map... (Attempt ${getMapTries}/10)`)setTimeout(() => getMap(), 500)}} else if (getMapTries < 10) {getMapTries++console.log(`Looking for map... (Attempt ${getMapTries}/10)`)setTimeout(() => getMap(), 500)}}}}function waitForGlobal() {unlockedRegions = [...new Map(unsafeWindow.kmtBoot.getProps().packages.models.map(region => [region.attributes.region.id, region])).values()]if (unlockedRegions) {displayHeaderText()processUnlockedRegions()} else {setTimeout(() => waitForGlobal(), 500)}}function displayHeaderText() {const unlockedText = () => {const count = unlockedRegions.lengthswitch (lang) {case 'de':return count > 0? `Du hast bereits ${count === 1 ? 'eine' : count} Region${count !== 1 ? 'en' : ''} freigeschaltet.`: `Du hast noch keine Regionen freigeschaltet.`default:return count > 0? `You have unlocked ${count === 1 ? 'one' : count} region${count !== 1 ? 's' : ''} already.`: `You haven't unlocked any regions yet.`}}const availableText = () => {const count = unsafeWindow.kmtBoot.getProps().freeProducts.lengthswitch (lang) {case 'de':return count > 0? `Du kannst noch <strong>${count === 1 ? 'eine' : count}</strong> weitere Region${count !== 1 ? 'en' : ''} kostenlos freischalten! 🎉`: `Aktuell kannst du leider keine weiteren kostenlosen Regionen freischalten.`default:return count > 0? `You can still unlock <strong>${count === 1 ? 'one' : count}</strong> more region${count !== 1 ? 's' : ''} for free! 🎉`: `Unfortunately, there are currently no more free regions to unlock.`}}document.querySelector('h2').innerHTML = unlockedText() + '<br>' + availableText()}function processUnlockedRegions() {const myRegionIds = unlockedRegions.map(region => region.attributes.region.id)const div = document.createElement('div')div.id = 'progress-container'div.classList.add('tw-text-xs', 'tw-px-3', 'tw-py-1', 'tw-overflow-y-auto', 'tw-bg-white-90')document.querySelector('.maplibregl-ctrl-top-left').append(div)switch (lang) {case 'de':div.append(`Verarbeite ${myRegionIds.length} freigeschaltete Regionen...`)breakdefault:div.append(`Processing ${myRegionIds.length} unlocked regions...`)}myRegionIds.forEach(id => getGeometry(id, div))}function getGeometry(id, div) {const totalCount = unlockedRegions.lengthGM_xmlhttpRequest({method: 'GET',url: `https://www.komoot.com/product/regions?region=${id}`,data: false,headers: { "onlyprops": "true" },responseType: 'json',onload: resp => {if (resp.response) {const { id, name, groupId: type, geometry } = resp.response.regions[0]const children = Array.from(div.children)children.forEach(child => child.classList.remove('region--active'))const p = document.createElement('p')p.classList.add('region', 'region--active')div.append(p)p.textContent = `${processedCount + 1}/${totalCount}: ${name}`buildGeoObject({ id, name, type, geometry })processedCount++if (processedCount === totalCount) {p.classList.remove('region--active')drawOnMap(features)switch (lang) {case 'de':div.append('Fertig 👍')breakdefault:div.append('Done 👍')}setTimeout(() => div.remove(), 2000)}div.scrollTo(0, div.scrollHeight)}},})}function buildGeoObject({ id, name, type, geometry }) {const geometryArr = geometry[0]const coordinates = []geometryArr.forEach(item => {const latLng = []latLng.push(item.lng)latLng.push(item.lat)coordinates.push(latLng)})const geoJson = {"type": "Feature","properties": {"id": id,"name": name,"region": type === 1},"geometry": {"type": "Polygon","coordinates": [coordinates]}}features.push(geoJson)}function drawOnMap(features) {if (!unsafeWindow.komootMap) returnconst geoJsonData = {"type": "FeatureCollection","features": features}const source = unsafeWindow.komootMap.getSource('my_unlocked_regions')if (source) {source.setData(data)} else {unsafeWindow.komootMap.addSource('my_unlocked_regions', {type: 'geojson',data: geoJsonData})}unsafeWindow.komootMap.addLayer({'id': 'Tad-my-regions','type': 'fill','source': 'my_unlocked_regions','layout': {},'paint': {'fill-color': ["case",["boolean", ["get", "region"]],["rgba", 16, 134, 232, 1],["rgba", 245, 82, 94, 1]],'fill-opacity': 0.333}}, "komoot-selected-marker")}function addGlobalStyle(css) {const head = document.getElementsByTagName('head')[0]if (!head) returnconst style = document.createElement('style')style.innerHTML = csshead.append(style)}addGlobalStyle(`.maplibregl-ctrl-top-left {max-height: 100%;z-index: 110 !important;}#progress-container {line-height: 1.75;font-weight: bold;}#progress-container .region {margin: 0;font-weight: normal;}#progress-container .region.region--active {position: relative;display: flex;align-items: center;}#progress-container .region.region--active::after {content: '';box-sizing: border-box;display: inline-flex;width: 13px;height: 13px;margin-left: 8px;border-radius: 50%;border: 2px solid transparent;border-top-color: #4f850d;border-bottom-color: #4f850d;animation: spinner .6s linear infinite;}@keyframes spinner {to {transform: rotate(360deg);}}`)getMap()})()