fdsfsda
This commit is contained in:
+371
-107
@@ -8,9 +8,30 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import url from "url";
|
||||
|
||||
const getGlobalfilter = () => {
|
||||
const getGlobalfilter = (session) => {
|
||||
if (!cfg.nsfp?.length) return null;
|
||||
const filteredTags = cfg.websrv.public_nsfw ? cfg.nsfp.filter(id => id !== 2) : cfg.nsfp;
|
||||
let filteredTags = [...cfg.nsfp];
|
||||
// For anonymous users, respect their allowed_modes permissions
|
||||
if (session && isAnonSession(session)) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
if (allowedModes.includes('nsfw')) {
|
||||
filteredTags = filteredTags.filter(id => id !== 2);
|
||||
}
|
||||
const nsflId = parseInt(cfg.nsfl_tag_id, 10) || 3;
|
||||
if (allowedModes.includes('nsfl')) {
|
||||
filteredTags = filteredTags.filter(id => id !== nsflId);
|
||||
}
|
||||
} else if (!session) {
|
||||
// Guest (not logged in): use public_nsfw setting
|
||||
if (cfg.websrv.public_nsfw) {
|
||||
filteredTags = filteredTags.filter(id => id !== 2);
|
||||
}
|
||||
} else {
|
||||
// Logged-in member: use public_nsfw setting
|
||||
if (cfg.websrv.public_nsfw) {
|
||||
filteredTags = filteredTags.filter(id => id !== 2);
|
||||
}
|
||||
}
|
||||
return filteredTags.length ? filteredTags.map(n => `tag_id = ${n}`).join(" or ") : null;
|
||||
};
|
||||
|
||||
@@ -175,7 +196,7 @@ function buildCountCacheKey({ modequery, tag, user, hall, mime, fav, session, ex
|
||||
hall ?? '',
|
||||
mime ?? '',
|
||||
fav ? 1 : 0,
|
||||
session ? 1 : 0, // guests get globalfilter applied; members don't
|
||||
(session && !isAnonSession(session)) ? 1 : 0, // guests and anon users get globalfilter applied; members don't
|
||||
excludedTags.slice().sort().join(','),
|
||||
newerThan ?? '',
|
||||
minXd,
|
||||
@@ -575,7 +596,7 @@ const f0cklib = {
|
||||
${mimeSQL}
|
||||
${hallFilter}
|
||||
${userHallFilter}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
|
||||
${newerThan ? db`and items.id > ${newerThan}` : db``}
|
||||
${xdFilter}
|
||||
@@ -601,7 +622,7 @@ const f0cklib = {
|
||||
${mimeSQL}
|
||||
${hallFilter}
|
||||
${userHallFilter}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
|
||||
${newerThan ? db`and items.id > ${newerThan}` : db``}
|
||||
${xdFilter}
|
||||
@@ -685,6 +706,28 @@ const f0cklib = {
|
||||
visibilityFilter
|
||||
} = filters;
|
||||
|
||||
const effMode = Number(mode ?? 0);
|
||||
const nsflId = parseInt(cfg.nsfl_tag_id, 10) || 3;
|
||||
const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null;
|
||||
let safeRatingsArr = ratingsArr;
|
||||
const isGuest = !session || !session.user;
|
||||
if (isGuest) {
|
||||
safeRatingsArr = ['sfw'];
|
||||
} else if (isAnonSession(session)) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
const canFilter = canAnonDo('filter');
|
||||
if (!canFilter) {
|
||||
safeRatingsArr = null;
|
||||
} else if (safeRatingsArr) {
|
||||
safeRatingsArr = safeRatingsArr.filter(r => allowedModes.includes(r));
|
||||
if (safeRatingsArr.length === 0) {
|
||||
safeRatingsArr = (allowedModes.length === 1 && !allowedModes.includes('all'))
|
||||
? [allowedModes[0]]
|
||||
: (allowedModes.length < 5 ? [...allowedModes] : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const actPage = +(page ?? 1);
|
||||
const eps = limit ?? cfg.websrv.eps;
|
||||
const tmp = { user, tag: isTitleSearch ? _decodedTag : tag, hall: hallObj || hall, mime, page: actPage, mode: mode, view_mode: fav ? 'favs' : 'uploads', strict: strict, userHall: userHallObj || userHallSlug, userHallOwner, tagger };
|
||||
@@ -709,7 +752,7 @@ const f0cklib = {
|
||||
${mimeSQL}
|
||||
${hallFilter}
|
||||
${userHallFilter}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
|
||||
${newerThan ? db`and items.id > ${newerThan}` : db``}
|
||||
${xdFilter}
|
||||
@@ -753,7 +796,7 @@ const f0cklib = {
|
||||
${mimeSQL}
|
||||
${hallFilter}
|
||||
${userHallFilter}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
|
||||
${newerThan ? db`and items.id > ${newerThan}` : db``}
|
||||
${xdFilter}
|
||||
@@ -821,12 +864,11 @@ const f0cklib = {
|
||||
row.is_audio = !!(row.mime && row.mime.startsWith('audio/'));
|
||||
}
|
||||
|
||||
if (tag && !isTitleSearch && rows.some(r => r.is_album)) {
|
||||
if (rows.some(r => r.is_album)) {
|
||||
const albumIds = rows.filter(r => r.is_album).map(r => r.id);
|
||||
const terms = tag.split(',').map(t => t.trim()).filter(Boolean);
|
||||
if (terms.length > 0 && albumIds.length > 0) {
|
||||
if (albumIds.length > 0) {
|
||||
try {
|
||||
const matchingSubs = await db`
|
||||
const subItemsRows = await db`
|
||||
SELECT
|
||||
ai.item_id,
|
||||
ai.id as subf0ck_id,
|
||||
@@ -835,55 +877,135 @@ const f0cklib = {
|
||||
ai.mime as subf0ck_mime,
|
||||
ai.order_index
|
||||
FROM album_items ai
|
||||
JOIN album_items_tags_assign aita ON aita.album_item_id = ai.id
|
||||
JOIN tags t ON t.id = aita.tag_id
|
||||
${tagger ? db`JOIN "user" u ON u.id = aita.user_id` : db``}
|
||||
WHERE ai.item_id = ANY(${albumIds}::int[])
|
||||
${tagger ? db`AND u.user ILIKE ${tagger}` : db``}
|
||||
AND (${isStrict
|
||||
? db`t.normalized = ANY(ARRAY(SELECT slugify(x) FROM unnest(${terms}::text[]) AS x))`
|
||||
: db`${terms.map(term => db`t.normalized LIKE '%' || slugify(${term}) || '%'`).reduce((a, b) => db`${a} OR ${b}`)}`
|
||||
})
|
||||
ORDER BY ai.item_id, ai.order_index ASC
|
||||
`;
|
||||
|
||||
const matchMap = new Map();
|
||||
for (const ms of matchingSubs) {
|
||||
if (!matchMap.has(ms.item_id)) {
|
||||
matchMap.set(ms.item_id, {
|
||||
first: ms,
|
||||
matchingIds: new Set([ms.subf0ck_id])
|
||||
});
|
||||
} else {
|
||||
matchMap.get(ms.item_id).matchingIds.add(ms.subf0ck_id);
|
||||
}
|
||||
const subIds = subItemsRows.map(s => s.subf0ck_id);
|
||||
const subTagsRows = subIds.length > 0 ? await db`
|
||||
SELECT aita.album_item_id, t.id, t.tag, t.normalized
|
||||
FROM album_items_tags_assign aita
|
||||
JOIN tags t ON t.id = aita.tag_id
|
||||
WHERE aita.album_item_id = ANY(${subIds}::int[])
|
||||
`.catch(() => []) : [];
|
||||
|
||||
const tagsBySubId = new Map();
|
||||
for (const st of subTagsRows) {
|
||||
if (!tagsBySubId.has(st.album_item_id)) tagsBySubId.set(st.album_item_id, []);
|
||||
tagsBySubId.get(st.album_item_id).push(st);
|
||||
}
|
||||
|
||||
const subsByItemId = new Map();
|
||||
for (const si of subItemsRows) {
|
||||
if (!subsByItemId.has(si.item_id)) subsByItemId.set(si.item_id, []);
|
||||
subsByItemId.get(si.item_id).push(si);
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
if (matchMap.has(row.id)) {
|
||||
const info = matchMap.get(row.id);
|
||||
const ms = info.first;
|
||||
row.target_subf0ck_slug = ms.subf0ck_slug || ms.subf0ck_id;
|
||||
if (ms.subf0ck_dest) {
|
||||
const subBase = ms.subf0ck_dest.replace(/\.[^.]+$/, '');
|
||||
row.thumb = `/t/${subBase}.webp`;
|
||||
row.matching_sub_thumb = `/t/${subBase}.webp`;
|
||||
row.matching_sub_dest = ms.subf0ck_dest;
|
||||
row.dest = ms.subf0ck_dest;
|
||||
if (!row.is_album || !subsByItemId.has(row.id)) continue;
|
||||
const subList = subsByItemId.get(row.id);
|
||||
const isOwnerOrAdmin = (session && session.user && row.username && session.user.toLowerCase() === row.username.toLowerCase()) || (session && (session.admin || session.is_moderator));
|
||||
|
||||
const isSubVisible = (subTags) => {
|
||||
if (isOwnerOrAdmin) return true;
|
||||
|
||||
const subIsNsfl = cfg.enable_nsfl && subTags.some(t => t.id == nsflId || t.normalized === 'nsfl');
|
||||
const subIsNsfw = subTags.some(t => t.id == 2);
|
||||
const subIsSfw = subTags.some(t => t.id == 1);
|
||||
const subIsUntagged = !subIsSfw && !subIsNsfw && !subIsNsfl;
|
||||
|
||||
if (excludedTags && excludedTags.length > 0 && subTags.length > 0) {
|
||||
if (subTags.some(t => excludedTags.includes(t.normalized) || excludedTags.includes(lib.slugify(t.tag)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (ms.subf0ck_mime) {
|
||||
row.matching_sub_mime = ms.subf0ck_mime;
|
||||
row.mime = ms.subf0ck_mime;
|
||||
|
||||
if (!session || !session.user) {
|
||||
if (subIsNsfl) return false;
|
||||
if (subIsNsfw && !cfg.websrv.public_nsfw) return false;
|
||||
if (subIsUntagged && !cfg.websrv.public_untagged) return false;
|
||||
if (effMode === 0 && (subIsNsfw || subIsNsfl || (subIsUntagged && !cfg.websrv.public_untagged))) return false;
|
||||
if (effMode === 1 && !subIsNsfw) return false;
|
||||
if (effMode === 2 && (!subIsUntagged || !cfg.websrv.public_untagged)) return false;
|
||||
return true;
|
||||
}
|
||||
const mCount = info.matchingIds.size;
|
||||
row.album_count = mCount;
|
||||
if (mCount <= 1) {
|
||||
row.is_album = false;
|
||||
|
||||
if (isAnonSession(session)) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
if (subIsNsfl && !allowedModes.includes('nsfl')) return false;
|
||||
if (subIsNsfw && !allowedModes.includes('nsfw')) return false;
|
||||
if (subIsUntagged && !allowedModes.includes('untagged')) return false;
|
||||
if (subIsSfw && !allowedModes.includes('sfw')) return false;
|
||||
|
||||
if (safeRatingsArr && safeRatingsArr.length > 0) {
|
||||
const subRatingName = subIsNsfl ? 'nsfl' : (subIsNsfw ? 'nsfw' : (subIsSfw ? 'sfw' : 'untagged'));
|
||||
if (!safeRatingsArr.includes(subRatingName)) return false;
|
||||
} else if (effMode !== 3) {
|
||||
if (effMode === 0 && (subIsNsfw || subIsNsfl || (subIsUntagged && !allowedModes.includes('untagged')))) return false;
|
||||
if (effMode === 1 && !subIsNsfw) return false;
|
||||
if (effMode === 2 && (!subIsUntagged || !allowedModes.includes('untagged'))) return false;
|
||||
if (effMode === 4 && !subIsNsfl) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (safeRatingsArr && safeRatingsArr.length > 0) {
|
||||
const subRatingName = subIsNsfl ? 'nsfl' : (subIsNsfw ? 'nsfw' : (subIsSfw ? 'sfw' : 'untagged'));
|
||||
if (!safeRatingsArr.includes(subRatingName)) return false;
|
||||
} else if (effMode !== 3) {
|
||||
if (effMode === 0 && (subIsNsfw || subIsNsfl)) return false;
|
||||
if (effMode === 1 && !subIsNsfw) return false;
|
||||
if (effMode === 2 && !subIsUntagged) return false;
|
||||
if (effMode === 4 && !subIsNsfl) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
let visibleSubs = subList.filter((si) => {
|
||||
let st = tagsBySubId.get(si.subf0ck_id) || [];
|
||||
return isSubVisible(st);
|
||||
});
|
||||
|
||||
if (tag && !isTitleSearch) {
|
||||
const terms = tag.split(',').map(t => t.trim()).filter(Boolean);
|
||||
if (terms.length > 0) {
|
||||
const tagMatchingSubs = visibleSubs.filter((si) => {
|
||||
let st = tagsBySubId.get(si.subf0ck_id) || [];
|
||||
if (isStrict) {
|
||||
return strictParams.every(sp => st.some(t => t.normalized === sp));
|
||||
}
|
||||
return terms.some(term => {
|
||||
const slugTerm = lib.slugify(term);
|
||||
return st.some(t => t.normalized && t.normalized.includes(slugTerm));
|
||||
});
|
||||
});
|
||||
if (tagMatchingSubs.length > 0) {
|
||||
const ms = tagMatchingSubs[0];
|
||||
row.target_subf0ck_slug = ms.subf0ck_slug || ms.subf0ck_id;
|
||||
if (ms.subf0ck_dest) {
|
||||
const subBase = ms.subf0ck_dest.replace(/\.[^.]+$/, '');
|
||||
row.thumb = `/t/${subBase}.webp`;
|
||||
row.matching_sub_thumb = `/t/${subBase}.webp`;
|
||||
row.matching_sub_dest = ms.subf0ck_dest;
|
||||
row.dest = ms.subf0ck_dest;
|
||||
}
|
||||
if (ms.subf0ck_mime) {
|
||||
row.matching_sub_mime = ms.subf0ck_mime;
|
||||
row.mime = ms.subf0ck_mime;
|
||||
}
|
||||
visibleSubs = tagMatchingSubs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
row.album_count = visibleSubs.length;
|
||||
if (visibleSubs.length <= 1) {
|
||||
row.is_album = false;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[FEED] Error resolving matching subf0cks for tag search:', err.message);
|
||||
console.warn('[FEED] Error calculating visible album count for feed:', err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -950,7 +1072,7 @@ const f0cklib = {
|
||||
view_mode: fav ? 'favs' : 'uploads'
|
||||
};
|
||||
},
|
||||
getf0ck: async ({ user: rawUser, tag: rawTag, hall: rawHall, mime: rawMime, itemid: rawItemid, mode, ratings, session, strict, exclude, user_id, is_admin, fav, random, userHall: rawUserHall, userHallOwner: rawUserHallOwner, lang, ids, subf0ck } = {}) => {
|
||||
getf0ck: async ({ user: rawUser, tag: rawTag, hall: rawHall, mime: rawMime, itemid: rawItemid, mode, ratings, session, strict, exclude, user_id, is_admin, fav, random, userHall: rawUserHall, userHallOwner: rawUserHallOwner, lang, ids, subf0ck, bypass_filter } = {}) => {
|
||||
if (fav && rawUser) {
|
||||
const { isPrivate, isAllowed } = await checkFavoritesAccess(rawUser, { session, user_id, is_admin });
|
||||
if (isPrivate && !isAllowed) {
|
||||
@@ -1029,6 +1151,21 @@ const f0cklib = {
|
||||
|
||||
const effMode = Number(mode ?? 0);
|
||||
const nsflId = parseInt(cfg.nsfl_tag_id, 10) || 3;
|
||||
const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null;
|
||||
let safeRatingsArr = ratingsArr;
|
||||
if (!session || !session.user) {
|
||||
safeRatingsArr = ['sfw'];
|
||||
} else if (isAnonSession(session)) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
if (safeRatingsArr) {
|
||||
safeRatingsArr = safeRatingsArr.filter(r => allowedModes.includes(r));
|
||||
if (safeRatingsArr.length === 0) {
|
||||
safeRatingsArr = (allowedModes.length === 1 && !allowedModes.includes('all'))
|
||||
? [allowedModes[0]]
|
||||
: (allowedModes.length < 5 ? [...allowedModes] : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
const itemModeQuery = computeBaseMode(mode, ratings, session);
|
||||
|
||||
let tagFilter = db``;
|
||||
@@ -1089,7 +1226,7 @@ const f0cklib = {
|
||||
${!fav && user ? db`and items.username ilike ${user}` : db``}
|
||||
${idsFilter}
|
||||
${mimeSQL}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
|
||||
`;
|
||||
};
|
||||
@@ -1180,16 +1317,17 @@ const f0cklib = {
|
||||
last_viewed = now()
|
||||
`.catch(e => console.error('Failed to track view:', e));
|
||||
}
|
||||
// Guest rating restriction check for public items (unlisted items requested by direct link/slug bypass guest rating blocks)
|
||||
if (!session && (actitem.visibility || 0) === 0) {
|
||||
// Public visitor / guest rating restriction check for public items (unlisted items requested by direct link/slug bypass guest rating blocks)
|
||||
const isPublicVisitor = !session || !session.user || isAnonSession(session);
|
||||
if (isPublicVisitor && !isOwnerOrAdmin && (actitem.visibility || 0) === 0) {
|
||||
let blocked = false;
|
||||
if (getGlobalfilter()) {
|
||||
if (getGlobalfilter(session)) {
|
||||
const filteredItem = await db`
|
||||
select 1 from tags_assign where item_id = ${itemid} and (${db.unsafe(getGlobalfilter())}) limit 1
|
||||
select 1 from tags_assign where item_id = ${itemid} and (${db.unsafe(getGlobalfilter(session))}) limit 1
|
||||
`;
|
||||
if (filteredItem.length > 0) blocked = true;
|
||||
}
|
||||
if (!blocked && !cfg.websrv.public_untagged) {
|
||||
if (!blocked && !session && !cfg.websrv.public_untagged) {
|
||||
const ratingTag = await db`
|
||||
select 1 from tags_assign where item_id = ${itemid} and tag_id in (1, 2, ${nsflId}) limit 1
|
||||
`;
|
||||
@@ -1217,7 +1355,7 @@ const f0cklib = {
|
||||
|
||||
// Determine the effective mode for optimization check (similar to Random)
|
||||
const nsfl_id = cfg.nsfl_tag_id || 3;
|
||||
const useTagsDriver = !!session && (effMode === 1 || effMode === 4) && !fav && !tag && !user && !hall && (!cleanIds || cleanIds.length === 0);
|
||||
const useTagsDriver = !!session && !isAnonSession(session) && (effMode === 1 || effMode === 4) && !fav && !tag && !user && !hall && (!cleanIds || cleanIds.length === 0);
|
||||
|
||||
const baseQuery = (whereClause, orderBy, limit = 1) => {
|
||||
return db`
|
||||
@@ -1244,7 +1382,7 @@ const f0cklib = {
|
||||
const useTagIdOpt = !mimeParts.includes('audio');
|
||||
|
||||
const nsfpIds = cfg.nsfp || [];
|
||||
const checkFilter = !session && nsfpIds.length > 0;
|
||||
const checkFilter = (!session || isAnonSession(session)) && nsfpIds.length > 0;
|
||||
|
||||
const query = db`
|
||||
SELECT ta.item_id as id, items.slug
|
||||
@@ -1305,13 +1443,27 @@ const f0cklib = {
|
||||
link.suffix = '';
|
||||
}
|
||||
const favorites = await db`
|
||||
select "user".user, "user_options".avatar, "user_options".avatar_file, "user_options".username_color, "user_options".display_name, "user_options".hide_fav_badge
|
||||
select "favorites".user_id, "user".user, "user".login, "user_options".avatar, "user_options".avatar_file, "user_options".username_color, "user_options".display_name, "user_options".hide_fav_badge, "anon_identities".fingerprint as anon_fingerprint
|
||||
from "favorites"
|
||||
left join "user" on "user".id = "favorites".user_id
|
||||
left join "user_options" on "user_options".user_id = "favorites".user_id
|
||||
left join "anon_identities" on "anon_identities".user_id = "favorites".user_id
|
||||
where "favorites".item_id = ${itemid}
|
||||
`;
|
||||
|
||||
for (const f of favorites) {
|
||||
if (f.anon_fingerprint || f.user === 'anonymous' || (typeof f.user === 'string' && f.user.startsWith('anon_'))) {
|
||||
f.is_anon = true;
|
||||
f.user = 'anonymous';
|
||||
f.login = 'anonymous';
|
||||
f.display_name = 'Anonymous';
|
||||
f.avatar = null;
|
||||
f.avatar_file = null;
|
||||
f.username_color = null;
|
||||
f.hide_fav_badge = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect reposts: items uploaded with bypass_duplicate_check have checksum = `{hash}_bypass_{ts}`
|
||||
// Find all items (including this one) that share the same base checksum.
|
||||
let repostItems = [];
|
||||
@@ -1355,27 +1507,21 @@ const f0cklib = {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Efficient coverart fallback with on-demand extraction
|
||||
let hasCoverart = actitem.has_coverart;
|
||||
if (!hasCoverart && actitem.mime?.startsWith('audio/')) {
|
||||
const caPath = path.join(cfg.paths.ca, `${actitem.id}.webp`);
|
||||
let hasCoverart = false;
|
||||
if (actitem.mime && actitem.mime.startsWith('audio/')) {
|
||||
try {
|
||||
const caPath = path.join(cfg.paths.ca, `${actitem.id}.webp`);
|
||||
if (fs.existsSync(caPath) && fs.statSync(caPath).size > 0) {
|
||||
hasCoverart = true;
|
||||
db`UPDATE items SET has_coverart = TRUE WHERE id = ${actitem.id}`.catch(() => {});
|
||||
} else {
|
||||
// Attempt extraction directly from audio file if embedded
|
||||
const sourcePath = path.join(cfg.paths.b, actitem.dest);
|
||||
if (fs.existsSync(sourcePath)) {
|
||||
await queue.spawn('ffmpeg', ['-y', '-i', sourcePath, '-an', '-vcodec', 'webp', '-frames:v', '1', caPath], { quiet: true }).catch(() => {});
|
||||
// On-demand cover art extraction fallback
|
||||
const audioFile = path.join(cfg.paths.b, actitem.dest);
|
||||
if (fs.existsSync(audioFile)) {
|
||||
await queue.spawn('ffmpeg', ['-y', '-i', audioFile, '-an', '-vcodec', 'webp', '-frames:v', '1', caPath], { quiet: true });
|
||||
if (fs.existsSync(caPath) && fs.statSync(caPath).size > 0) {
|
||||
hasCoverart = true;
|
||||
db`UPDATE items SET has_coverart = TRUE WHERE id = ${actitem.id}`.catch(() => {});
|
||||
const tPath = path.join(cfg.paths.t, `${actitem.id}.webp`);
|
||||
if (!fs.existsSync(tPath) || fs.statSync(tPath).size === 0) {
|
||||
await queue.spawn('magick', [caPath + '[0]', '-resize', '256x256^', '-gravity', 'center', '-crop', '256x256+0+0', '+repage', tPath], { quiet: true }).catch(() => {});
|
||||
}
|
||||
await queue.spawn('magick', [caPath + '[0]', '-resize', '256x256^', '-gravity', 'center', '-crop', '256x256+0+0', '+repage', tPath], { quiet: true });
|
||||
} else {
|
||||
try { fs.unlinkSync(caPath); } catch (_) {}
|
||||
}
|
||||
@@ -1416,11 +1562,8 @@ const f0cklib = {
|
||||
if (tag && !isTitleSearch) {
|
||||
const terms = tag.split(',').map(t => t.trim()).filter(Boolean);
|
||||
if (terms.length > 0 && tagsBySubId.size > 0) {
|
||||
const matching = albumRows.filter((r, idx) => {
|
||||
const matching = albumRows.filter((r) => {
|
||||
let st = tagsBySubId.get(r.id) || [];
|
||||
if (st.length === 0 && (r.order_index === 0 || idx === 0)) {
|
||||
st = tags;
|
||||
}
|
||||
if (isStrict) {
|
||||
return strictParams.every(sp => st.some(t => t.normalized === sp));
|
||||
}
|
||||
@@ -1435,6 +1578,88 @@ const f0cklib = {
|
||||
}
|
||||
}
|
||||
|
||||
const isSubVisible = (subTags) => {
|
||||
if (bypass_filter) return true;
|
||||
|
||||
const subIsNsfl = cfg.enable_nsfl && subTags.some(t => t.id == nsfl_id || t.normalized === 'nsfl');
|
||||
const subIsNsfw = subTags.some(t => t.id == 2);
|
||||
const subIsSfw = subTags.some(t => t.id == 1);
|
||||
const subIsUntagged = !subIsSfw && !subIsNsfw && !subIsNsfl;
|
||||
|
||||
// Excluded tags check
|
||||
if (excludedTags && excludedTags.length > 0 && subTags.length > 0) {
|
||||
if (subTags.some(t => excludedTags.includes(t.normalized) || excludedTags.includes(lib.slugify(t.tag)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Guest check
|
||||
if (!session || !session.user) {
|
||||
if (subIsNsfl) return false;
|
||||
if (subIsNsfw && !cfg.websrv.public_nsfw) return false;
|
||||
if (subIsUntagged && !cfg.websrv.public_untagged) return false;
|
||||
if (effMode === 0 && (subIsNsfw || subIsNsfl || (subIsUntagged && !cfg.websrv.public_untagged))) return false;
|
||||
if (effMode === 1 && !subIsNsfw) return false;
|
||||
if (effMode === 2 && (!subIsUntagged || !cfg.websrv.public_untagged)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Anon session check
|
||||
if (isAnonSession(session)) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
if (subIsNsfl && !allowedModes.includes('nsfl')) return false;
|
||||
if (subIsNsfw && !allowedModes.includes('nsfw')) return false;
|
||||
if (subIsUntagged && !allowedModes.includes('untagged')) return false;
|
||||
if (subIsSfw && !allowedModes.includes('sfw')) return false;
|
||||
|
||||
if (safeRatingsArr && safeRatingsArr.length > 0) {
|
||||
const subRatingName = subIsNsfl ? 'nsfl' : (subIsNsfw ? 'nsfw' : (subIsSfw ? 'sfw' : 'untagged'));
|
||||
if (!safeRatingsArr.includes(subRatingName)) return false;
|
||||
} else if (effMode !== 3) {
|
||||
if (effMode === 0 && (subIsNsfw || subIsNsfl || (subIsUntagged && !allowedModes.includes('untagged')))) return false;
|
||||
if (effMode === 1 && !subIsNsfw) return false;
|
||||
if (effMode === 2 && (!subIsUntagged || !allowedModes.includes('untagged'))) return false;
|
||||
if (effMode === 4 && !subIsNsfl) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Logged-in member check
|
||||
if (safeRatingsArr && safeRatingsArr.length > 0) {
|
||||
const subRatingName = subIsNsfl ? 'nsfl' : (subIsNsfw ? 'nsfw' : (subIsSfw ? 'sfw' : 'untagged'));
|
||||
if (!safeRatingsArr.includes(subRatingName)) return false;
|
||||
} else if (effMode !== 3) {
|
||||
if (effMode === 0 && (subIsNsfw || subIsNsfl)) return false;
|
||||
if (effMode === 1 && !subIsNsfw) return false;
|
||||
if (effMode === 2 && !subIsUntagged) return false;
|
||||
if (effMode === 4 && !subIsNsfl) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
albumRows = albumRows.filter((r) => {
|
||||
let subTags = tagsBySubId.get(r.id) || [];
|
||||
return isSubVisible(subTags);
|
||||
});
|
||||
|
||||
if (albumRows.length === 0) {
|
||||
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
|
||||
return {
|
||||
success: false,
|
||||
message: "Sorry, this post is currently not visible.",
|
||||
item: {
|
||||
id: itemid,
|
||||
slug: actitem.slug || null,
|
||||
og_thumbnail: `${cfg.websrv.paths.thumbnails}/${itemid}_blur.webp`,
|
||||
og_url: hallSlug
|
||||
? `https://${cfg.main.url.domain}/h/${encodeURIComponent(hallSlug)}/${itemid}`
|
||||
: `https://${cfg.main.url.domain}/${itemid}`,
|
||||
og_description: `Content not visible in current mode`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
album = await Promise.all(albumRows.map(async (r, idx) => {
|
||||
const order = idx;
|
||||
const subSlug = r.slug || r.id;
|
||||
@@ -1503,9 +1728,6 @@ const f0cklib = {
|
||||
}
|
||||
|
||||
let subTags = tagsBySubId.get(r.id) || [];
|
||||
if (subTags.length === 0 && (r.order_index === 0 || idx === 0)) {
|
||||
subTags = tags;
|
||||
}
|
||||
|
||||
return {
|
||||
id: r.id,
|
||||
@@ -1537,6 +1759,21 @@ const f0cklib = {
|
||||
const found = album.findIndex(s => String(s.slug || '') === String(requestedSubf0ckSlug) || String(s.subf0ck_id || '') === String(requestedSubf0ckSlug) || String(s.id) === String(requestedSubf0ckSlug));
|
||||
if (found !== -1) {
|
||||
reqIdx = found;
|
||||
} else {
|
||||
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
|
||||
return {
|
||||
success: false,
|
||||
message: "Sorry, this post is currently not visible.",
|
||||
item: {
|
||||
id: itemid,
|
||||
slug: actitem.slug || null,
|
||||
og_thumbnail: `${cfg.websrv.paths.thumbnails}/${itemid}_blur.webp`,
|
||||
og_url: hallSlug
|
||||
? `https://${cfg.main.url.domain}/h/${encodeURIComponent(hallSlug)}/${itemid}`
|
||||
: `https://${cfg.main.url.domain}/${itemid}`,
|
||||
og_description: `Content not visible in current mode`
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1560,12 +1797,10 @@ const f0cklib = {
|
||||
}
|
||||
if (targetSub.tags && targetSub.tags.length > 0) {
|
||||
effectiveItemTags = targetSub.tags;
|
||||
if (!effectiveItemTags.some(t => t.id === 1 || t.id === 2 || t.normalized === 'nsfl')) {
|
||||
const ratingTag = tags.find(t => t.id === 1 || t.id === 2 || t.normalized === 'nsfl');
|
||||
if (ratingTag) {
|
||||
effectiveItemTags = [ratingTag, ...effectiveItemTags];
|
||||
}
|
||||
}
|
||||
} else if (reqIdx === 0) {
|
||||
effectiveItemTags = tags;
|
||||
} else {
|
||||
effectiveItemTags = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1577,7 +1812,7 @@ const f0cklib = {
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`[${new Date().toISOString()}] [GETF0CK_OPT] Fetch complete in ${duration}ms`);
|
||||
|
||||
const isNsfl = cfg.enable_nsfl && effectiveItemTags.some(t => t.id == nsfl_id);
|
||||
const isNsfl = cfg.enable_nsfl && effectiveItemTags.some(t => t.id == nsfl_id || t.normalized === 'nsfl');
|
||||
const isNsfw = effectiveItemTags.some(t => t.id == 2);
|
||||
const isSfw = effectiveItemTags.some(t => t.id == 1);
|
||||
const isTagged = effectiveItemTags.length > 0;
|
||||
@@ -1604,20 +1839,15 @@ const f0cklib = {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if (isAnonSession(session) && !isOwnerOrAdmin && (actitem.visibility || 0) === 0) {
|
||||
const allowedModes = getAnonAllowedModes();
|
||||
let anonBlocked = false;
|
||||
if (isNsfw && !allowedModes.includes('nsfw')) anonBlocked = true;
|
||||
else if (isNsfl && !allowedModes.includes('nsfl')) anonBlocked = true;
|
||||
else if (isUntagged && !allowedModes.includes('untagged')) anonBlocked = true;
|
||||
else if (isSfw && !allowedModes.includes('sfw')) anonBlocked = true;
|
||||
|
||||
// Mode-mismatch visibility check:
|
||||
// Only enforce for members (session users) with an explicit mode preference.
|
||||
// Mode 0=sfw, 1=nsfw, 2=untagged, 3=all
|
||||
const userMode = Number(mode ?? 0);
|
||||
if (userMode !== 3) {
|
||||
let modeBlocked = false;
|
||||
if (userMode === 0 && (isNsfw || isNsfl || (!session && isUntagged && !cfg.websrv.public_untagged))) modeBlocked = true; // SFW mode, item is NSFW or NSFL
|
||||
else if (userMode === 1 && !isNsfw) modeBlocked = true; // NSFW mode, item is not NSFW
|
||||
else if (userMode === 4 && (!cfg.enable_nsfl || !isNsfl)) modeBlocked = true; // NSFL mode, item is not NSFL
|
||||
else if (userMode === 2 && (isTagged || (!session && !cfg.websrv.public_untagged))) modeBlocked = true; // Untagged mode, item has tags
|
||||
|
||||
if (modeBlocked && !isOwnerOrAdmin && actitem.visibility !== 1) {
|
||||
if (anonBlocked) {
|
||||
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
|
||||
return {
|
||||
success: false,
|
||||
@@ -1634,6 +1864,40 @@ const f0cklib = {
|
||||
}
|
||||
}
|
||||
|
||||
// Mode-mismatch visibility check:
|
||||
// Mode 0=sfw, 1=nsfw, 2=untagged, 3=all
|
||||
const userMode = Number(mode ?? 0);
|
||||
if (!bypass_filter) {
|
||||
let modeBlocked = false;
|
||||
const untaggedDisallowed = (!session && !cfg.websrv.public_untagged) || (isAnonSession(session) && !getAnonAllowedModes().includes('untagged'));
|
||||
if (safeRatingsArr && safeRatingsArr.length > 0) {
|
||||
const itemRatingName = isNsfl ? 'nsfl' : (isNsfw ? 'nsfw' : (isSfw ? 'sfw' : 'untagged'));
|
||||
if (!safeRatingsArr.includes(itemRatingName)) modeBlocked = true;
|
||||
} else if (userMode !== 3) {
|
||||
if (userMode === 0 && (isNsfw || isNsfl || (isUntagged && untaggedDisallowed))) modeBlocked = true; // SFW mode, item is NSFW or NSFL
|
||||
else if (userMode === 1 && !isNsfw) modeBlocked = true; // NSFW mode, item is not NSFW
|
||||
else if (userMode === 4 && (!cfg.enable_nsfl || !isNsfl)) modeBlocked = true; // NSFL mode, item is not NSFL
|
||||
else if (userMode === 2 && (isTagged || untaggedDisallowed)) modeBlocked = true; // Untagged mode, item has tags
|
||||
}
|
||||
|
||||
if (modeBlocked) {
|
||||
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
|
||||
return {
|
||||
success: false,
|
||||
message: "Sorry, this post is currently not visible.",
|
||||
item: {
|
||||
id: itemid,
|
||||
slug: actitem.slug || null,
|
||||
og_thumbnail: `${cfg.websrv.paths.thumbnails}/${itemid}${isNsfw ? '_blur' : ''}.webp`,
|
||||
og_url: hallSlug
|
||||
? `https://${cfg.main.url.domain}/h/${encodeURIComponent(hallSlug)}/${itemid}`
|
||||
: `https://${cfg.main.url.domain}/${itemid}`,
|
||||
og_description: `Content not visible in current mode`
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (getEnableItemSlugs() && !actitem.slug) {
|
||||
actitem.slug = lib.generateSlug(11);
|
||||
db`UPDATE items SET slug = ${actitem.slug} WHERE id = ${actitem.id} AND (slug IS NULL OR slug = '')`.catch(e => console.error('[AUTO_SLUG] Failed DB update:', e.message));
|
||||
@@ -1796,7 +2060,7 @@ const f0cklib = {
|
||||
AND items.title ILIKE ${'%' + titleQuery + '%'}
|
||||
AND items.title IS NOT NULL
|
||||
${mimeSQL}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
|
||||
ORDER BY random()
|
||||
LIMIT 1
|
||||
@@ -1817,7 +2081,7 @@ const f0cklib = {
|
||||
and items.active = true
|
||||
and coalesce(items.visibility, 0) = 0
|
||||
${mimeSQL}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
group by items.id
|
||||
order by random()
|
||||
limit 1
|
||||
@@ -1860,7 +2124,7 @@ const f0cklib = {
|
||||
${user ? db`and items.username ilike ${user}` : db``}
|
||||
${hall ? db`and items.id in (select item_id from halls_assign ha join halls h on h.id = ha.hall_id where h.slug = ${hall})` : db``}
|
||||
${mimeSQL}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
|
||||
group by items.id, tags.tag
|
||||
order by random()
|
||||
@@ -1880,7 +2144,7 @@ const f0cklib = {
|
||||
and items.active = true
|
||||
and coalesce(items.visibility, 0) = 0
|
||||
${mimeSQL}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
${(!session || isAnonSession(session)) && getGlobalfilter(session) ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter(session))}))` : db``}
|
||||
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
|
||||
order by random()
|
||||
limit 1
|
||||
@@ -1906,13 +2170,13 @@ const f0cklib = {
|
||||
// When multi-rating SQL is active, use it directly. Otherwise use the tag-join optimisation.
|
||||
const globalModeQuery = modequery;
|
||||
// tagId optimisation only applies for single native modes for logged-in users (not multi-rating or guest mode)
|
||||
const tagId = session && !multiRatingSQL && (mode === 0 || mode === 1 || mode === 4)
|
||||
const tagId = session && !isAnonSession(session) && !multiRatingSQL && (mode === 0 || mode === 1 || mode === 4)
|
||||
? (mode === 4 ? (cfg.nsfl_tag_id || 3) : (mode === 1 ? 2 : 1))
|
||||
: null;
|
||||
// If audio is included, we avoid the strict tagId optimization to ensure audio is visible
|
||||
const useTagIdOpt = tagId && !mimeParts.includes('audio');
|
||||
const nsfpIds = cfg.nsfp || [];
|
||||
const checkFilter = !session && nsfpIds.length > 0;
|
||||
const checkFilter = (!session || isAnonSession(session)) && nsfpIds.length > 0;
|
||||
|
||||
// Use a single uniform query with ORDER BY random()
|
||||
// For 30k-100k items, this is performant enough and much more reliable than seeking.
|
||||
@@ -2452,7 +2716,7 @@ const f0cklib = {
|
||||
getRandomRecommendations: async ({ limit = 20, mode, ratings, session, exclude, user_id, is_admin, mime, exclude_ids } = {}) => {
|
||||
const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null;
|
||||
const modequery = computeBaseMode(mode, ratingsArr, session);
|
||||
const globalfilter = !session ? getGlobalfilter() : null;
|
||||
const globalfilter = (!session || isAnonSession(session)) ? getGlobalfilter(session) : null;
|
||||
const excludedTags = session && exclude ? (exclude || []) : [];
|
||||
const maxLimit = Math.min(Math.max(1, Number(limit) || 20), 50);
|
||||
|
||||
@@ -2741,7 +3005,7 @@ const f0cklib = {
|
||||
} = {}) => {
|
||||
const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null;
|
||||
const modequery = computeBaseMode(mode, ratingsArr, session);
|
||||
const globalfilter = !session ? getGlobalfilter() : null;
|
||||
const globalfilter = (!session || isAnonSession(session)) ? getGlobalfilter(session) : null;
|
||||
const excludedTags = session && exclude ? (exclude || []) : [];
|
||||
const maxLimit = Math.min(Math.max(1, Number(limit) || 20), 50);
|
||||
|
||||
@@ -3059,7 +3323,7 @@ const f0cklib = {
|
||||
|
||||
const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null;
|
||||
const modequery = computeBaseMode(mode, ratingsArr, session);
|
||||
const globalfilter = !session ? getGlobalfilter() : null;
|
||||
const globalfilter = (!session || isAnonSession(session)) ? getGlobalfilter(session) : null;
|
||||
const excludedTags = session && exclude ? (exclude || []) : [];
|
||||
const maxLimit = Math.min(Math.max(1, Number(limit) || 20), 50);
|
||||
const numOffset = Math.max(0, Number(offset) || 0);
|
||||
|
||||
Reference in New Issue
Block a user