Google Maps Contributions Downloader

Download all the photospheres of a specific Google Maps contributor as a GeoGuessr json

  1. // ==UserScript==
  2. // @name Google Maps Contributions Downloader
  3. // @namespace gmcd
  4. // @description Download all the photospheres of a specific Google Maps contributor as a GeoGuessr json
  5. // @version 0.2
  6. // @match https://www.google.com/*
  7. // @run-at document-start
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. locations = []
  13. semaphore = null
  14. n = 0
  15. label = null
  16. lastUpdate = 0
  17. function onLoad() {
  18. h1 = document.querySelector("h1[jsaction='pane.profile-stats.showStats; keydown:pane.profile-stats.showStats']")
  19. div = document.createElement("div")
  20. div.style = "display: flex; flex-direction: horizontal; align-items: center;"
  21. h1.parentNode.insertBefore(div, h1)
  22. div.appendChild(h1)
  23. button = document.createElement("button")
  24. button.innerText = "💾"
  25. button.classList = h1.classList
  26. button.addEventListener("click", onClick)
  27. div.appendChild(button)
  28. label = document.createElement("label")
  29. label.classList = h1.classList
  30. div.appendChild(label)
  31. semaphore = new Semaphore(1000)
  32. }
  33. window.addEventListener("load", onLoad)
  34.  
  35. function onClick() {
  36. locations = []
  37. for (let img of document.getElementsByTagName("img")) {
  38. if (img.src.includes("-fo")) {
  39. let panoId = /AF1Q[^=]*/.exec(img.src)[0]
  40. if (panoId.length == 44) {
  41. panoId = btoa("\b\n\x12," + panoId)
  42. } else if (panoId.length == 43) {
  43. panoId = btoa("\b\n\x12+" + panoId).replaceAll("=", "")
  44. } else if (panoId.length == 42) {
  45. panoId = btoa("\b\n\x12+" + panoId).replaceAll("=", "")
  46. }
  47. locations.push({ lat: 0, lng:0, panoId: panoId })
  48. }
  49. }
  50. n = 0
  51. label.innerText = 0 + "/" + locations.length
  52. lastUpdate = 0
  53. semaphore.add()
  54. for (let location of locations) {
  55. semaphore.add(getMetadata, location)
  56. }
  57. }
  58. class Semaphore {
  59. constructor(max = 1) {
  60. this.max = max
  61. this.compteur = 0
  62. this.liste = []
  63. }
  64.  
  65. add(f, ...args) {
  66. return new Promise((resolve, reject) => {
  67. this.liste.push({
  68. f, args, resolve, reject
  69. })
  70. this.next()
  71. })
  72. }
  73.  
  74. next() {
  75. if (this.liste.length > 0 && this.compteur < this.max) {
  76. let { f, args, resolve, reject } = this.liste.shift()
  77. this.compteur++
  78. f(...args)
  79. .then(resolve)
  80. .catch(reject)
  81. .finally(() => {
  82. this.compteur--
  83. this.next()
  84. })
  85. }
  86. }
  87. }
  88. function getMetadata(location) {
  89. return new Promise((resolve, reject) => {
  90. let url = "https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/GetMetadata"
  91. let init = {
  92. method: "POST",
  93. headers: {
  94. "Content-Type": "application/json+protobuf",
  95. "x-user-agent": "grpc-web-javascript/0.1"
  96. },
  97. body: JSON.stringify([["apiv3"],[],[[[10,atob(location.panoId.replaceAll(".", "")).slice(4)]]],[[1, 2, 3, 4, 6, 8]]])
  98. }
  99. fetch(url, init)
  100. .then((response) => {
  101. return response.json()
  102. })
  103. .then((response) => {
  104. location.lat = response[1][0][5][0][1][0][2]
  105. location.lng = response[1][0][5][0][1][0][3]
  106. })
  107. .catch((response) => {
  108. console.error(response)
  109. })
  110. .finally(() => {
  111. resolve()
  112. n++
  113. if (Date.now() - lastUpdate > 1000) {
  114. label.innerText = n + "/" + locations.length
  115. lastUpdate = Date.now()
  116. }
  117.  
  118. if (n == locations.length) {
  119. label.innerText = null
  120. downloadJSON()
  121. }
  122. })
  123. })
  124. }
  125. function downloadJSON() {
  126. let file = new Blob([JSON.stringify(locations)], { type: "application/json" })
  127. let link = document.createElement("a")
  128. link.target= "_blank"
  129. link.href = URL.createObjectURL(file)
  130. link.download = document.querySelector("h1[jsaction='pane.profile-stats.showStats; keydown:pane.profile-stats.showStats']").innerText + ".json"
  131. link.click()
  132. URL.revokeObjectURL(link.href)
  133. }
  134. })();