Let new accounts send picker GIFs; allow klipy.com on the link allowlist

This commit is contained in:
ancsemi committed 2026-10-22 18:44:53 -04:00
1 parent 86041b72e7
commit fba21ccf89
3 files changed
+67 -2

No files matched your search

+9 -1
View File
@@ -142,6 +142,9 @@ const extractUrls = rules.extractUrls;
// Content check
// ══════════════════════════════════════════════════════════════════════
// The image hosts the GIF picker's services send from (server.js fetchGifs).
const GIF_HOSTS = ['giphy.com', 'tenor.com', 'klipy.com'];
// ctx: { userId, isAdmin, effectiveLevel, createdAt, surface, markdown }
// markdown: the text is a chat message, so bare addresses inside code do not count.
// surface is one of 'message' | 'edit' | 'dm' | 'profile' | 'channel'.
@@ -195,8 +198,13 @@ function checkText(text, ctx = {}) {
// Independent of the allowlist and deliberately blunt. The register ->
// post-link -> get-banned -> re-register loop is the pattern this exists
// to break, and it does not care which domain was used.
// GIFs from the GIF picker are links too, to the GIF services' own image
// hosts. A new member trying the picker was struck, then muted (the wait is
// for links people paste, not for GIFs), so a message whose only links are
// those is let through. The domain rules below still apply to it.
const gifOnly = links.every(l => GIF_HOSTS.some(h => l.host === h || String(l.host).endsWith('.' + h)));
const minHours = parseInt(s.automod_link_min_account_hours, 10);
if (Number.isFinite(minHours) && minHours > 0 && ctx.createdAt) {
if (!gifOnly && Number.isFinite(minHours) && minHours > 0 && ctx.createdAt) {
const ageMs = Date.now() - new Date(String(ctx.createdAt).replace(' ', 'T') + 'Z').getTime();
if (Number.isFinite(ageMs) && ageMs < minHours * 3600 * 1000) {
const hoursLeft = Math.max(1, Math.ceil((minHours * 3600 * 1000 - ageMs) / 3600000));
+14 -1
View File
@@ -589,7 +589,7 @@ function initDatabase() {
const starter = [
'youtube.com', 'youtu.be', 'twitch.tv', 'x.com', 'twitter.com', 'bsky.app',
'reddit.com', 'github.com', 'gitlab.com', 'stackoverflow.com', 'wikipedia.org',
'imgur.com', 'giphy.com', 'tenor.com', 'spotify.com', 'soundcloud.com',
'imgur.com', 'giphy.com', 'tenor.com', 'klipy.com', 'spotify.com', 'soundcloud.com',
'steamcommunity.com', 'steampowered.com', 'last.fm', 'archive.org',
'haven-app.com'
];
@@ -617,6 +617,19 @@ function initDatabase() {
} catch (err) {
console.error('automod: adding haven-app.com to the allowlist failed:', err.message);
}
// KLIPY became a GIF picker choice after the allowlist was seeded, so its
// GIFs were blocked on allowlist servers. Added once, the same way.
try {
const added = db.prepare("SELECT value FROM server_settings WHERE key = 'automod_klipy_v4270'").get();
if (!added || added.value !== 'true') {
db.prepare(
"INSERT OR IGNORE INTO automod_domains (domain, mode, include_subdomains, note) VALUES ('klipy.com', 'allow', 1, 'Seeded default')"
).run();
db.prepare("INSERT OR REPLACE INTO server_settings (key, value) VALUES ('automod_klipy_v4270', 'true')").run();
}
} catch (err) {
console.error('automod: adding klipy.com to the allowlist failed:', err.message);
}
// ── Migration: turn the safe protections on, once (v3.43.0) ──
// The seeds above are INSERT OR IGNORE, so they only reach brand-new
@@ -0,0 +1,44 @@
'use strict';
/**
* The new-account link wait is for links people paste. A GIF sent from the
* GIF picker is a link to the GIF service's image host, and a new member who
* tried the picker used to be struck and then muted for it.
*
* node --test test/automodGifNewAccount.integration.test.js
*/
const test = require('node:test');
const assert = require('node:assert');
const { createHarness, wait } = require('./socketServerHarness');
const h = createHarness(3505, 'automod-gif');
test.before(() => h.start());
test.after(() => h.stop());
test('a new account can send GIFs but not other links', async () => {
const admin = await h.register('admin');
const bob = await h.register('bob');
const A = await h.connect(admin.token);
const B = await h.connect(bob.token);
A.emit('update-server-setting', { key: 'automod_enabled', value: 'true' });
A.emit('update-server-setting', { key: 'automod_link_min_account_hours', value: '24' });
const made = h.next(A, 'channels-list', (chs) => Array.isArray(chs) && chs.some((c) => c.name === 'chat'));
A.emit('create-channel', { name: 'chat' });
const chat = (await made).find((c) => c.name === 'chat');
B.emit('join-channel', { code: chat.code });
await wait(300);
B.emit('enter-channel', { code: chat.code });
await wait(200);
const gif = 'https://media4.giphy.com/media/abc123/giphy.gif';
const got = h.next(B, 'new-message', (d) => d && d.message && d.message.content === gif);
B.emit('send-message', { code: chat.code, content: gif });
assert.ok(await got, 'the GIF went through');
const refused = h.next(B, 'new-message', (d) => d && d.message && d.message.content.includes('example.org'), 1500);
B.emit('send-message', { code: chat.code, content: 'look https://example.org/page' });
assert.strictEqual(await refused, null, 'a pasted link still waits');
});