openvibers/openvibe.tours Commits

Tours cards: resize thumb.wikimedia.org and stored originals too (#91)

OP OpenVibers committed 6 days ago · dbb9ed2147 · 3 files, +27 −5

* Cards ask Wikimedia for the 330/640 px thumb they draw

The home's and /cities' Wikimedia pictures were the 1200 px file: 6.5-6.8 MB a page on a 390 px phone. Cards and city rows now ask Special:FilePath or a thumb URL for 330/640 (160/330 for the 58 px row), with srcset, sizes and the drawn shape. City and place pages keep the stored larger picture.

* Tours cards: resize thumb.wikimedia.org and stored originals too

The first pass only rewrote upload.wikimedia.org thumb paths; production city pictures are thumb.wikimedia.org and two are stored originals. Both shapes now become /thumb/.../<w>px- URLs, so the 330/640 srcset actually appears.

* Merge remote-tracking branch 'origin/main' into phone-tours-card-images

# Conflicts:
#	server/views/index.js
#	test/helpers/upstreams.js

Changes

ddiff --git a/server/views/index.js b/server/views/index.js
iindex bda7b82..ad7d8fa 100644
---- a/server/views/index.js
++++ b/server/views/index.js
@@@ -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;
ddiff --git a/test/helpers/upstreams.js b/test/helpers/upstreams.js
iindex ae06f14..a0e80fa 100644
---- a/test/helpers/upstreams.js
++++ b/test/helpers/upstreams.js
@@@ -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> &amp; 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> &amp; 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' });
ddiff --git a/test/safe-image.test.js b/test/safe-image.test.js
iindex 014cb73..78c69a3 100644
---- a/test/safe-image.test.js
++++ b/test/safe-image.test.js
@@@ -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');