GIFs from Wikimedia Commons (MediaWiki search) with no key; shared places link to Apple Maps
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
584a95753d
commit
651d0def2f
9 files changed
+43
-28
No files matched your search
@@ -1,7 +1,7 @@
|
||||
// Family Chat extras: GIFs (kept family-friendly, fetched through the hub, kept in the chat) and sharing a place (a search, and coordinates that are checked).
|
||||
const http = require('http'), assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path');
|
||||
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-chatx-'));
|
||||
const GIF = Buffer.from('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 'base64'); let asked = [], tenorQ = [], giphyQ = [];
|
||||
const GIF = Buffer.from('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 'base64'); let commonsPages = null, asked = [], tenorQ = [], giphyQ = [], wikiQ = [];
|
||||
const media = http.createServer((req, res) => { asked.push(req.url); if (req.url.startsWith('/big')) { res.writeHead(200, { 'content-type': 'image/gif' }); return res.end(Buffer.alloc(9 * 1024 * 1024)); } if (req.url.startsWith('/html')) { res.writeHead(200, { 'content-type': 'text/html' }); return res.end('<b>x</b>'); } res.writeHead(200, { 'content-type': 'image/gif' }); res.end(GIF); }).listen(0);
|
||||
const svc = http.createServer((req, res) => {
|
||||
const u = new URL(req.url, 'http://x'), M = 'http://127.0.0.1:' + media.address().port; res.setHeader('content-type', 'application/json');
|
||||
@@ -11,9 +11,12 @@ const svc = http.createServer((req, res) => {
|
||||
{ content_description: 'party', tags: ['topless', 'fun'], media_formats: { tinygif: { url: M + '/t3.gif', dims: [100, 80] }, gif: { url: M + '/g3.gif', dims: [300, 240] } } },
|
||||
{ content_description: 'elsewhere', tags: [], media_formats: { tinygif: { url: 'https://evil.example/t4.gif', dims: [1, 1] }, gif: { url: 'https://evil.example/g4.gif', dims: [1, 1] } } }] })); }
|
||||
if (u.pathname.startsWith('/v1/gifs/')) { giphyQ.push(u.pathname + u.search); return res.end(JSON.stringify({ data: [{ title: 'cat', rating: 'g', images: { fixed_width_small: { url: M + '/s1.gif', width: '100', height: '80' }, fixed_width: { url: M + '/f1.gif' } } }, { title: 'cat 2', rating: 'pg-13', images: { fixed_width_small: { url: M + '/s2.gif', width: '100', height: '80' }, fixed_width: { url: M + '/f2.gif' } } }] })); }
|
||||
if (u.pathname === '/w/api.php') { wikiQ.push(u.search); const M2 = M, mk = (i, title, mime, desc, cats, thumb) => ({ pageid: i, index: i, title: 'File:' + title, imageinfo: [{ mime, thumburl: thumb === null ? '' : (thumb || M2 + '/commons-' + i + '.gif'), thumbwidth: 320, thumbheight: 240, extmetadata: { ImageDescription: { value: desc }, Categories: { value: cats } } }] });
|
||||
const pages = [mk(1, 'Cat walking.gif', 'image/gif', 'A <b>cat</b> walking', 'Cats|Animation'), mk(2, 'Nude study.gif', 'image/gif', 'Study', 'Nudity in art'), mk(3, 'Cat.png', 'image/png', 'still', 'Cats'), mk(4, 'Dancing robot.gif', 'image/gif', 'A robot', 'Robots'), mk(5, 'Clinic.gif', 'image/gif', 'Medical animation of the female genitalia', 'Medicine'), mk(6, 'No thumb.gif', 'image/gif', 'x', 'Cats', null)];
|
||||
commonsPages = pages; const cont = pages.length ? { continue: { gsroffset: 24 } } : {}; return res.end(JSON.stringify({ ...cont, query: { pages: Object.fromEntries(pages.map(p => [p.pageid, p])) } })); }
|
||||
if (u.pathname === '/search') { return res.end(JSON.stringify([{ name: 'Central Park', display_name: 'Central Park, Manhattan, New York, United States', lat: '40.78', lon: '-73.96' }, { name: 'Bad', display_name: 'Bad', lat: 'x', lon: 'y' }])); }
|
||||
res.statusCode = 404; res.end('{}'); }).listen(0, async () => {
|
||||
process.env.GIF_MEDIA_HOSTS = '127.0.0.1:' + media.address().port; process.env.TENOR_URL = process.env.GIPHY_URL = process.env.NOMINATIM_URL = 'http://127.0.0.1:' + svc.address().port;
|
||||
process.env.GIF_MEDIA_HOSTS = '127.0.0.1:' + media.address().port; process.env.WIKIMEDIA_URL = process.env.TENOR_URL = process.env.GIPHY_URL = process.env.NOMINATIM_URL = 'http://127.0.0.1:' + svc.address().port;
|
||||
const users = require('../users'), chat = require('../chat'), gifs = require('../gifs');
|
||||
const P = { a: users.create({ name: 'Ann', role: 'admin', handle: 'YQ' }), b: users.create({ name: 'Bob', role: 'user', tabs: ['chat'], handle: 'Yg' }), c: users.create({ name: 'Cy', role: 'user', tabs: ['chat'], handle: 'Yw' }) };
|
||||
const hub = http.createServer((req, res) => users.runAs(P[req.headers['x-user'] || 'a'], () => chat.handle(req, res))).listen(0, async () => {
|
||||
@@ -22,7 +25,7 @@ const svc = http.createServer((req, res) => {
|
||||
// the filter's words
|
||||
for (const bad of ['sexy dance', 'Boobs', 'naked man', 'p0rn'.replace('0', 'o'), 'bikini beach', 'F-U-C-K'.replace(/-/g, '')]) assert.equal(gifs.unsafe(bad), true, bad + ' is refused'); for (const ok of ['happy dance', 'class', 'assistant', 'Scunthorpe', 'cat', 'thanks', 'high five', 'book club', 'pass the butter']) assert.equal(gifs.unsafe(ok), false, ok + ' is fine');
|
||||
// not set up
|
||||
let r = await call('/gif/status'); assert.deepEqual(await j(r), { enabled: false, provider: '' }); assert.equal((await call('/gif?q=cat')).status, 503);
|
||||
process.env.GIF_PROVIDER = 'off'; let r = await call('/gif/status'); assert.deepEqual(await j(r), { enabled: false, provider: '' }); assert.equal((await call('/gif?q=cat')).status, 503); delete process.env.GIF_PROVIDER;
|
||||
// Tenor: always the strictest setting, and what comes back is filtered by words and by where it is hosted
|
||||
process.env.TENOR_API_KEY = 'k1'; assert.deepEqual(await j(await call('/gif/status')), { enabled: true, provider: 'tenor' });
|
||||
r = await j(await call('/gif?q=dance')); assert.match(tenorQ[0], /contentfilter=high/); assert.match(tenorQ[0], /q=dance/); assert.deepEqual(r.items.map(x => x.title), ['happy dance'], 'a title or a tag with a bad word, and a picture from another site, are dropped'); assert.equal(r.next, 'n2');
|
||||
@@ -35,11 +38,17 @@ const svc = http.createServer((req, res) => {
|
||||
assert.equal((await post('/gif/send', { conv: dm.id, url: 'http://127.0.0.1:' + media.address().port + '/never-listed.gif' })).status, 400, 'only listed GIFs can be sent');
|
||||
// size and type limits
|
||||
{ const M = 'http://127.0.0.1:' + media.address().port; gifs.remember([M + '/big.gif', M + '/html.gif']); assert.equal((await post('/gif/send', { conv: dm.id, url: M + '/big.gif' })).status, 413, 'a huge GIF is refused'); assert.equal((await post('/gif/send', { conv: dm.id, url: M + '/html.gif' })).status, 502, 'something that is not a picture is refused'); }
|
||||
// Wikimedia Commons (MediaWiki's search): no key needed, the default when there is no Tenor or GIPHY key; animated GIFs only, and files with risky titles, descriptions or categories are dropped
|
||||
delete process.env.TENOR_API_KEY; delete process.env.GIPHY_API_KEY; assert.deepEqual(await j(await call('/gif/status')), { enabled: true, provider: 'wikimedia' }, 'with no key, Commons is used');
|
||||
r = await j(await call('/gif?q=cats')); assert.match(wikiQ[0], /filemime%3Aimage%2Fgif/); assert.match(wikiQ[0], /gsrsearch=cats/); assert.deepEqual(r.items.map(x => x.title), ['Cat walking', 'Dancing robot'], 'a risky category, a risky description, a still picture and one with no thumbnail are dropped, in search order'); assert.equal(r.next, '24'); assert.ok(!r.items.some(x => /Bikini|Nude/i.test(x.title)));
|
||||
await call('/gif'); assert.match(wikiQ[1], /gsrsearch=animal/, 'with no words, something to look at'); r = await j(await call('/gif?q=cats&pos=24')); assert.match(wikiQ[2], /gsroffset=24/); assert.equal((await call('/gif?q=naked')).status, 400, 'the same word list applies');
|
||||
{ const M = 'http://127.0.0.1:' + media.address().port; assert.equal((await post('/gif/send', { conv: (await j(await post('/new', { type: 'direct', with: P.b.id }))).conv.id, url: M + '/commons-1.gif', title: 'Cat walking' })).status, 200, 'a Commons GIF is kept in the chat too'); }
|
||||
process.env.TENOR_API_KEY = 'k1'; process.env.GIF_PROVIDER = 'wikimedia'; assert.equal((await j(await call('/gif/status'))).provider, 'wikimedia', 'GIF_PROVIDER picks one even when keys exist'); delete process.env.GIF_PROVIDER; delete process.env.TENOR_API_KEY;
|
||||
// Giphy: G only, and nothing above it slips through
|
||||
delete process.env.TENOR_API_KEY; process.env.GIPHY_API_KEY = 'k2'; r = await j(await call('/gif?q=cat')); assert.match(giphyQ[0], /rating=g/); assert.match(giphyQ[0], /api_key=k2/); assert.deepEqual(r.items.map(x => x.title), ['cat'], 'a result rated above G is dropped even if the service sent it');
|
||||
// a place: search through the hub, and a share with checked coordinates
|
||||
assert.equal((await call('/places?q=ab')).status, 400); r = await j(await call('/places?q=central park')); assert.deepEqual(r.items, [{ title: 'Central Park', sub: 'Manhattan, New York, United States', lat: 40.78, lng: -73.96 }], 'bad rows are dropped');
|
||||
r = await post('/send', { conv: dm.id, share: { kind: 'place', title: 'Central Park', sub: 'New York', lat: 40.78, lng: -73.96, link: 'https://evil.example/' } }); assert.equal(r.status, 200); const sh = (await j(r)).msg.share; assert.equal(sh.link, 'https://www.openstreetmap.org/?mlat=40.78&mlon=-73.96#map=17/40.78/-73.96', 'the link is made by the hub, not taken from the sender'); assert.equal(sh.kind, 'place');
|
||||
r = await post('/send', { conv: dm.id, share: { kind: 'place', title: 'Central Park', sub: 'New York', lat: 40.78, lng: -73.96, link: 'https://evil.example/' } }); assert.equal(r.status, 200); const sh = (await j(r)).msg.share; assert.equal(sh.link, 'https://maps.apple.com/?ll=40.78,-73.96&q=Central%20Park', 'the Apple Maps link is made by the hub, not taken from the sender'); assert.equal(sh.kind, 'place');
|
||||
for (const bad of [{ lat: 91, lng: 0 }, { lat: 0, lng: 181 }, { lat: 'x', lng: 1 }, { lat: '', lng: '' }, {}]) assert.equal((await post('/send', { conv: dm.id, share: { kind: 'place', title: 'x', ...bad } })).status, 400, JSON.stringify(bad) + ' is not a place');
|
||||
console.log('chat extras ok');
|
||||
} catch (e) { console.error(e); process.exitCode = 1; }
|
||||
|
||||
Reference in new issue
Block a user