Increase meme funniness requirements

This commit is contained in:
2026-07-04 17:40:39 -03:00
parent 2baa814cbd
commit 4d317ba1d3
4 changed files with 23 additions and 6 deletions
+1
View File
@@ -20,6 +20,7 @@ The server listens on `http://localhost:8080` by default.
- `ADMIN_TOKEN`: secret review URL token. If omitted, one is generated at boot and printed in server logs.
- `OPENAI_API_KEY`: enables AI upload moderation. Without it, uploads are queued for admin review.
- `OPENAI_MODERATION_MODEL`: moderation vision model, default `gpt-4o-mini`
- `AUTO_APPROVE_MIN_SCORE`: minimum `MEME_CONSENSUS_SCORE` for immediate AI approval, default `80`
- `TRUST_PROXY`: set to `true` when running behind a trusted reverse proxy so upload limits use `X-Forwarded-For`
Uploads accept PNG and JPEG images. The server rejects files over 5 MB, any image edge over `6000px`, and images over 20 million pixels. Accepted uploads are decoded, metadata-stripped, resized so the longest edge is at most `1600px`, and stored as WebP.
+1
View File
@@ -15,6 +15,7 @@ services:
ADMIN_TOKEN: ${ADMIN_TOKEN}
OPENAI_API_KEY: ${OPENAI_API_KEY}
OPENAI_MODERATION_MODEL: ${OPENAI_MODERATION_MODEL:-gpt-4o-mini}
AUTO_APPROVE_MIN_SCORE: ${AUTO_APPROVE_MIN_SCORE:-80}
MAX_IMAGE_DIMENSION: ${MAX_IMAGE_DIMENSION:-1600}
WEBP_QUALITY: ${WEBP_QUALITY:-85}
volumes:
+11 -3
View File
@@ -1,5 +1,5 @@
const DEFAULT_MODEL = process.env.OPENAI_MODERATION_MODEL || 'gpt-4o-mini';
const AUTO_APPROVE_MIN_SCORE = 80;
const DEFAULT_AUTO_APPROVE_MIN_SCORE = 80;
export async function moderateImage({ buffer, mime }) {
if (!process.env.OPENAI_API_KEY) {
@@ -59,6 +59,7 @@ export async function moderateImage({ buffer, mime }) {
}
function moderationPrompt() {
const autoApproveMinScore = autoApproveMinScoreValue();
return [
'You are moderating image uploads for THE_MEME_PROTOCOL, a meme gallery.',
'This is a meme site. Images may be sarcastic, roasting, edgy, political, profane, absurd, or not PG-13.',
@@ -72,7 +73,7 @@ function moderationPrompt() {
'Assign MEME_CONSENSUS_SCORE from 0-100: higher means it is legal, clearly a meme/reaction/roast/remix, visually meaningful, has a real punchline, and is suitable for this site; lower means random photo, spam, ad, QR scam, screenshot dump, unclear, generic template caption, text-only joke, weak payoff, or AI-slop.',
'Return only compact JSON with keys: decision, score, reason.',
'decision must be one of: approved, pending, rejected.',
`Use approved only when it appears legal and score is at least ${AUTO_APPROVE_MIN_SCORE}.`,
`Use approved only when it appears legal and score is at least ${autoApproveMinScore}.`,
'Use pending for ambiguity, uncertainty, low meme relevance, weak quality, generic/slop memes, or anything that needs human review.',
'Use rejected only for likely illegal content.'
].join('\n');
@@ -100,12 +101,19 @@ function normalizeDecision(raw) {
? raw.reason.trim().slice(0, 300)
: 'No moderation reason supplied.';
if (decision === 'approved' && score < AUTO_APPROVE_MIN_SCORE) {
const autoApproveMinScore = autoApproveMinScoreValue();
if (decision === 'approved' && score < autoApproveMinScore) {
return { status: 'pending', score, reason: `${reason} Low MEME_CONSENSUS_SCORE queued for review.` };
}
return { status: decision, score, reason };
}
function autoApproveMinScoreValue() {
const value = Number.parseInt(process.env.AUTO_APPROVE_MIN_SCORE || '', 10);
if (!Number.isFinite(value)) return DEFAULT_AUTO_APPROVE_MIN_SCORE;
return Math.max(0, Math.min(100, value));
}
function extractOutputText(payload) {
const parts = [];
for (const item of payload.output || []) {
+10 -3
View File
@@ -27,22 +27,24 @@ test('queues uploads when AI moderation is not configured', async () => {
test('queues model approvals below the auto-publish quality threshold', async () => {
const originalApiKey = process.env.OPENAI_API_KEY;
const originalThreshold = process.env.AUTO_APPROVE_MIN_SCORE;
const originalFetch = globalThis.fetch;
process.env.OPENAI_API_KEY = 'test-key';
process.env.AUTO_APPROVE_MIN_SCORE = '90';
globalThis.fetch = async (_url, options) => {
const body = JSON.parse(options.body);
const prompt = body.input[0].content.find((item) => item.type === 'input_text').text;
assert.match(prompt, /could the exact same caption work on 50 unrelated images/);
assert.match(prompt, /Use approved only when it appears legal and score is at least 80/);
assert.match(prompt, /Use approved only when it appears legal and score is at least 90/);
return {
ok: true,
json: async () => ({
output_text: JSON.stringify({
decision: 'approved',
score: 79,
score: 89,
reason: 'Specific joke, but not strong enough.'
})
})
@@ -56,7 +58,7 @@ test('queues model approvals below the auto-publish quality threshold', async ()
});
assert.equal(moderation.status, 'pending');
assert.equal(moderation.score, 79);
assert.equal(moderation.score, 89);
assert.match(moderation.reason, /Low MEME_CONSENSUS_SCORE queued for review/);
} finally {
globalThis.fetch = originalFetch;
@@ -65,5 +67,10 @@ test('queues model approvals below the auto-publish quality threshold', async ()
} else {
process.env.OPENAI_API_KEY = originalApiKey;
}
if (originalThreshold === undefined) {
delete process.env.AUTO_APPROVE_MIN_SCORE;
} else {
process.env.AUTO_APPROVE_MIN_SCORE = originalThreshold;
}
}
});