Skip to content

Commit 1dda298

Browse files
committed
v1.3.0
1 parent f977412 commit 1dda298

2 files changed

Lines changed: 338 additions & 0 deletions

File tree

dist/release-1.3.0.user.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// ==UserScript==
2+
// @name Bikemap.net Export GPX and KML routes
3+
// @description Download GPX, KML, TCX and geoJSON files for a route on bikemap.net
4+
// @namespace github.com/cvzi
5+
// @icon https://static.bikemap.net/favicons/apple-touch-icon.png
6+
// @match https://www.bikemap.net/*
7+
// @match https://web.bikemap.net/*
8+
// @connect www.bikemap.net
9+
// @version 1.3.0
10+
// @homepage https://github.com/cvzi/bikemapnet-userscript
11+
// @author cuzi
12+
// @license MIT
13+
// @grant GM.xmlHttpRequest
14+
// @grant GM.registerMenuCommand
15+
// @downloadURL https://update.greasyfork.org/scripts/445713/Bikemapnet%20Export%20GPX%20and%20KML%20routes.user.js
16+
// @updateURL https://update.greasyfork.org/scripts/445713/Bikemapnet%20Export%20GPX%20and%20KML%20routes.meta.js
17+
// ==/UserScript==
18+
19+
/*
20+
MIT License
21+
22+
Copyright (c) 2022, cuzi (https://openuserjs.org/users/cuzi)
23+
24+
Permission is hereby granted, free of charge, to any person obtaining a copy
25+
of this software and associated documentation files (the "Software"), to deal
26+
in the Software without restriction, including without limitation the rights
27+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
28+
copies of the Software, and to permit persons to whom the Software is
29+
furnished to do so, subject to the following conditions:
30+
31+
The above copyright notice and this permission notice shall be included in all
32+
copies or substantial portions of the Software.
33+
34+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40+
SOFTWARE.
41+
*/
42+
43+
/* globals React, ReactDOM */
44+
(function () {
45+
'use strict';
46+
47+
/* globals GM, sessionStorage, Blob */
48+
49+
if (document.location.href.match(/\/r\/(\d+)/)) {
50+
addDownloadButtons();
51+
}
52+
53+
window.setInterval(makeButtonsClickable, 3000)
54+
55+
function makeButtonsClickable() {
56+
if (document.getElementById('script_gpx_button')) {
57+
return
58+
}
59+
const buttonGroup = document.querySelector('[class*=ButtonGroup_root]')
60+
61+
const aGPX = document.createElement('a')
62+
aGPX.id = 'script_gpx_button'
63+
aGPX.href = '#'
64+
aGPX.textContent = 'GPX'
65+
aGPX.addEventListener('click', function(ev) {
66+
if (!('ready' in this.dataset)) {
67+
ev.preventDefault()
68+
startDownload('gpx', this)
69+
}
70+
})
71+
aGPX.className = buttonGroup.querySelector('a').className
72+
buttonGroup.appendChild(aGPX)
73+
74+
const aKML = document.createElement('a')
75+
aKML.href = '#'
76+
aKML.textContent = 'KML'
77+
aKML.addEventListener('click', function(ev) {
78+
if (!('ready' in this.dataset)) {
79+
ev.preventDefault()
80+
startDownload('kml', this)
81+
}
82+
})
83+
aKML.className = buttonGroup.querySelector('a').className
84+
buttonGroup.appendChild(aKML)
85+
}
86+
87+
function addDownloadButtons() {
88+
GM.registerMenuCommand('Download gpx', () => startDownload('gpx'));
89+
GM.registerMenuCommand('Download kml', () => startDownload('kml'));
90+
}
91+
92+
function downloadUrl(url, title, ext, button) {
93+
if (button) {
94+
button.href = url
95+
button.style.color = 'green'
96+
button.dataset.ready = 1
97+
button.target = '_blank'
98+
window.setTimeout(() => button.click(), 100)
99+
} else {
100+
document.location.href = url
101+
}
102+
}
103+
104+
105+
function startDownload(key, button) {
106+
const routeId = parseInt(document.location.href.match(/\/r\/(\d+)/)[1]);
107+
cachedRequest({
108+
method: 'GET',
109+
url: `https://www.bikemap.net/api/v5/routes/${routeId}/`,
110+
headers: {
111+
'x-requested-with': 'XMLHttpRequest'
112+
},
113+
onload: function (resp) {
114+
const routeData = JSON.parse(resp.responseText);
115+
downloadUrl(routeData[key], routeData.title, key, button);
116+
},
117+
onerror: function (response) {
118+
window.alert('Error:' + response.status);
119+
}
120+
});
121+
122+
}
123+
124+
function fileName(title, ext) {
125+
let name = title.replace(/[:*?<>/\\,|\u0000]/g, ''); // eslint-disable-line no-control-regex
126+
127+
name = name.trim().replace(/^\.+/, '').replace(/\.+$/, '').trim();
128+
return name + '.' + ext;
129+
}
130+
131+
function cachedRequest(obj) {
132+
if ('data' in obj || obj.method !== 'GET') {
133+
return GM.xmlHttpRequest(obj);
134+
}
135+
136+
const cached = sessionStorage.getItem(obj.url);
137+
138+
if (cached !== null) {
139+
window.setTimeout(function () {
140+
const result = JSON.parse(cached);
141+
obj.onload(result);
142+
}, 1);
143+
} else {
144+
const orgOnload = obj.onload;
145+
146+
obj.onload = function (response) {
147+
const newResponse = {};
148+
149+
for (const key in response) {
150+
newResponse[key] = response[key];
151+
}
152+
153+
newResponse.responseText = '' + response.responseText;
154+
newResponse.cached = true;
155+
156+
if (!('time' in newResponse)) {
157+
newResponse.time = new Date().toJSON();
158+
}
159+
160+
sessionStorage.setItem(obj.url, JSON.stringify(newResponse));
161+
orgOnload.apply(this, [response]);
162+
};
163+
164+
GM.xmlHttpRequest(obj);
165+
}
166+
}
167+
168+
})();
169+

simple.user.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// ==UserScript==
2+
// @name Bikemap.net Export GPX and KML routes
3+
// @description Download GPX, KML, TCX and geoJSON files for a route on bikemap.net
4+
// @namespace github.com/cvzi
5+
// @icon https://static.bikemap.net/favicons/apple-touch-icon.png
6+
// @match https://www.bikemap.net/*
7+
// @match https://web.bikemap.net/*
8+
// @connect www.bikemap.net
9+
// @version 1.3.0
10+
// @homepage https://github.com/cvzi/bikemapnet-userscript
11+
// @author cuzi
12+
// @license MIT
13+
// @grant GM.xmlHttpRequest
14+
// @grant GM.registerMenuCommand
15+
// @downloadURL https://update.greasyfork.org/scripts/445713/Bikemapnet%20Export%20GPX%20and%20KML%20routes.user.js
16+
// @updateURL https://update.greasyfork.org/scripts/445713/Bikemapnet%20Export%20GPX%20and%20KML%20routes.meta.js
17+
// ==/UserScript==
18+
19+
/*
20+
MIT License
21+
22+
Copyright (c) 2022, cuzi (https://openuserjs.org/users/cuzi)
23+
24+
Permission is hereby granted, free of charge, to any person obtaining a copy
25+
of this software and associated documentation files (the "Software"), to deal
26+
in the Software without restriction, including without limitation the rights
27+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
28+
copies of the Software, and to permit persons to whom the Software is
29+
furnished to do so, subject to the following conditions:
30+
31+
The above copyright notice and this permission notice shall be included in all
32+
copies or substantial portions of the Software.
33+
34+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40+
SOFTWARE.
41+
*/
42+
43+
/* globals React, ReactDOM */
44+
(function () {
45+
'use strict';
46+
47+
/* globals GM, sessionStorage, Blob */
48+
49+
if (document.location.href.match(/\/r\/(\d+)/)) {
50+
addDownloadButtons();
51+
}
52+
53+
window.setInterval(makeButtonsClickable, 3000)
54+
55+
function makeButtonsClickable() {
56+
if (document.getElementById('script_gpx_button')) {
57+
return
58+
}
59+
const buttonGroup = document.querySelector('[class*=ButtonGroup_root]')
60+
61+
const aGPX = document.createElement('a')
62+
aGPX.id = 'script_gpx_button'
63+
aGPX.href = '#'
64+
aGPX.textContent = 'GPX'
65+
aGPX.addEventListener('click', function(ev) {
66+
if (!('ready' in this.dataset)) {
67+
ev.preventDefault()
68+
startDownload('gpx', this)
69+
}
70+
})
71+
aGPX.className = buttonGroup.querySelector('a').className
72+
buttonGroup.appendChild(aGPX)
73+
74+
const aKML = document.createElement('a')
75+
aKML.href = '#'
76+
aKML.textContent = 'KML'
77+
aKML.addEventListener('click', function(ev) {
78+
if (!('ready' in this.dataset)) {
79+
ev.preventDefault()
80+
startDownload('kml', this)
81+
}
82+
})
83+
aKML.className = buttonGroup.querySelector('a').className
84+
buttonGroup.appendChild(aKML)
85+
}
86+
87+
function addDownloadButtons() {
88+
GM.registerMenuCommand('Download gpx', () => startDownload('gpx'));
89+
GM.registerMenuCommand('Download kml', () => startDownload('kml'));
90+
}
91+
92+
function downloadUrl(url, title, ext, button) {
93+
if (button) {
94+
button.href = url
95+
button.style.color = 'green'
96+
button.dataset.ready = 1
97+
button.target = '_blank'
98+
window.setTimeout(() => button.click(), 100)
99+
} else {
100+
document.location.href = url
101+
}
102+
}
103+
104+
105+
function startDownload(key, button) {
106+
const routeId = parseInt(document.location.href.match(/\/r\/(\d+)/)[1]);
107+
cachedRequest({
108+
method: 'GET',
109+
url: `https://www.bikemap.net/api/v5/routes/${routeId}/`,
110+
headers: {
111+
'x-requested-with': 'XMLHttpRequest'
112+
},
113+
onload: function (resp) {
114+
const routeData = JSON.parse(resp.responseText);
115+
downloadUrl(routeData[key], routeData.title, key, button);
116+
},
117+
onerror: function (response) {
118+
window.alert('Error:' + response.status);
119+
}
120+
});
121+
122+
}
123+
124+
function fileName(title, ext) {
125+
let name = title.replace(/[:*?<>/\\,|\u0000]/g, ''); // eslint-disable-line no-control-regex
126+
127+
name = name.trim().replace(/^\.+/, '').replace(/\.+$/, '').trim();
128+
return name + '.' + ext;
129+
}
130+
131+
function cachedRequest(obj) {
132+
if ('data' in obj || obj.method !== 'GET') {
133+
return GM.xmlHttpRequest(obj);
134+
}
135+
136+
const cached = sessionStorage.getItem(obj.url);
137+
138+
if (cached !== null) {
139+
window.setTimeout(function () {
140+
const result = JSON.parse(cached);
141+
obj.onload(result);
142+
}, 1);
143+
} else {
144+
const orgOnload = obj.onload;
145+
146+
obj.onload = function (response) {
147+
const newResponse = {};
148+
149+
for (const key in response) {
150+
newResponse[key] = response[key];
151+
}
152+
153+
newResponse.responseText = '' + response.responseText;
154+
newResponse.cached = true;
155+
156+
if (!('time' in newResponse)) {
157+
newResponse.time = new Date().toJSON();
158+
}
159+
160+
sessionStorage.setItem(obj.url, JSON.stringify(newResponse));
161+
orgOnload.apply(this, [response]);
162+
};
163+
164+
GM.xmlHttpRequest(obj);
165+
}
166+
}
167+
168+
})();
169+

0 commit comments

Comments
 (0)