openvibers/openvibe.tours Commits
Tours cards: resize thumb.wikimedia.org and stored originals too (#91)
Changes
@@@ -37,8 +37,8 @@ const safeImage = (v) => {} catch { return null; }};// Wikimedia answers a width through the Special:FilePath redirect (OSM/place pictures) or the--// "<n>px-" segment of a thumb URL (Commons' iiurlwidth, city pictures). Any other picture is--// left as it is.++// "<n>px-" segment of a thumb URL (Commons' iiurlwidth, city pictures). A stored original can be++// turned into a thumb path. Any other picture is left as it is.function imageWidth(u, w) {if (!u) return null;try {@@@ -47,8 +47,17 @@ function imageWidth(u, w) {url.searchParams.set('width', String(w));return url.toString();}-- if (/(^|\.)upload\.wikimedia\.org$/i.test(url.hostname) && /\/thumb\//.test(url.pathname)) {-- return u.replace(/(\/)(\d+)px-/, `/$1${w}px-`);++ if (/(^|\.)wikimedia\.org$/i.test(url.hostname)) {++ const thumb = url.pathname.match(/^(.*\/thumb\/.*\/)(\d+)px-([^/]+)$/);++ if (thumb) {++ url.pathname = `${thumb[1]}${w}px-${thumb[3]}`;++ return url.toString();++ }++ const original = url.pathname.match(/^\/(wikipedia\/[^/]+)\/([0-9a-f])\/([0-9a-f]{2})\/([^/]+)$/i);++ if (original) {++ url.pathname = `/${original[1]}/thumb/${original[2]}/${original[3]}/${original[4]}/${w}px-${original[4]}`;++ return url.toString();++ }}} catch { /* not a URL: use it unchanged */ }return null;@@@ -182,7 +182,7 @@ async function startUpstreams() {}if (action === 'query' && url.searchParams.get('prop') === 'imageinfo') {const titles = String(url.searchParams.get('titles') || '').split('|');-- const pages = titles.map((t) => ({ title: t, imageinfo: [{ thumburl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Tokyo_test.jpg/1200px-Tokyo_test.jpg', url: 'https://upload.wikimedia.org/wikipedia/commons/a/ab/Tokyo_test.jpg', extmetadata: { Artist: { value: '<a href="https://commons.wikimedia.org/wiki/User:A">A photographer</a> & friends.' }, LicenseShortName: { value: 'CC BY-SA 4.0' }, LicenseUrl: { value: 'https://creativecommons.org/licenses/by-sa/4.0/' } } }] }));++ const pages = titles.map((t) => ({ title: t, imageinfo: [{ thumburl: 'https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Tokyo_test.jpg/1280px-Tokyo_test.jpg', url: 'https://upload.wikimedia.org/wikipedia/commons/a/ab/Tokyo_test.jpg', extmetadata: { Artist: { value: '<a href="https://commons.wikimedia.org/wiki/User:A">A photographer</a> & friends.' }, LicenseShortName: { value: 'CC BY-SA 4.0' }, LicenseUrl: { value: 'https://creativecommons.org/licenses/by-sa/4.0/' } } }] }));return json(200, { query: { pages } });}return json(400, { error: 'unknown action' });@@@ -12,6 +12,19 @@ const hosts = /const IMAGE_HOSTS = [^\n]+\n/.exec(src);const img = /const safeImage = \(v\) => \{[\s\S]*?\n\};\n/.exec(src);assert.ok(http && hosts && img, 'views has safeHttp, IMAGE_HOSTS and safeImage');const safeImage = new Function(`${http[0]}${hosts[0]}${img[0]}; return safeImage;`)();++const iw = /function imageWidth\(u, w\) \{[\s\S]*?\n\}\n/.exec(src);++assert.ok(iw, 'views has imageWidth');++const imageWidth = new Function(`${iw[0]}; return imageWidth;`)();++++// Cards ask for the width they draw on both URL shapes Wikimedia's imageinfo returns, and on a++// stored original; no other host is rewritten.++const thumbUrl = 'https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Foo.jpg/1280px-Foo.jpg?utm_source=commons.wikimedia.org';++assert.strictEqual(imageWidth(thumbUrl, 330), 'https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Foo.jpg/330px-Foo.jpg?utm_source=commons.wikimedia.org', 'a thumb URL changes its width');++assert.strictEqual(imageWidth('https://upload.wikimedia.org/wikipedia/commons/5/51/Foo.png?utm_source=x', 640), 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/51/Foo.png/640px-Foo.png?utm_source=x', 'a stored original becomes a thumb');++assert.strictEqual(imageWidth('https://commons.wikimedia.org/wiki/Special:FilePath/Foo.jpg?width=1000', 330), 'https://commons.wikimedia.org/wiki/Special:FilePath/Foo.jpg?width=330', 'Special:FilePath takes a width');++assert.strictEqual(imageWidth('https://openvibe.media/t/x.jpg', 330), null, 'a non-Wikimedia picture is not rewritten');++assert.strictEqual(imageWidth('https://upload.wikimedia.org/wikipedia/commons/a/ab/Foo.jpg', 330), 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Foo.jpg/330px-Foo.jpg', 'a two-letter hash prefix');++assert.strictEqual(imageWidth(null, 330), null);assert.strictEqual(safeImage('https://photos.app.goo.gl/A7PbTNW1RbBbZUrTA'), null, 'a share link is not a picture');assert.ok(safeImage('https://upload.wikimedia.org/wikipedia/commons/a/ab/Hachiko.jpg'), 'Commons uploads');
@@ -37,8 +37,8 @@ const safeImage = (v) => {
@@ -37,8 +37,8 @@ const safeImage = (v) => {
} catch { return null; }
} catch { return null; }
};
};
// Wikimedia answers a width through the Special:FilePath redirect (OSM/place pictures) or the
// Wikimedia answers a width through the Special:FilePath redirect (OSM/place pictures) or the
-// "<n>px-" segment of a thumb URL (Commons' iiurlwidth, city pictures). Any other picture is
-// left as it is.
+// "<n>px-" segment of a thumb URL (Commons' iiurlwidth, city pictures). A stored original can be
+// turned into a thumb path. Any other picture is left as it is.
function imageWidth(u, w) {
function imageWidth(u, w) {
if (!u) return null;
if (!u) return null;
try {
try {
@@ -47,8 +47,17 @@ function imageWidth(u, w) {
@@ -47,8 +47,17 @@ function imageWidth(u, w) {
url.searchParams.set('width', String(w));
url.searchParams.set('width', String(w));
return url.toString();
return url.toString();
}
}
- if (/(^|\.)upload\.wikimedia\.org$/i.test(url.hostname) && /\/thumb\//.test(url.pathname)) {
- return u.replace(/(\/)(\d+)px-/, `/$1${w}px-`);
+ if (/(^|\.)wikimedia\.org$/i.test(url.hostname)) {
+ const thumb = url.pathname.match(/^(.*\/thumb\/.*\/)(\d+)px-([^/]+)$/);
+ if (thumb) {
+ url.pathname = `${thumb[1]}${w}px-${thumb[3]}`;
+ return url.toString();
+ }
+ const original = url.pathname.match(/^\/(wikipedia\/[^/]+)\/([0-9a-f])\/([0-9a-f]{2})\/([^/]+)$/i);
+ if (original) {
+ url.pathname = `/${original[1]}/thumb/${original[2]}/${original[3]}/${original[4]}/${w}px-${original[4]}`;
+ return url.toString();
+ }
}
}
} catch { /* not a URL: use it unchanged */ }
} catch { /* not a URL: use it unchanged */ }
return null;
return null;
@@ -182,7 +182,7 @@ async function startUpstreams() {
@@ -182,7 +182,7 @@ async function startUpstreams() {
}
}
if (action === 'query' && url.searchParams.get('prop') === 'imageinfo') {
if (action === 'query' && url.searchParams.get('prop') === 'imageinfo') {
const titles = String(url.searchParams.get('titles') || '').split('|');
const titles = String(url.searchParams.get('titles') || '').split('|');
- const pages = titles.map((t) => ({ title: t, imageinfo: [{ thumburl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Tokyo_test.jpg/1200px-Tokyo_test.jpg', url: 'https://upload.wikimedia.org/wikipedia/commons/a/ab/Tokyo_test.jpg', extmetadata: { Artist: { value: '<a href="https://commons.wikimedia.org/wiki/User:A">A photographer</a> & friends.' }, LicenseShortName: { value: 'CC BY-SA 4.0' }, LicenseUrl: { value: 'https://creativecommons.org/licenses/by-sa/4.0/' } } }] }));
+ const pages = titles.map((t) => ({ title: t, imageinfo: [{ thumburl: 'https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Tokyo_test.jpg/1280px-Tokyo_test.jpg', url: 'https://upload.wikimedia.org/wikipedia/commons/a/ab/Tokyo_test.jpg', extmetadata: { Artist: { value: '<a href="https://commons.wikimedia.org/wiki/User:A">A photographer</a> & friends.' }, LicenseShortName: { value: 'CC BY-SA 4.0' }, LicenseUrl: { value: 'https://creativecommons.org/licenses/by-sa/4.0/' } } }] }));
return json(200, { query: { pages } });
return json(200, { query: { pages } });
}
}
return json(400, { error: 'unknown action' });
return json(400, { error: 'unknown action' });
@@ -12,6 +12,19 @@ const hosts = /const IMAGE_HOSTS = [^\n]+\n/.exec(src);
@@ -12,6 +12,19 @@ const hosts = /const IMAGE_HOSTS = [^\n]+\n/.exec(src);
const img = /const safeImage = \(v\) => \{[\s\S]*?\n\};\n/.exec(src);
const img = /const safeImage = \(v\) => \{[\s\S]*?\n\};\n/.exec(src);
assert.ok(http && hosts && img, 'views has safeHttp, IMAGE_HOSTS and safeImage');
assert.ok(http && hosts && img, 'views has safeHttp, IMAGE_HOSTS and safeImage');
const safeImage = new Function(`${http[0]}${hosts[0]}${img[0]}; return safeImage;`)();
const safeImage = new Function(`${http[0]}${hosts[0]}${img[0]}; return safeImage;`)();
+const iw = /function imageWidth\(u, w\) \{[\s\S]*?\n\}\n/.exec(src);
+assert.ok(iw, 'views has imageWidth');
+const imageWidth = new Function(`${iw[0]}; return imageWidth;`)();
+
+// Cards ask for the width they draw on both URL shapes Wikimedia's imageinfo returns, and on a
+// stored original; no other host is rewritten.
+const thumbUrl = 'https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Foo.jpg/1280px-Foo.jpg?utm_source=commons.wikimedia.org';
+assert.strictEqual(imageWidth(thumbUrl, 330), 'https://thumb.wikimedia.org/wikipedia/commons/thumb/a/ab/Foo.jpg/330px-Foo.jpg?utm_source=commons.wikimedia.org', 'a thumb URL changes its width');
+assert.strictEqual(imageWidth('https://upload.wikimedia.org/wikipedia/commons/5/51/Foo.png?utm_source=x', 640), 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/51/Foo.png/640px-Foo.png?utm_source=x', 'a stored original becomes a thumb');
+assert.strictEqual(imageWidth('https://commons.wikimedia.org/wiki/Special:FilePath/Foo.jpg?width=1000', 330), 'https://commons.wikimedia.org/wiki/Special:FilePath/Foo.jpg?width=330', 'Special:FilePath takes a width');
+assert.strictEqual(imageWidth('https://openvibe.media/t/x.jpg', 330), null, 'a non-Wikimedia picture is not rewritten');
+assert.strictEqual(imageWidth('https://upload.wikimedia.org/wikipedia/commons/a/ab/Foo.jpg', 330), 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Foo.jpg/330px-Foo.jpg', 'a two-letter hash prefix');
+assert.strictEqual(imageWidth(null, 330), null);
assert.strictEqual(safeImage('https://photos.app.goo.gl/A7PbTNW1RbBbZUrTA'), null, 'a share link is not a picture');
assert.strictEqual(safeImage('https://photos.app.goo.gl/A7PbTNW1RbBbZUrTA'), null, 'a share link is not a picture');
assert.ok(safeImage('https://upload.wikimedia.org/wikipedia/commons/a/ab/Hachiko.jpg'), 'Commons uploads');
assert.ok(safeImage('https://upload.wikimedia.org/wikipedia/commons/a/ab/Hachiko.jpg'), 'Commons uploads');