fdsafs
This commit is contained in:
+133
-52
@@ -1,7 +1,7 @@
|
||||
import db from "../sql.mjs";
|
||||
import lib from "../lib.mjs";
|
||||
import cfg from "../config.mjs";
|
||||
import { getEnableItemSlugs } from "../settings.mjs";
|
||||
import { getEnableItemSlugs, canAnonDo, getAnonAllowedModes, getAnonAllowedMimes, isAnonSession } from "../settings.mjs";
|
||||
import { updateHallsCache } from "../halls_cache.mjs";
|
||||
import queue from "../queue.mjs";
|
||||
import fs from "fs";
|
||||
@@ -15,7 +15,7 @@ const getGlobalfilter = () => {
|
||||
};
|
||||
|
||||
const computeBaseMode = (mode, ratings, session) => {
|
||||
const effMode = Number(mode ?? 0);
|
||||
let effMode = Number(mode ?? 0);
|
||||
const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null;
|
||||
|
||||
// For guest sessions, sanitize ratingsArr to only allow permitted ratings
|
||||
@@ -28,6 +28,32 @@ const computeBaseMode = (mode, ratings, session) => {
|
||||
if (safeRatingsArr.length === 0) {
|
||||
return "1 = 0";
|
||||
}
|
||||
} else if (isAnonSession(session)) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
const canFilter = canAnonDo('filter');
|
||||
|
||||
if (!canFilter) {
|
||||
safeRatingsArr = null;
|
||||
effMode = 0;
|
||||
} else if (safeRatingsArr) {
|
||||
safeRatingsArr = safeRatingsArr.filter(r => allowedModes.includes(r));
|
||||
if (safeRatingsArr.length === 0) {
|
||||
return "1 = 0";
|
||||
}
|
||||
}
|
||||
|
||||
const modeNames = ['sfw', 'nsfw', 'untagged', 'all', 'nsfl'];
|
||||
const currentModeName = modeNames[effMode] || 'sfw';
|
||||
|
||||
if (effMode === 3) {
|
||||
if (allowedModes.length < 5) {
|
||||
safeRatingsArr = [...allowedModes];
|
||||
}
|
||||
} else if (!allowedModes.includes(currentModeName)) {
|
||||
const fallbackModeName = allowedModes[0] || 'sfw';
|
||||
const fallbackModeIdx = modeNames.indexOf(fallbackModeName);
|
||||
effMode = fallbackModeIdx >= 0 ? fallbackModeIdx : 0;
|
||||
}
|
||||
}
|
||||
|
||||
let baseMode;
|
||||
@@ -67,6 +93,21 @@ const computeBaseMode = (mode, ratings, session) => {
|
||||
} else if (effMode === 4) {
|
||||
baseMode = "1 = 0";
|
||||
}
|
||||
} else if (isAnonSession(session)) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
const nsflId = parseInt(cfg.nsfl_tag_id, 10) || 3;
|
||||
if (!allowedModes.includes('nsfl')) {
|
||||
baseMode = `(${baseMode}) and not exists (select 1 from tags_assign where item_id = items.id and tag_id = ${nsflId})`;
|
||||
}
|
||||
if (!allowedModes.includes('nsfw')) {
|
||||
baseMode = `(${baseMode}) and not exists (select 1 from tags_assign where item_id = items.id and tag_id = 2)`;
|
||||
}
|
||||
if (!allowedModes.includes('untagged')) {
|
||||
baseMode = `(${baseMode}) and exists (select 1 from tags_assign where item_id = items.id and tag_id in (1, 2, ${nsflId}))`;
|
||||
}
|
||||
if (!allowedModes.includes('sfw')) {
|
||||
baseMode = `(${baseMode}) and not exists (select 1 from tags_assign where item_id = items.id and tag_id = 1)`;
|
||||
}
|
||||
}
|
||||
return baseMode;
|
||||
};
|
||||
@@ -87,6 +128,36 @@ const resolveNumericItemId = async (itemIdOrSlug) => {
|
||||
// All MIME types that map to the 'swf' extension in config (e.g. application/x-shockwave-flash, application/vnd.adobe.flash.movie)
|
||||
const flashMimes = Object.entries(cfg.mimes || {}).filter(([, ext]) => ext === 'swf').map(([mime]) => mime);
|
||||
|
||||
const resolveMimeSQL = (rawMime, session, itemAlias = 'items') => {
|
||||
let mimeParts = (rawMime || "").split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
|
||||
if (isAnonSession(session)) {
|
||||
const allowedMimes = getAnonAllowedMimes();
|
||||
const canFilter = canAnonDo('filter');
|
||||
if (!canFilter || allowedMimes.length === 1) {
|
||||
mimeParts = allowedMimes.length < 5 ? [...allowedMimes] : [];
|
||||
} else {
|
||||
if (mimeParts.length > 0) {
|
||||
mimeParts = mimeParts.filter(m => allowedMimes.includes(m));
|
||||
if (mimeParts.length === 0) {
|
||||
mimeParts = allowedMimes.length < 5 ? [...allowedMimes] : [];
|
||||
}
|
||||
} else if (allowedMimes.length < 5) {
|
||||
mimeParts = [...allowedMimes];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mimeSQL = mimeParts.length > 0
|
||||
? db`and (${mimeParts.map(m => m === 'flash'
|
||||
? (flashMimes.length > 0
|
||||
? (itemAlias === 'i' ? flashMimes.map(fm => db`i.mime = ${fm}`).reduce((a, b) => db`${a} or ${b}`) : flashMimes.map(fm => db`items.mime = ${fm}`).reduce((a, b) => db`${a} or ${b}`))
|
||||
: db`false`)
|
||||
: (m === 'pdf' ? (itemAlias === 'i' ? db`i.mime = 'application/pdf'` : db`items.mime = 'application/pdf'`) : (itemAlias === 'i' ? db`i.mime ilike ${m + '/%'}` : db`items.mime ilike ${m + '/%'}`))).reduce((a, b) => db`${a} or ${b}`)})`
|
||||
: db``;
|
||||
|
||||
return { mimeParts, mimeSQL };
|
||||
};
|
||||
|
||||
// ── Count cache ─────────────────────────────────────────────────────────────
|
||||
// The COUNT(DISTINCT items.id) in getf0cks is expensive (full filtered scan).
|
||||
// Cache it per unique filter combination for 90 seconds so that navigating
|
||||
@@ -300,16 +371,7 @@ const buildFeedFilters = async ({
|
||||
if (uhData.length) userHallObj = uhData[0];
|
||||
}
|
||||
const mime = rawMime ?? null;
|
||||
|
||||
// Support multiple MIME types (comma separated)
|
||||
const mimeParts = (mime || "").split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
|
||||
const mimeSQL = mimeParts.length > 0
|
||||
? db`and (${mimeParts.map(m => m === 'flash'
|
||||
? (flashMimes.length > 0
|
||||
? flashMimes.map(fm => db`items.mime = ${fm}`).reduce((a, b) => db`${a} or ${b}`)
|
||||
: db`false`)
|
||||
: (m === 'pdf' ? db`items.mime = 'application/pdf'` : db`items.mime ilike ${m + '/%'}`)).reduce((a, b) => db`${a} or ${b}`)})`
|
||||
: db``;
|
||||
const { mimeParts, mimeSQL } = resolveMimeSQL(mime, session);
|
||||
|
||||
const excludedTags = session && exclude ? (exclude || []) : [];
|
||||
const newerThan = newer ? parseInt(newer) : null;
|
||||
@@ -853,14 +915,7 @@ const f0cklib = {
|
||||
const isNumeric = /^\d+$/.test(String(rawIdOrSlug));
|
||||
const itemLookup = isNumeric ? db`items.id = ${+rawIdOrSlug}` : db`items.slug = ${String(rawIdOrSlug)}`;
|
||||
|
||||
const mimeParts = (mime || "").split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
|
||||
const mimeSQL = mimeParts.length > 0
|
||||
? db`and (${mimeParts.map(m => m === 'flash'
|
||||
? (flashMimes.length > 0
|
||||
? flashMimes.map(fm => db`items.mime = ${fm}`).reduce((a, b) => db`${a} or ${b}`)
|
||||
: db`false`)
|
||||
: (m === 'pdf' ? db`items.mime = 'application/pdf'` : db`items.mime ilike ${m + '/%'}`)).reduce((a, b) => db`${a} or ${b}`)})`
|
||||
: db``;
|
||||
const { mimeParts, mimeSQL } = resolveMimeSQL(mime, session);
|
||||
const excludedTags = exclude || [];
|
||||
|
||||
const strictParams = ((strict || (tag && tag.includes(','))) && tag) ? tag.split(',').map(t => lib.slugify(t)).filter(t => t) : [];
|
||||
@@ -1404,14 +1459,7 @@ const f0cklib = {
|
||||
}
|
||||
|
||||
// Support multiple MIME types (comma separated)
|
||||
const mimeParts = (mime || "").split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
|
||||
const mimeSQL = mimeParts.length > 0
|
||||
? db`and (${mimeParts.map(m => m === 'flash'
|
||||
? (flashMimes.length > 0
|
||||
? flashMimes.map(fm => db`items.mime = ${fm}`).reduce((a, b) => db`${a} or ${b}`)
|
||||
: db`false`)
|
||||
: (m === 'pdf' ? db`items.mime = 'application/pdf'` : db`items.mime ilike ${m + '/%'}`)).reduce((a, b) => db`${a} or ${b}`)})`
|
||||
: db``;
|
||||
const { mimeParts, mimeSQL } = resolveMimeSQL(mime, session);
|
||||
const excludedTags = session && exclude ? (exclude || []) : [];
|
||||
|
||||
const strictParams = ((strict || (tag && tag.includes(','))) && tag) ? tag.split(',').map(t => lib.slugify(t)).filter(t => t) : [];
|
||||
@@ -2113,14 +2161,7 @@ const f0cklib = {
|
||||
? db`AND items.id != ALL(${excludeItemIds}::int[])`
|
||||
: db``;
|
||||
|
||||
const mimeParts = (mime || "").split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
|
||||
const mimeSQL = mimeParts.length > 0
|
||||
? db`and (${mimeParts.map(m => m === 'flash'
|
||||
? (flashMimes.length > 0
|
||||
? flashMimes.map(fm => db`items.mime = ${fm}`).reduce((a, b) => db`${a} or ${b}`)
|
||||
: db`false`)
|
||||
: (m === 'pdf' ? db`items.mime = 'application/pdf'` : db`items.mime ilike ${m + '/%'}`)).reduce((a, b) => db`${a} or ${b}`)})`
|
||||
: db``;
|
||||
const { mimeParts, mimeSQL } = resolveMimeSQL(mime, session);
|
||||
|
||||
let rows;
|
||||
if (mimeParts.length > 0) {
|
||||
@@ -2298,6 +2339,59 @@ const f0cklib = {
|
||||
}
|
||||
},
|
||||
|
||||
updateUserTagAffinity: async ({ user_id, tag, scoreDelta = 2.0 }) => {
|
||||
if (!user_id || !tag || !scoreDelta) return;
|
||||
try {
|
||||
const rawList = typeof tag === 'string'
|
||||
? tag.split(',')
|
||||
: (Array.isArray(tag) ? tag : [tag]);
|
||||
|
||||
const tagsList = rawList
|
||||
.map(t => typeof t === 'string' ? t.trim().toLowerCase() : '')
|
||||
.filter(t => t && !t.startsWith('title:') && !t.startsWith('src:'))
|
||||
.slice(0, 10);
|
||||
|
||||
if (tagsList.length === 0) return;
|
||||
|
||||
const slugList = tagsList.map(t => lib.slugify(t)).filter(Boolean);
|
||||
|
||||
const tagRows = await db`
|
||||
SELECT DISTINCT id FROM tags
|
||||
WHERE LOWER(tag) = ANY(${tagsList}::text[])
|
||||
OR (normalized != '' AND normalized = ANY(${slugList}::text[]))
|
||||
`;
|
||||
if (tagRows.length === 0) return;
|
||||
|
||||
const tagIds = tagRows.map(r => r.id);
|
||||
|
||||
// Guard: Only increment score and interaction_count if last_interacted was more than 10s ago,
|
||||
// avoiding duplicate score inflation from rapid page refreshes or dual beacon/page loads.
|
||||
await db`
|
||||
INSERT INTO user_tag_affinity (user_id, tag_id, score, interaction_count, last_interacted)
|
||||
SELECT
|
||||
${user_id},
|
||||
unnest(${tagIds}::int[]),
|
||||
${scoreDelta},
|
||||
1,
|
||||
now()
|
||||
ON CONFLICT (user_id, tag_id) DO UPDATE SET
|
||||
score = CASE
|
||||
WHEN user_tag_affinity.last_interacted < now() - interval '10 seconds'
|
||||
THEN GREATEST(-10.0, LEAST(1000.0, user_tag_affinity.score + EXCLUDED.score))
|
||||
ELSE user_tag_affinity.score
|
||||
END,
|
||||
interaction_count = CASE
|
||||
WHEN user_tag_affinity.last_interacted < now() - interval '10 seconds'
|
||||
THEN user_tag_affinity.interaction_count + 1
|
||||
ELSE user_tag_affinity.interaction_count
|
||||
END,
|
||||
last_interacted = now()
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error("[AFFINITY] Failed to update user tag affinity from search:", err);
|
||||
}
|
||||
},
|
||||
|
||||
decayUserAffinities: async () => {
|
||||
try {
|
||||
await db`
|
||||
@@ -2455,14 +2549,7 @@ const f0cklib = {
|
||||
? db`AND items.id != ALL(${excludeItemIds}::int[])`
|
||||
: db``;
|
||||
|
||||
const mimeParts = (mime || "").split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
|
||||
const mimeSQL = mimeParts.length > 0
|
||||
? db`and (${mimeParts.map(m => m === 'flash'
|
||||
? (flashMimes.length > 0
|
||||
? flashMimes.map(fm => db`items.mime = ${fm}`).reduce((a, b) => db`${a} or ${b}`)
|
||||
: db`false`)
|
||||
: (m === 'pdf' ? db`items.mime = 'application/pdf'` : db`items.mime ilike ${m + '/%'}`)).reduce((a, b) => db`${a} or ${b}`)})`
|
||||
: db``;
|
||||
const { mimeParts, mimeSQL } = resolveMimeSQL(mime, session);
|
||||
|
||||
let personalizedItems = [];
|
||||
if (personalizedTarget > 0 && targetTagIds.length > 0) {
|
||||
@@ -2671,14 +2758,7 @@ const f0cklib = {
|
||||
? db`AND (COALESCE(items.visibility, 0) = 0 OR items.username = (SELECT "user" FROM "user" WHERE id = ${user_id}))`
|
||||
: db`AND COALESCE(items.visibility, 0) = 0`);
|
||||
|
||||
const mimeParts = (mime || "").split(',').filter(m => ['video', 'audio', 'image', 'flash', 'pdf'].includes(m));
|
||||
const mimeSQL = mimeParts.length > 0
|
||||
? db`and (${mimeParts.map(m => m === 'flash'
|
||||
? (flashMimes.length > 0
|
||||
? flashMimes.map(fm => db`items.mime = ${fm}`).reduce((a, b) => db`${a} or ${b}`)
|
||||
: db`false`)
|
||||
: (m === 'pdf' ? db`items.mime = 'application/pdf'` : db`items.mime ilike ${m + '/%'}`)).reduce((a, b) => db`${a} or ${b}`)})`
|
||||
: db``;
|
||||
const { mimeParts, mimeSQL } = resolveMimeSQL(mime, session);
|
||||
|
||||
const terms = tag.split(',').map(t => t.trim()).filter(Boolean);
|
||||
const isStrict = !!strict || (tag && tag.includes(','));
|
||||
@@ -2835,6 +2915,7 @@ const f0cklib = {
|
||||
processEmbeds,
|
||||
computeXdScore,
|
||||
xdScoreMeta,
|
||||
resolveMimeSQL,
|
||||
// Bust the count cache (call after a new upload is accepted so page totals stay accurate)
|
||||
clearCountCache: () => countCache.clear()
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user