您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download all the photospheres of a specific Google Maps contributor as a GeoGuessr json
// ==UserScript== // @name Google Maps Contributions Downloader // @namespace gmcd // @description Download all the photospheres of a specific Google Maps contributor as a GeoGuessr json // @version 0.2 // @match https://www.google.com/* // @run-at document-start // @license MIT // ==/UserScript== (function() { locations = [] semaphore = null n = 0 label = null lastUpdate = 0 function onLoad() { h1 = document.querySelector("h1[jsaction='pane.profile-stats.showStats; keydown:pane.profile-stats.showStats']") div = document.createElement("div") div.style = "display: flex; flex-direction: horizontal; align-items: center;" h1.parentNode.insertBefore(div, h1) div.appendChild(h1) button = document.createElement("button") button.innerText = "💾" button.classList = h1.classList button.addEventListener("click", onClick) div.appendChild(button) label = document.createElement("label") label.classList = h1.classList div.appendChild(label) semaphore = new Semaphore(1000) } window.addEventListener("load", onLoad) function onClick() { locations = [] for (let img of document.getElementsByTagName("img")) { if (img.src.includes("-fo")) { let panoId = /AF1Q[^=]*/.exec(img.src)[0] if (panoId.length == 44) { panoId = btoa("\b\n\x12," + panoId) } else if (panoId.length == 43) { panoId = btoa("\b\n\x12+" + panoId).replaceAll("=", "") } else if (panoId.length == 42) { panoId = btoa("\b\n\x12+" + panoId).replaceAll("=", "") } locations.push({ lat: 0, lng:0, panoId: panoId }) } } n = 0 label.innerText = 0 + "/" + locations.length lastUpdate = 0 semaphore.add() for (let location of locations) { semaphore.add(getMetadata, location) } } class Semaphore { constructor(max = 1) { this.max = max this.compteur = 0 this.liste = [] } add(f, ...args) { return new Promise((resolve, reject) => { this.liste.push({ f, args, resolve, reject }) this.next() }) } next() { if (this.liste.length > 0 && this.compteur < this.max) { let { f, args, resolve, reject } = this.liste.shift() this.compteur++ f(...args) .then(resolve) .catch(reject) .finally(() => { this.compteur-- this.next() }) } } } function getMetadata(location) { return new Promise((resolve, reject) => { let url = "https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/GetMetadata" let init = { method: "POST", headers: { "Content-Type": "application/json+protobuf", "x-user-agent": "grpc-web-javascript/0.1" }, body: JSON.stringify([["apiv3"],[],[[[10,atob(location.panoId.replaceAll(".", "")).slice(4)]]],[[1, 2, 3, 4, 6, 8]]]) } fetch(url, init) .then((response) => { return response.json() }) .then((response) => { location.lat = response[1][0][5][0][1][0][2] location.lng = response[1][0][5][0][1][0][3] }) .catch((response) => { console.error(response) }) .finally(() => { resolve() n++ if (Date.now() - lastUpdate > 1000) { label.innerText = n + "/" + locations.length lastUpdate = Date.now() } if (n == locations.length) { label.innerText = null downloadJSON() } }) }) } function downloadJSON() { let file = new Blob([JSON.stringify(locations)], { type: "application/json" }) let link = document.createElement("a") link.target= "_blank" link.href = URL.createObjectURL(file) link.download = document.querySelector("h1[jsaction='pane.profile-stats.showStats; keydown:pane.profile-stats.showStats']").innerText + ".json" link.click() URL.revokeObjectURL(link.href) } })();