openvibers/openvibe.meme Commits
Sitemap: an entry's licensed picture as an image entry (SEO-17) (#81)
Changes
@@@ -101,10 +101,12 @@ function createDiscoveryRoutes(ctx) {r.get('/sitemap.xml', async (_req, res) => {const lastmod = siteUpdated();-- const rows = await s.db.many("SELECT slug, updated_at FROM meme_entries WHERE visibility = 'public' ORDER BY slug LIMIT 5000");++ // An entry's licensed picture rides its <url> as an image sitemap entry (SEO-17), so image search can find it.++ const rows = await s.db.many("SELECT slug, updated_at, image_url FROM meme_entries WHERE visibility = 'public' ORDER BY slug LIMIT 5000");const urls = [...pages.map((e) => ({ loc: abs(e.path), ...(lastmod ? { lastmod } : {}), changefreq: e.changefreq, priority: e.priority })),-- ...rows.map((e) => ({ loc: abs(`/m/${e.slug}`), lastmod: dayOf(e.updated_at) || lastmod, changefreq: 'weekly', priority: 0.7 })),++ ...rows.map((e) => ({ loc: abs(`/m/${e.slug}`), lastmod: dayOf(e.updated_at) || lastmod, changefreq: 'weekly', priority: 0.7,++ ...(/^https:\/\//.test(e.image_url || '') ? { images: [e.image_url] } : {}) })),];res.type('application/xml').set('Cache-Control', TEXT).send(seo.sitemapXml(urls));});@@@ -177,9 +177,16 @@ function jsonLd(html) {assert.match(js.headers.get('content-type'), /javascript/);const robots = await t.get('/robots.txt');assert.match(robots.text, /Disallow: \/api\//);++ await t.ctx.entries.createCandidate({++ slug: 'shake-weight', title: 'Shake Weight', kind: 'meme', summary: 'A dumbbell that shakes.', status: 'rising', first_seen_at: now, last_seen_at: now,++ visibility: 'public', citations: [], image_url: 'https://upload.wikimedia.org/wikipedia/commons/0/01/Shake_Weight.jpg', image_credit: 'Someone', image_license: 'CC BY 2.0',++ });const map = await t.get('/sitemap.xml');assert.strictEqual(map.status, 200);assert.ok(map.text.includes('https://openvibe.meme/m/robot-is-offline'));++ // SEO-17: an entry's picture is in its <url>; an entry without one has no image entry.++ assert.match(map.text, /<loc>https:\/\/openvibe\.meme\/m\/shake-weight<\/loc>[^\n]*<image:image><image:loc>https:\/\/upload\.wikimedia\.org\/wikipedia\/commons\/0\/01\/Shake_Weight\.jpg<\/image:loc><\/image:image>/);++ assert.doesNotMatch(map.text, /<loc>https:\/\/openvibe\.meme\/m\/robot-is-offline<\/loc>[^\n]*<image:image>/);const llms = await t.get('/llms.txt');assert.strictEqual(llms.status, 200);assert.ok(llms.text.includes('OpenVibe.Meme'));
@@ -101,10 +101,12 @@ function createDiscoveryRoutes(ctx) {
@@ -101,10 +101,12 @@ function createDiscoveryRoutes(ctx) {
r.get('/sitemap.xml', async (_req, res) => {
r.get('/sitemap.xml', async (_req, res) => {
const lastmod = siteUpdated();
const lastmod = siteUpdated();
- const rows = await s.db.many("SELECT slug, updated_at FROM meme_entries WHERE visibility = 'public' ORDER BY slug LIMIT 5000");
+ // An entry's licensed picture rides its <url> as an image sitemap entry (SEO-17), so image search can find it.
+ const rows = await s.db.many("SELECT slug, updated_at, image_url FROM meme_entries WHERE visibility = 'public' ORDER BY slug LIMIT 5000");
const urls = [
const urls = [
...pages.map((e) => ({ loc: abs(e.path), ...(lastmod ? { lastmod } : {}), changefreq: e.changefreq, priority: e.priority })),
...pages.map((e) => ({ loc: abs(e.path), ...(lastmod ? { lastmod } : {}), changefreq: e.changefreq, priority: e.priority })),
- ...rows.map((e) => ({ loc: abs(`/m/${e.slug}`), lastmod: dayOf(e.updated_at) || lastmod, changefreq: 'weekly', priority: 0.7 })),
+ ...rows.map((e) => ({ loc: abs(`/m/${e.slug}`), lastmod: dayOf(e.updated_at) || lastmod, changefreq: 'weekly', priority: 0.7,
+ ...(/^https:\/\//.test(e.image_url || '') ? { images: [e.image_url] } : {}) })),
];
];
res.type('application/xml').set('Cache-Control', TEXT).send(seo.sitemapXml(urls));
res.type('application/xml').set('Cache-Control', TEXT).send(seo.sitemapXml(urls));
});
});
@@ -177,9 +177,16 @@ function jsonLd(html) {
@@ -177,9 +177,16 @@ function jsonLd(html) {
assert.match(js.headers.get('content-type'), /javascript/);
assert.match(js.headers.get('content-type'), /javascript/);
const robots = await t.get('/robots.txt');
const robots = await t.get('/robots.txt');
assert.match(robots.text, /Disallow: \/api\//);
assert.match(robots.text, /Disallow: \/api\//);
+ await t.ctx.entries.createCandidate({
+ slug: 'shake-weight', title: 'Shake Weight', kind: 'meme', summary: 'A dumbbell that shakes.', status: 'rising', first_seen_at: now, last_seen_at: now,
+ visibility: 'public', citations: [], image_url: 'https://upload.wikimedia.org/wikipedia/commons/0/01/Shake_Weight.jpg', image_credit: 'Someone', image_license: 'CC BY 2.0',
+ });
const map = await t.get('/sitemap.xml');
const map = await t.get('/sitemap.xml');
assert.strictEqual(map.status, 200);
assert.strictEqual(map.status, 200);
assert.ok(map.text.includes('https://openvibe.meme/m/robot-is-offline'));
assert.ok(map.text.includes('https://openvibe.meme/m/robot-is-offline'));
+ // SEO-17: an entry's picture is in its <url>; an entry without one has no image entry.
+ assert.match(map.text, /<loc>https:\/\/openvibe\.meme\/m\/shake-weight<\/loc>[^\n]*<image:image><image:loc>https:\/\/upload\.wikimedia\.org\/wikipedia\/commons\/0\/01\/Shake_Weight\.jpg<\/image:loc><\/image:image>/);
+ assert.doesNotMatch(map.text, /<loc>https:\/\/openvibe\.meme\/m\/robot-is-offline<\/loc>[^\n]*<image:image>/);
const llms = await t.get('/llms.txt');
const llms = await t.get('/llms.txt');
assert.strictEqual(llms.status, 200);
assert.strictEqual(llms.status, 200);
assert.ok(llms.text.includes('OpenVibe.Meme'));
assert.ok(llms.text.includes('OpenVibe.Meme'));