diff --git a/public/s/js/comments.js b/public/s/js/comments.js index 3789876..cd99de1 100644 --- a/public/s/js/comments.js +++ b/public/s/js/comments.js @@ -757,10 +757,11 @@ class CommentSystem { // Render skeleton (Result: Layout visible immediately) // Skip when preserveScroll=true (tab re-focus refresh): the user already sees comments, // so wiping the DOM causes the browser to lose the #c anchor element and auto-scroll to top. - // Only render skeleton for logged-in users to avoid flashing "Login to comment" before comments/permissions load. - if (!scrollToId && !preserveScroll && this.user) { - this.render([], this.user, initialIsSubscribed); - this.restoreState(state); + if (!scrollToId && !preserveScroll) { + if (this.user || !this.container.querySelector('.login-placeholder, .comment-input, .lock-notice')) { + this.render([], this.user, initialIsSubscribed); + this.restoreState(state); + } } try { diff --git a/src/inc/routeinc/f0cklib.mjs b/src/inc/routeinc/f0cklib.mjs index 5e4f64b..15aac10 100644 --- a/src/inc/routeinc/f0cklib.mjs +++ b/src/inc/routeinc/f0cklib.mjs @@ -18,23 +18,35 @@ const computeBaseMode = (mode, ratings, session) => { const effMode = Number(mode ?? 0); const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null; + // For guest sessions, sanitize ratingsArr to only allow permitted ratings + let safeRatingsArr = ratingsArr; + if (!session && ratingsArr) { + const allowedRatings = ['sfw']; + if (cfg.websrv.public_nsfw) allowedRatings.push('nsfw'); + if (cfg.websrv.public_untagged) allowedRatings.push('untagged'); + safeRatingsArr = ratingsArr.filter(r => allowedRatings.includes(r)); + if (safeRatingsArr.length === 0) { + return "1 = 0"; + } + } + let baseMode; if (effMode === 2) { - if (ratingsArr && ratingsArr.includes('untagged') && ratingsArr.length > 1) { - baseMode = lib.getMultiRatingMode(ratingsArr); + if (safeRatingsArr && safeRatingsArr.includes('untagged') && safeRatingsArr.length > 1) { + baseMode = lib.getMultiRatingMode(safeRatingsArr); } else { baseMode = lib.getMode(2); } } else if (effMode === 3) { - baseMode = (ratingsArr && ratingsArr.length > 1) ? lib.getMultiRatingMode(ratingsArr) : lib.getMode(3); + baseMode = (safeRatingsArr && safeRatingsArr.length > 1) ? lib.getMultiRatingMode(safeRatingsArr) : lib.getMode(3); } else { - const multiRatingSQL = ratingsArr ? lib.getMultiRatingMode(ratingsArr) : null; + const multiRatingSQL = safeRatingsArr ? lib.getMultiRatingMode(safeRatingsArr) : null; baseMode = multiRatingSQL ?? lib.getMode(effMode); } if (!session) { const nsflId = parseInt(cfg.nsfl_tag_id, 10) || 3; - if ((effMode === 0 || effMode === 3 || mode === undefined || mode === null) && (!ratingsArr || ratingsArr.length <= 1)) { + if ((effMode === 0 || effMode === 3 || mode === undefined || mode === null) && (!safeRatingsArr || safeRatingsArr.length <= 1)) { if (cfg.websrv.public_nsfw) { baseMode = cfg.websrv.public_untagged ? `(items.id in (select item_id from tags_assign where tag_id in (1, 2)) or not exists (select 1 from tags_assign where item_id = items.id and tag_id in (1, 2, ${nsflId})))` @@ -44,10 +56,16 @@ const computeBaseMode = (mode, ratings, session) => { ? `(items.id in (select item_id from tags_assign where tag_id = 1) or not exists (select 1 from tags_assign where item_id = items.id and tag_id in (1, 2, ${nsflId})))` : "items.id in (select item_id from tags_assign where tag_id = 1)"; } - } else if (!cfg.websrv.public_untagged) { - if (effMode === 2 || (ratingsArr && ratingsArr.length === 1 && ratingsArr[0] === 'untagged')) { + } else if (effMode === 2) { + if (!cfg.websrv.public_untagged) { baseMode = "1 = 0"; } + } else if (effMode === 1) { + if (!cfg.websrv.public_nsfw) { + baseMode = "1 = 0"; + } + } else if (effMode === 4) { + baseMode = "1 = 0"; } } return baseMode; @@ -824,12 +842,7 @@ export default { const effMode = Number(mode ?? 0); const nsflId = parseInt(cfg.nsfl_tag_id, 10) || 3; - const itemModeQuery = session - ? computeBaseMode(mode, ratings, session) - : (cfg.websrv.public_nsfw - ? `not exists (select 1 from tags_assign where item_id = items.id and tag_id = ${nsflId})` - : `not exists (select 1 from tags_assign where item_id = items.id and tag_id in (2, ${nsflId}))` - ); + const itemModeQuery = computeBaseMode(mode, ratings, session); let tagFilter = db``; let titleFilter = db``; @@ -969,12 +982,22 @@ export default { last_viewed = now() `.catch(e => console.error('Failed to track view:', e)); } - // Guest global filter check for public items (unlisted items requested by direct link/slug bypass guest rating blocks) - if (!session && getGlobalfilter() && (actitem.visibility || 0) === 0) { - const filteredItem = await db` - select 1 from tags_assign where item_id = ${itemid} and (${db.unsafe(getGlobalfilter())}) limit 1 - `; - if (filteredItem.length > 0) { + // Guest rating restriction check for public items (unlisted items requested by direct link/slug bypass guest rating blocks) + if (!session && (actitem.visibility || 0) === 0) { + let blocked = false; + if (getGlobalfilter()) { + const filteredItem = await db` + select 1 from tags_assign where item_id = ${itemid} and (${db.unsafe(getGlobalfilter())}) limit 1 + `; + if (filteredItem.length > 0) blocked = true; + } + if (!blocked && !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 + `; + if (ratingTag.length === 0) blocked = true; + } + if (blocked) { const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall; return { success: false, @@ -1157,12 +1180,13 @@ export default { const isNsfw = tags.some(t => t.id == 2); const isSfw = tags.some(t => t.id == 1); const isTagged = tags.length > 0; + const isUntagged = !isSfw && !isNsfw && !isNsfl; // Guest rating restriction check for public uploads (visibility === 0) - // Direct links to public unrated/untagged posts are allowed for guests. if (!session && !isOwnerOrAdmin && (actitem.visibility || 0) === 0) { let guestBlocked = false; if (isNsfw && !cfg.websrv.public_nsfw) guestBlocked = true; else if (isNsfl) guestBlocked = true; + else if (isUntagged && !cfg.websrv.public_untagged) guestBlocked = true; if (guestBlocked) { const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall; @@ -1187,10 +1211,10 @@ export default { const userMode = Number(mode ?? 0); if (userMode !== 3) { let modeBlocked = false; - if (userMode === 0 && (isNsfw || isNsfl)) modeBlocked = true; // SFW mode, item is NSFW or NSFL + 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) modeBlocked = true; // Untagged mode, item has tags + else if (userMode === 2 && (isTagged || (!session && !cfg.websrv.public_untagged))) modeBlocked = true; // Untagged mode, item has tags if (modeBlocked && !isOwnerOrAdmin && actitem.visibility !== 1) { const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall; diff --git a/src/inc/routes/comments.mjs b/src/inc/routes/comments.mjs index a1c2ce9..15cb3c9 100644 --- a/src/inc/routes/comments.mjs +++ b/src/inc/routes/comments.mjs @@ -1016,7 +1016,7 @@ export default (router, tpl) => { const multiRatingSQL = (ratingsArr && ratingsArr.length > 0) ? lib.getMultiRatingMode(ratingsArr) : null; // Build mode SQL — replace items.id alias with i.id used in the activity query - const modequery = (multiRatingSQL ?? lib.getMode(mode)).replace(/items\.id/g, 'i.id'); + const modequery = f0cklib.computeBaseMode(mode, ratingsArr, req.session).replace(/items\.id/g, 'i.id'); const globalfilterTags = cfg.websrv.public_nsfw ? (cfg.nsfp || []).filter(id => id !== 2) : (cfg.nsfp || []); const globalfilter = globalfilterTags.length ? globalfilterTags.map(n => `tag_id = ${n}`).join(' or ') : null; const excludedTags = req.session ? (req.session.excluded_tags || []) : []; diff --git a/src/index.mjs b/src/index.mjs index 9f79ea4..09412b3 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1463,6 +1463,8 @@ process.on('uncaughtException', err => { default_font: cfg.websrv.default_font || "", site_description: cfg.websrv.description || "The webs dumpster", enable_nsfl: !!cfg.enable_nsfl, + public_nsfw: !!cfg.websrv.public_nsfw, + public_untagged: !!cfg.websrv.public_untagged, onara: !!(cfg.onara !== undefined ? cfg.onara : cfg.websrv?.onara), is_onara_item: false, enable_private_uploads: cfg.enable_private_uploads !== false, diff --git a/views/item-partial-legacy.html b/views/item-partial-legacy.html index 2e0649b..bd86119 100644 --- a/views/item-partial-legacy.html +++ b/views/item-partial-legacy.html @@ -227,10 +227,14 @@ @if(session) data-user="{{ session.user }}" @endif @if(session && (session.admin || session.is_moderator)) data-is-admin="true" @endif @if(item.is_comments_locked) data-is-locked="true" @endif> - @if(session && !item.is_comments_locked) + @if(item.is_comments_locked && !is_mod_or_admin) +
🔒 Comments are disabled on this thread.
+ @elseif(session)
+ @else +
Login to comment
@endif @endif diff --git a/views/item-partial-modern.html b/views/item-partial-modern.html index 621a7a9..73c0763 100644 --- a/views/item-partial-modern.html +++ b/views/item-partial-modern.html @@ -19,10 +19,14 @@ @if(session) data-user="{{ session.user }}" @endif @if(session && is_mod_or_admin) data-is-admin="true" @endif @if(item.is_comments_locked) data-is-locked="true" @endif> - @if(session && !item.is_comments_locked) + @if(item.is_comments_locked && !is_mod_or_admin) +
🔒 Comments are disabled on this thread.
+ @elseif(session)
+ @else +
Login to comment
@endif @endif diff --git a/views/snippets/footer.html b/views/snippets/footer.html index bd7a601..6cb7397 100644 --- a/views/snippets/footer.html +++ b/views/snippets/footer.html @@ -139,7 +139,7 @@ @endif @if(private_society && !session)