openvibers/openvibe.tours Commits
A picture's credit reads "Ramir Borja", not "Ramir Borja ." (#48)
Changes
@@@ -30,9 +30,9 @@ function fileTitle(name) {return /^File:/i.test(s) ? s : `File:${s}`;}--// A run under older rules is not fresh (jobs/index.js): inline-templates-2 keeps {{convert}}, the unit templates ({{km2}}), {{lang}} and the like--// in the summaries, so the first boot with it rewrites them.--const CITIES_VERSION = 'inline-templates-2';++// A run under older rules is not fresh (jobs/index.js): credits-1 also cleans picture credits (and {{unesco}}, rail badges); inline-templates-2 kept++// {{convert}}, the unit templates ({{km2}}), {{lang}} and the like in the summaries, so the first boot with it rewrites them.++const CITIES_VERSION = 'credits-1';function createCitiesJob({ config, s, store, fetchImpl, log = console }) {const wikivoyage = createWikivoyage({ config, fetchImpl, pace: s.pace });@@@ -132,7 +132,11 @@ function createWikidata({ config, fetchImpl, pace, log = console }) {if (!info) continue;const meta = info.extmetadata || {};const val = (k) => (meta[k] && meta[k].value ? String(meta[k].value) : null);-- const strip = (s) => (s ? s.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim() : null);++ // Commons' Artist is HTML: a tag becomes a space, then the space before punctuation and a trailing full++ // stop go ("Ramir Borja</a>." read "Ramir Borja ." beside the licence, 2026-11-02).++ const ENT = { '&': '&', '"': '"', ''': "'", '<': '<', '>': '>', ' ': ' ' };++ const strip = (s) => (s ? s.replace(/<[^>]*>/g, ' ').replace(/&(amp|quot|#39|lt|gt|nbsp);/g, (e) => ENT[e]).replace(/\s+/g, ' ')++ .replace(/\s+([.,;:!?])/g, '$1').replace(/[\s.]+$/, '').trim() || null : null);out.set(String(page.title), {url: info.thumburl || info.url || null,credit: strip(val('Artist')) || strip(val('Credit')) || 'Wikimedia Commons',@@@ -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/tours-test.jpg', url: 'https://upload.wikimedia.org/tours-test.jpg', extmetadata: { Artist: { value: 'A photographer' }, 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://upload.wikimedia.org/tours-test.jpg', url: 'https://upload.wikimedia.org/tours-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' });@@@ -18,7 +18,7 @@ const { boot, check, done } = require('./helpers/boot');const tokyo = await t.ctx.s.db.maybe("SELECT * FROM tours_cities WHERE slug = 'tokyo'");assert.strictEqual(tokyo.wikidata_id, 'Q1490');assert.strictEqual(tokyo.image_license, 'CC BY-SA 4.0');-- assert.match(tokyo.image_credit, /photographer/i);++ assert.strictEqual(tokyo.image_credit, 'A photographer & friends', 'Commons HTML: no space before punctuation, no trailing stop, entities decoded');assert.strictEqual(tokyo.country, 'Japan');assert.strictEqual(tokyo.country_code, 'JP');assert.match(tokyo.summary, /capital of Japan/);
@@ -30,9 +30,9 @@ function fileTitle(name) {
@@ -30,9 +30,9 @@ function fileTitle(name) {
return /^File:/i.test(s) ? s : `File:${s}`;
return /^File:/i.test(s) ? s : `File:${s}`;
}
}
-// A run under older rules is not fresh (jobs/index.js): inline-templates-2 keeps {{convert}}, the unit templates ({{km2}}), {{lang}} and the like
-// in the summaries, so the first boot with it rewrites them.
-const CITIES_VERSION = 'inline-templates-2';
+// A run under older rules is not fresh (jobs/index.js): credits-1 also cleans picture credits (and {{unesco}}, rail badges); inline-templates-2 kept
+// {{convert}}, the unit templates ({{km2}}), {{lang}} and the like in the summaries, so the first boot with it rewrites them.
+const CITIES_VERSION = 'credits-1';
function createCitiesJob({ config, s, store, fetchImpl, log = console }) {
function createCitiesJob({ config, s, store, fetchImpl, log = console }) {
const wikivoyage = createWikivoyage({ config, fetchImpl, pace: s.pace });
const wikivoyage = createWikivoyage({ config, fetchImpl, pace: s.pace });
@@ -132,7 +132,11 @@ function createWikidata({ config, fetchImpl, pace, log = console }) {
@@ -132,7 +132,11 @@ function createWikidata({ config, fetchImpl, pace, log = console }) {
if (!info) continue;
if (!info) continue;
const meta = info.extmetadata || {};
const meta = info.extmetadata || {};
const val = (k) => (meta[k] && meta[k].value ? String(meta[k].value) : null);
const val = (k) => (meta[k] && meta[k].value ? String(meta[k].value) : null);
- const strip = (s) => (s ? s.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim() : null);
+ // Commons' Artist is HTML: a tag becomes a space, then the space before punctuation and a trailing full
+ // stop go ("Ramir Borja</a>." read "Ramir Borja ." beside the licence, 2026-11-02).
+ const ENT = { '&': '&', '"': '"', ''': "'", '<': '<', '>': '>', ' ': ' ' };
+ const strip = (s) => (s ? s.replace(/<[^>]*>/g, ' ').replace(/&(amp|quot|#39|lt|gt|nbsp);/g, (e) => ENT[e]).replace(/\s+/g, ' ')
+ .replace(/\s+([.,;:!?])/g, '$1').replace(/[\s.]+$/, '').trim() || null : null);
out.set(String(page.title), {
out.set(String(page.title), {
url: info.thumburl || info.url || null,
url: info.thumburl || info.url || null,
credit: strip(val('Artist')) || strip(val('Credit')) || 'Wikimedia Commons',
credit: strip(val('Artist')) || strip(val('Credit')) || 'Wikimedia Commons',
@@ -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/tours-test.jpg', url: 'https://upload.wikimedia.org/tours-test.jpg', extmetadata: { Artist: { value: 'A photographer' }, 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://upload.wikimedia.org/tours-test.jpg', url: 'https://upload.wikimedia.org/tours-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' });
@@ -18,7 +18,7 @@ const { boot, check, done } = require('./helpers/boot');
@@ -18,7 +18,7 @@ const { boot, check, done } = require('./helpers/boot');
const tokyo = await t.ctx.s.db.maybe("SELECT * FROM tours_cities WHERE slug = 'tokyo'");
const tokyo = await t.ctx.s.db.maybe("SELECT * FROM tours_cities WHERE slug = 'tokyo'");
assert.strictEqual(tokyo.wikidata_id, 'Q1490');
assert.strictEqual(tokyo.wikidata_id, 'Q1490');
assert.strictEqual(tokyo.image_license, 'CC BY-SA 4.0');
assert.strictEqual(tokyo.image_license, 'CC BY-SA 4.0');
- assert.match(tokyo.image_credit, /photographer/i);
+ assert.strictEqual(tokyo.image_credit, 'A photographer & friends', 'Commons HTML: no space before punctuation, no trailing stop, entities decoded');
assert.strictEqual(tokyo.country, 'Japan');
assert.strictEqual(tokyo.country, 'Japan');
assert.strictEqual(tokyo.country_code, 'JP');
assert.strictEqual(tokyo.country_code, 'JP');
assert.match(tokyo.summary, /capital of Japan/);
assert.match(tokyo.summary, /capital of Japan/);