diff --git a/public/s/js/comments.js b/public/s/js/comments.js index cd99de1..b7b3882 100644 --- a/public/s/js/comments.js +++ b/public/s/js/comments.js @@ -2223,14 +2223,29 @@ class CommentSystem { } const bannerEnabled = window.f0ckCommentBannerEnabled !== false && window.f0ckSession?.comment_banner_enabled !== false; - const bannerStyle = (bannerEnabled && comment.banner_file && comment.banner_file !== 'null') + let bannerStyle = (bannerEnabled && comment.banner_file && comment.banner_file !== 'null') ? `style="--author-banner: url('/a/${comment.banner_file}'); --author-banner-position: ${comment.banner_position === 'center' ? 'center top' : (comment.banner_position || 'center top')}; --author-banner-size: ${(comment.banner_size && comment.banner_size !== 'cover') ? comment.banner_size : '100% auto'}; --author-banner-repeat: no-repeat;"` : ''; const authorUserId = comment.user_id ?? (comment.username && window.f0ckSession && comment.username.toLowerCase() === (window.f0ckSession.user || '').toLowerCase() ? (window.f0ckSession.id || window.f0ckSession.user_id) : null); const authorUsernameColor = comment.username_color || (comment.username && window.f0ckSession && comment.username.toLowerCase() === (window.f0ckSession.user || '').toLowerCase() ? window.f0ckSession.username_color : null); - return `
${comment.username ? `` : ''}${comment.username ? `` : ''}
${pinnedBadge}${comment.username ? `${this.escapeHtml(comment.display_name || comment.username)}` : 'System'}${contextMarker}${backlinkHtml}
${timeAgo}
${content}
${this.renderCommentAttachments(comment.files, comment.content)}${this.renderCommentPoll(comment.poll, comment.id, comment.username)}
#${comment.id}
${repliesHtml}`; + const isAnonGuest = window.f0ckSession?.guest_anonymize && !window.f0ckSession?.logged_in; + if (isAnonGuest) bannerStyle = ''; + + const avatarHtml = isAnonGuest + ? `
` + : `
${comment.username ? `` : ''}${comment.username ? `` : ''}
`; + + const authorHtml = isAnonGuest + ? `anonymous` + : (comment.username + ? `${this.escapeHtml(comment.display_name || comment.username)}` + : 'System'); + + const anonDataAttrs = isAnonGuest ? '' : `data-username="${comment.username}" data-display="${this.escapeHtml(comment.display_name || '')}"`; + + return `
${avatarHtml}
${pinnedBadge}${authorHtml}${contextMarker}${backlinkHtml}
${timeAgo}
${content}
${this.renderCommentAttachments(comment.files, comment.content)}${this.renderCommentPoll(comment.poll, comment.id, isAnonGuest ? null : comment.username)}
#${comment.id}
${repliesHtml}`; } timeAgo(date) { diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index 866044a..2bc703a 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -6526,7 +6526,7 @@ window.cancelAnimFrame = (function () { } }; const initExcludedTagsModal = () => { - if (window.f0ckSession && !window.f0ckSession.logged_in) return; + const isLoggedIn = !!(window.f0ckSession && window.f0ckSession.logged_in); const overlay = document.getElementById('excluded-tags-overlay'); if (!overlay) return; @@ -6544,9 +6544,9 @@ window.cancelAnimFrame = (function () { overlay.offsetHeight; overlay.classList.add('visible'); document.body.style.overflow = 'hidden'; - renderTags(); + if (isLoggedIn) renderTags(); if (window.syncRatingButtonUI) window.syncRatingButtonUI(); - if (window.innerWidth > 768) input.focus(); + if (isLoggedIn && window.innerWidth > 768) input.focus(); } else { overlay.classList.remove('visible'); document.body.style.overflow = ''; @@ -6611,149 +6611,105 @@ window.cancelAnimFrame = (function () { if (e.target === overlay) toggleModal(false); }); - const addTag = async () => { - const tagname = input.value.trim(); - if (!tagname) return; - suggestions.style.display = 'none'; - try { - const res = await fetch('/api/v2/settings/excluded_tags', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - tagname - }) - }); - const data = await res.json(); - if (data.success) { - renderTags(); - input.value = ''; - } else { - window.flashMessage(data.msg || 'Error adding tag', 3000, 'error'); - } - } catch (e) { - console.error(e); - } finally { - if (window.innerWidth > 768) input.focus(); - } - }; - input.addEventListener('keydown', e => { - if (e.key === 'Enter') { - e.preventDefault(); - // If a suggestion is highlighted, use it - if (highlightIdx >= 0) { - const items = suggestions.querySelectorAll('.tag-suggestion-item'); - if (items[highlightIdx]) { - input.value = items[highlightIdx].querySelector('.tag-suggestion-name').textContent; - } - } + if (isLoggedIn) { + const addTag = async () => { + const tagname = input.value.trim(); + if (!tagname) return; suggestions.style.display = 'none'; - addTag(); - } - }); - - list.addEventListener('click', async e => { - if (e.target.classList.contains('remove-excluded-tag')) { - e.preventDefault(); - const tag = e.target.getAttribute('data-tag'); try { - const res = await fetch(`/api/v2/settings/excluded_tags/${encodeURIComponent(tag)}`, { - method: 'DELETE' + const res = await fetch('/api/v2/settings/excluded_tags', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tagname }) }); const data = await res.json(); - if (data.success) renderTags(); + if (data.success) { renderTags(); input.value = ''; } + else window.flashMessage(data.msg || 'Error adding tag', 3000, 'error'); } catch (e) { console.error(e); + } finally { + if (window.innerWidth > 768) input.focus(); } - } - }); - - // Autocomplete with dropdown suggestions - let debounceTimer = null; - let highlightIdx = -1; - - const renderSuggestions = (items) => { - suggestions.innerHTML = ''; - highlightIdx = -1; - if (!items.length) { - suggestions.style.display = 'none'; - return; - } - items.forEach(s => { - const div = document.createElement('div'); - div.className = 'tag-suggestion-item'; - - const name = document.createElement('span'); - name.className = 'tag-suggestion-name'; - name.textContent = s.tag; - - const meta = document.createElement('span'); - meta.className = 'tag-suggestion-meta'; - meta.textContent = `${s.tagged}× · ${s.score.toFixed(2)}`; - - div.appendChild(name); - div.appendChild(meta); - - div.addEventListener('mousedown', (e) => { + }; + input.addEventListener('keydown', e => { + if (e.key === 'Enter') { e.preventDefault(); - input.value = s.tag; + if (highlightIdx >= 0) { + const items = suggestions.querySelectorAll('.tag-suggestion-item'); + if (items[highlightIdx]) input.value = items[highlightIdx].querySelector('.tag-suggestion-name').textContent; + } suggestions.style.display = 'none'; addTag(); - }); - suggestions.appendChild(div); - }); - suggestions.style.display = 'block'; - }; - - input.addEventListener('input', () => { - const q = input.value.trim(); - highlightIdx = -1; - if (q.length < 1) { - suggestions.style.display = 'none'; - return; - } - if (debounceTimer) clearTimeout(debounceTimer); - debounceTimer = setTimeout(async () => { - try { - const res = await fetch(`/api/v2/tags/suggest?q=${encodeURIComponent(q)}`); - const json = await res.json(); - if (json.success && json.suggestions) { - renderSuggestions(json.suggestions.slice(0, 8)); - } else { - suggestions.style.display = 'none'; - } - } catch (e) { - suggestions.style.display = 'none'; } - }, 300); - }); + }); - input.addEventListener('keydown', e => { - const items = suggestions.querySelectorAll('.tag-suggestion-item'); - if (!items.length || suggestions.style.display === 'none') return; + list.addEventListener('click', async e => { + if (e.target.classList.contains('remove-excluded-tag')) { + e.preventDefault(); + const tag = e.target.getAttribute('data-tag'); + try { + const res = await fetch(`/api/v2/settings/excluded_tags/${encodeURIComponent(tag)}`, { method: 'DELETE' }); + const data = await res.json(); + if (data.success) renderTags(); + } catch (e) { console.error(e); } + } + }); - if (e.key === 'ArrowDown') { - e.preventDefault(); - if (highlightIdx >= 0 && highlightIdx < items.length) items[highlightIdx].classList.remove('active'); - highlightIdx = highlightIdx < items.length - 1 ? highlightIdx + 1 : 0; - items[highlightIdx].classList.add('active'); - items[highlightIdx].scrollIntoView({ block: 'nearest' }); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - if (highlightIdx >= 0 && highlightIdx < items.length) items[highlightIdx].classList.remove('active'); - highlightIdx = highlightIdx > 0 ? highlightIdx - 1 : items.length - 1; - items[highlightIdx].classList.add('active'); - items[highlightIdx].scrollIntoView({ block: 'nearest' }); - } else if (e.key === 'Escape') { - suggestions.style.display = 'none'; + // Autocomplete with dropdown suggestions + let debounceTimer = null; + let highlightIdx = -1; + + const renderSuggestions = (items) => { + suggestions.innerHTML = ''; highlightIdx = -1; - } - }); + if (!items.length) { suggestions.style.display = 'none'; return; } + items.forEach(s => { + const div = document.createElement('div'); + div.className = 'tag-suggestion-item'; + const name = document.createElement('span'); name.className = 'tag-suggestion-name'; name.textContent = s.tag; + const meta = document.createElement('span'); meta.className = 'tag-suggestion-meta'; meta.textContent = `${s.tagged}× · ${s.score.toFixed(2)}`; + div.appendChild(name); div.appendChild(meta); + div.addEventListener('mousedown', (e) => { e.preventDefault(); input.value = s.tag; suggestions.style.display = 'none'; addTag(); }); + suggestions.appendChild(div); + }); + suggestions.style.display = 'block'; + }; - input.addEventListener('blur', () => { - setTimeout(() => { suggestions.style.display = 'none'; }, 150); - }); + input.addEventListener('input', () => { + const q = input.value.trim(); + highlightIdx = -1; + if (q.length < 1) { suggestions.style.display = 'none'; return; } + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(async () => { + try { + const res = await fetch(`/api/v2/tags/suggest?q=${encodeURIComponent(q)}`); + const json = await res.json(); + if (json.success && json.suggestions) renderSuggestions(json.suggestions.slice(0, 8)); + else suggestions.style.display = 'none'; + } catch (e) { suggestions.style.display = 'none'; } + }, 300); + }); + + input.addEventListener('keydown', e => { + const items = suggestions.querySelectorAll('.tag-suggestion-item'); + if (!items.length || suggestions.style.display === 'none') return; + if (e.key === 'ArrowDown') { + e.preventDefault(); + if (highlightIdx >= 0 && highlightIdx < items.length) items[highlightIdx].classList.remove('active'); + highlightIdx = highlightIdx < items.length - 1 ? highlightIdx + 1 : 0; + items[highlightIdx].classList.add('active'); items[highlightIdx].scrollIntoView({ block: 'nearest' }); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + if (highlightIdx >= 0 && highlightIdx < items.length) items[highlightIdx].classList.remove('active'); + highlightIdx = highlightIdx > 0 ? highlightIdx - 1 : items.length - 1; + items[highlightIdx].classList.add('active'); items[highlightIdx].scrollIntoView({ block: 'nearest' }); + } else if (e.key === 'Escape') { + suggestions.style.display = 'none'; highlightIdx = -1; + } + }); + + input.addEventListener('blur', () => { setTimeout(() => { suggestions.style.display = 'none'; }, 150); }); + } // end isLoggedIn tag block // ESC to close, "e" to open document.addEventListener('keydown', (e) => { @@ -6922,30 +6878,39 @@ window.cancelAnimFrame = (function () { const THUMB_SIZES = ['s', 'm', 'l', 'xl']; const gridSizeBtns = overlay.querySelectorAll('.nav-grid-size-btn'); - const applyNavThumbSize = (size) => { - if (!THUMB_SIZES.includes(size)) size = 'm'; + const applyNavThumbSize = (size, persist = true) => { + if (!THUMB_SIZES.includes(size)) size = window.f0ckDefaultThumbSize || 'm'; document.documentElement.dataset.thumbSize = size; - localStorage.setItem(THUMB_SIZE_KEY, size); + if (persist) localStorage.setItem(THUMB_SIZE_KEY, size); gridSizeBtns.forEach(b => b.classList.toggle('active', b.dataset.thumbSize === size)); }; // Sync active state each time the modal opens if (filterBtn) { filterBtn.addEventListener('click', () => { - const cur = localStorage.getItem(THUMB_SIZE_KEY) || 'm'; + const cur = localStorage.getItem(THUMB_SIZE_KEY) || window.f0ckDefaultThumbSize || 'm'; gridSizeBtns.forEach(b => b.classList.toggle('active', b.dataset.thumbSize === cur)); }); } gridSizeBtns.forEach(btn => { - btn.addEventListener('click', () => applyNavThumbSize(btn.dataset.thumbSize)); + btn.addEventListener('click', () => applyNavThumbSize(btn.dataset.thumbSize, true)); }); - // Apply persisted size immediately on init (every page load) - applyNavThumbSize(localStorage.getItem(THUMB_SIZE_KEY) || 'm'); + // Apply on init — do NOT persist so config default stays effective until user chooses + applyNavThumbSize(localStorage.getItem(THUMB_SIZE_KEY) || window.f0ckDefaultThumbSize || 'm', false); + }; + + // ── Thumb size init (runs on every page, independently of the filter modal) ── + const initThumbSize = () => { + const THUMB_SIZE_KEY = 'scroller_thumb_size'; + const THUMB_SIZES = ['s', 'm', 'l', 'xl']; + const size = localStorage.getItem(THUMB_SIZE_KEY) || window.f0ckDefaultThumbSize || 'm'; + document.documentElement.dataset.thumbSize = THUMB_SIZES.includes(size) ? size : (window.f0ckDefaultThumbSize || 'm'); }; document.addEventListener('DOMContentLoaded', () => { + initThumbSize(); // always runs — applies data-thumb-size to initSearch(); initExcludedTagsModal(); if (window.updateFilterBadge) window.updateFilterBadge(); diff --git a/public/s/js/scroller.js b/public/s/js/scroller.js index 56bb7f1..a89e8d6 100644 --- a/public/s/js/scroller.js +++ b/public/s/js/scroller.js @@ -100,27 +100,27 @@ // ── Grid / Thumbnail size ───────────────────────────────────────────────── const THUMB_SIZES = ['s', 'm', 'l', 'xl']; - let thumbSize = localStorage.getItem(THUMB_SIZE_KEY) || 'm'; - if (!THUMB_SIZES.includes(thumbSize)) thumbSize = 'm'; + let thumbSize = localStorage.getItem(THUMB_SIZE_KEY) || window.f0ckDefaultThumbSize || 'm'; + if (!THUMB_SIZES.includes(thumbSize)) thumbSize = window.f0ckDefaultThumbSize || 'm'; - function applyThumbSize(size) { + function applyThumbSize(size, persist = true) { thumbSize = size; document.documentElement.dataset.thumbSize = size; - localStorage.setItem(THUMB_SIZE_KEY, size); + if (persist) localStorage.setItem(THUMB_SIZE_KEY, size); // Sync pill active state whenever called outside the click handler document.querySelectorAll('#thumb-size-pills .filter-pill').forEach(p => p.classList.toggle('active', p.dataset.thumbSize === size) ); } - // Apply persisted size on load - applyThumbSize(thumbSize); + // Apply on init — do NOT persist so config default stays effective until user chooses + applyThumbSize(thumbSize, false); // Wire pill click events const thumbSizePillsEl = document.getElementById('thumb-size-pills'); if (thumbSizePillsEl) { thumbSizePillsEl.querySelectorAll('.filter-pill').forEach(pill => { pill.addEventListener('click', () => { - applyThumbSize(pill.dataset.thumbSize); + applyThumbSize(pill.dataset.thumbSize, true); }); }); } @@ -1373,7 +1373,8 @@ const meta = document.createElement('div'); meta.className = 'scroll-meta'; // esc() the color value: a raw '"' in username_color would break out of the // style attribute and allow arbitrary HTML injection (XSS). - const colorStyle = item.username_color ? `color:${esc(item.username_color)}` : ''; + const isAnonGuest = window.f0ckSession?.guest_anonymize && !window.f0ckSession?.logged_in; + const colorStyle = (!isAnonGuest && item.username_color) ? `color:${esc(item.username_color)}` : ''; const ratingHtml = `${esc(item.rating_label)}`; const ocHtml = item.is_oc ? ` OC` : ''; const badgesHtml = `
${ratingHtml}${ocHtml}
`; @@ -1383,10 +1384,15 @@ item.tags.split(', ').map(t => `${esc(t.trim())}`).join('') }` : ''; - meta.innerHTML = ` -
- ${badgesHtml} -
+ const userMetaHtml = isAnonGuest + ? `
+ +
+
anonymous
+
${esc(item.stamp ? timeAgo(item.stamp * 1000) : (item.timeago || ''))}
+
+
` + : `
@@ -1396,7 +1402,11 @@
${esc(item.stamp ? timeAgo(item.stamp * 1000) : (item.timeago || ''))}
-
+
`; + meta.innerHTML = ` +
+ ${badgesHtml} + ${userMetaHtml} ${tagsHtml} ${item.is_external && item.external_board && item.external_tid ? `/${item.external_board}/thread/${item.external_tid}` @@ -2187,10 +2197,11 @@ function renderCommentEl(c, canReply) { const el = document.createElement('div'); el.className = 'comment-item'; el.dataset.commentId = c.id || ''; - const av = c.avatar_file ? `/a/${c.avatar_file}` : (c.avatar ? `/t/${c.avatar}.webp` : '/a/default.png'); - const nc = c.username_color ? `color:${esc(c.username_color)}` : ''; + const isAnonGuest = window.f0ckSession?.guest_anonymize && !window.f0ckSession?.logged_in; + const av = isAnonGuest ? '/a/default.png' : (c.avatar_file ? `/a/${c.avatar_file}` : (c.avatar ? `/t/${c.avatar}.webp` : '/a/default.png')); + const nc = (!isAnonGuest && c.username_color) ? `color:${esc(c.username_color)}` : ''; const _i = window.f0ckI18n || {}; - const uname = c.display_name || c.username || 'anon'; + const uname = isAnonGuest ? 'anonymous' : (c.display_name || c.username || 'anon'); el.innerHTML = `
@@ -2198,7 +2209,7 @@
${renderCommentContent(c.content || '')}
${c.created_at ? timeAgo(c.created_at) : (_i.ta_just_now || _i.just_now || 'just now')} - ${canReply ? ` + ${(!isAnonGuest && canReply) ? ` ` : ''} diff --git a/public/s/js/sidebar-activity.js b/public/s/js/sidebar-activity.js index 743ab3f..4dcd469 100644 --- a/public/s/js/sidebar-activity.js +++ b/public/s/js/sidebar-activity.js @@ -562,15 +562,22 @@ ? `style="--author-banner: url('/a/${bannerFile}'); --author-banner-position: ${bannerPos === 'center' ? 'center top' : (bannerPos || 'center top')}; --author-banner-size: ${(bannerSz && bannerSz !== 'cover') ? bannerSz : '100% auto'}; --author-banner-repeat: no-repeat;"` : ''; + const isAnonGuest = window.f0ckSession?.guest_anonymize && !window.f0ckSession?.logged_in; + const effectiveBannerStyle = isAnonGuest ? '' : bannerStyle; + const authorAvatarHtml = isAnonGuest + ? `` + : ``; + const authorNameHtml = isAnonGuest + ? `anonymous` + : `${escapeHtml(c.display_name || c.username)}`; + return ` -
+
- - - - ${escapeHtml(c.display_name || c.username)} + ${authorAvatarHtml} + ${authorNameHtml}
${timeStr}
diff --git a/src/inc/routes/comments.mjs b/src/inc/routes/comments.mjs index 15cb3c9..b53464c 100644 --- a/src/inc/routes/comments.mjs +++ b/src/inc/routes/comments.mjs @@ -63,11 +63,22 @@ export default (router, tpl) => { } // Transform for frontend if needed, or send as is + const anonymize = !req.session && cfg.main.guest_anonymize; + const outComments = anonymize + ? comments.map(c => ({ + ...c, + username: 'anonymous', + display_name: null, + username_color: null, + avatar: null, + avatar_file: null + })) + : comments; return res.reply({ headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify({ success: true, - comments, + comments: outComments, is_subscribed, is_locked, user_id: req.session ? req.session.id : null, @@ -122,6 +133,9 @@ export default (router, tpl) => { // Browse User Comments router.get(/\/user\/(?[^\/]+)\/comments/, async (req, res) => { + if (cfg.main.guest_anonymize && !req.session) { + return res.redirect('/login'); + } const user = decodeURIComponent(req.params.user); try { @@ -1135,6 +1149,7 @@ export default (router, tpl) => { } } + const isAnonymized = !req.session && cfg.main.guest_anonymize; const processedComments = comments.map(c => { let ratingLabel = '?'; let ratingClass = 'untagged'; @@ -1144,9 +1159,6 @@ export default (router, tpl) => { const commentContent = (c.content || '').trim(); const commentFiles = filesMap.get(c.id) || []; - // Compute overflow hint: true if content is likely taller than the 80px clamp. - // ~120 chars ≈ 2-3 wrapped lines in the sidebar; any newlines > 2 or file - // attachments (images/video) will also push height above the limit. const isLong = commentContent.length > 120 || commentContent.split('\n').length > 2 || commentFiles.length > 0 @@ -1154,17 +1166,22 @@ export default (router, tpl) => { return { ...c, + username: isAnonymized ? 'anonymous' : c.username, + display_name: isAnonymized ? null : c.display_name, + username_color: isAnonymized ? null : c.username_color, + avatar: isAnonymized ? null : c.avatar, + avatar_file: isAnonymized ? null : c.avatar_file, + banner_file: isAnonymized ? null : c.banner_file, content: commentContent, - username_color: c.username_color, item_rating_class: ratingClass, item_rating_label: ratingLabel, files: commentFiles, poll: pollMap.get(c.id) || null, is_long: isLong - // created_at stays as the raw ISO timestamp so the frontend f0ckTimeAgo can localize it }; }); + if (req.url.qs?.json === 'true' || req.headers['x-requested-with'] === 'XMLHttpRequest') { return res.reply({ headers: { diff --git a/src/inc/routes/index.mjs b/src/inc/routes/index.mjs index 493b7b7..d97a2db 100644 --- a/src/inc/routes/index.mjs +++ b/src/inc/routes/index.mjs @@ -12,6 +12,10 @@ const auth = async (req, res, next) => { export default (router, tpl) => { router.get(/\/user\/(?[^/]+)\/?$/, async (req, res) => { + // When guest anonymization is active, user profiles must not be accessible without a session + if (cfg.main.guest_anonymize && !req.session) { + return res.redirect('/login'); + } const user = decodeURIComponent(req.params.user); const mime = req.cookies.mime !== undefined ? req.cookies.mime : (req.query?.mime || req.url.qs?.mime || null); @@ -223,6 +227,11 @@ export default (router, tpl) => { return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) }); } + // When guest anonymization is active, user gallery pages must require authentication + if (cfg.main.guest_anonymize && !req.session && req.params.user && req.params.mode) { + return res.redirect('/login'); + } + // Auto-persist strict mode from URL to session if it's there if (req.session && (req.query?.strict !== undefined || req.url.qs?.strict !== undefined)) { req.session.strict_mode = (req.query?.strict === '1' || req.url.qs?.strict === '1'); @@ -384,6 +393,10 @@ export default (router, tpl) => { // Hall columns for display data.halls_slugs = Array.isArray(item.halls) ? item.halls.map(h => h.slug).join(',') : ''; data.user_halls_slugs = Array.isArray(item.user_halls) ? item.user_halls.map(h => h.slug).join(',') : ''; + // When guest anonymization is active, suppress source URL from the info modal + if (cfg.main.guest_anonymize && !req.session && item.src) { + item.src = null; + } // Precomputed for template engine compatibility (avoids nested { } inside {{ }}) data.item_rating_class = item.is_nsfl ? 'is-nsfl' : (item.is_nsfw ? 'is-nsfw' : (item.is_sfw ? 'is-sfw' : 'is-untagged')); data.item_rating_label = item.is_nsfl ? 'NSFL' : (item.is_nsfw ? 'NSFW' : (item.is_sfw ? 'SFW' : '?')); diff --git a/src/inc/routes/scroller.mjs b/src/inc/routes/scroller.mjs index 8fa378c..e19f1e7 100644 --- a/src/inc/routes/scroller.mjs +++ b/src/inc/routes/scroller.mjs @@ -377,9 +377,21 @@ export default (router, tpl) => { const lastItem = items[items.length - 1]; const nextCursor = lastItem ? lastItem.id : null; + const isAnonymized = !req.session && cfg.main.guest_anonymize; + const outItems = isAnonymized + ? items.map(item => ({ + ...item, + username: 'anonymous', + display_name: 'anonymous', + avatar: '/a/default.png', + username_color: null, + src_host: '' + })) + : items; + return res.reply({ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store, no-cache' }, - body: JSON.stringify({ success: true, items, nextCursor }) + body: JSON.stringify({ success: true, items: outItems, nextCursor }) }); } catch (e) { console.error('[SCROLLER] Feed error:', e); diff --git a/src/inc/routes/user_halls.mjs b/src/inc/routes/user_halls.mjs index ebac59b..e8b0833 100644 --- a/src/inc/routes/user_halls.mjs +++ b/src/inc/routes/user_halls.mjs @@ -43,6 +43,9 @@ export default (router, tpl) => { // List halls for a user router.get(/^\/user\/(?[^/]+)\/halls\/?$/, async (req, res) => { + if (cfg.main.guest_anonymize && !req.session) { + return res.redirect('/login'); + } if (cfg.websrv.userhalls_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) }); const ownerName = decodeURIComponent(req.params.owner); const mode = req.mode ?? 0; @@ -80,6 +83,9 @@ export default (router, tpl) => { // Item grid for a user hall router.get(/^\/user\/(?[^/]+)\/hall\/(?[^/]+)(?:\/p\/(?\d+))?\/?$/, async (req, res) => { + if (cfg.main.guest_anonymize && !req.session) { + return res.redirect('/login'); + } if (cfg.websrv.userhalls_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) }); const ownerName = decodeURIComponent(req.params.owner); const slug = decodeURIComponent(req.params.slug); @@ -126,6 +132,9 @@ export default (router, tpl) => { // Single item within a user hall router.get(/^\/user\/(?[^/]+)\/hall\/(?[^/]+)\/(?\d+)\/?$/, async (req, res) => { + if (cfg.main.guest_anonymize && !req.session) { + return res.redirect('/login'); + } if (cfg.websrv.userhalls_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) }); const ownerName = decodeURIComponent(req.params.owner); const slug = decodeURIComponent(req.params.slug); @@ -176,6 +185,10 @@ export default (router, tpl) => { data.current_user_hall_slug = (data.tmp && data.tmp.userHall && typeof data.tmp.userHall === 'object') ? data.tmp.userHall.slug : (data.tmp && data.tmp.userHall ? data.tmp.userHall : ''); data.current_user_hall_owner = (data.tmp && data.tmp.userHallOwner) ? data.tmp.userHallOwner : ''; data.item_has_dimensions = !!(item.width && item.height); + // When guest anonymization is active, suppress source URL from the info modal + if (cfg.main.guest_anonymize && !req.session && item.src) { + item.src = null; + } } // Precompute hall display diff --git a/src/inc/routes/user_tags.mjs b/src/inc/routes/user_tags.mjs index f8648f6..6a71928 100644 --- a/src/inc/routes/user_tags.mjs +++ b/src/inc/routes/user_tags.mjs @@ -158,6 +158,9 @@ export default (router, tpl) => { // Main tags page router.get(/^\/user\/(?[^\/]+)\/tags$/, async (req, res) => { + if (cfg.main.guest_anonymize && !req.session) { + return res.redirect('/login'); + } const userParam = decodeURIComponent(req.params.user); const u = await db` diff --git a/src/index.mjs b/src/index.mjs index 09412b3..ff196b0 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1419,6 +1419,7 @@ process.on('uncaughtException', err => { site: cfg.main.url.full, domain: cfg.main.url.domain, hide_comments_from_public: cfg.main.hide_comments_from_public, + guest_anonymize: !!cfg.main.guest_anonymize, git_hash: typeof gitHash !== 'undefined' ? gitHash : 'unknown', get motd() { return getMotd(); }, get manual_approval() { return getManualApproval(); }, @@ -1495,6 +1496,7 @@ process.on('uncaughtException', err => { allow_language_change: cfg.websrv.allow_language_change !== false, enable_xd_score: !!cfg.websrv.enable_xd_score, enable_dynamic_thumbs: !!cfg.websrv.enable_dynamic_thumbs, + default_thumb_size: (() => { const v = (cfg.websrv.default_thumb_size || '').toLowerCase(); return ['s', 'm', 'l', 'xl'].includes(v) ? v : 'm'; })(), comment_max_length: cfg.main.comment_max_length ?? null, enable_swf: !!cfg.websrv.enable_swf, enable_archive: !!cfg.websrv.enable_archive, diff --git a/views/item-partial-legacy.html b/views/item-partial-legacy.html index 9f9bff3..63cd3fe 100644 --- a/views/item-partial-legacy.html +++ b/views/item-partial-legacy.html @@ -100,7 +100,11 @@
+ @if(session || !guest_anonymize) {!! item.author_display_name || item.username !!} + @else + anonymous + @endif
@@ -126,7 +130,7 @@ {{ (enable_item_slugs && item.slug) ? item.slug : item.id }} - @if(!user_alternative_infobox) — [{!! item.author_display_name || item.username || 'unknown' !!}] @endif + @if(!user_alternative_infobox) — [@if(session || !guest_anonymize){!! item.author_display_name || item.username || 'unknown' !!}@elseanonymous@endif] @endif @if(!user_alternative_infobox) @if(item.is_oc) — OC@endif @endif @@ -206,6 +210,8 @@ @each(item.favorites as fav) @if(fav.hide_fav_badge && (!session || session.user !== fav.user)) + @elseif(!session && guest_anonymize) + @else @endif diff --git a/views/item-partial-modern.html b/views/item-partial-modern.html index 7ae642b..15de014 100644 --- a/views/item-partial-modern.html +++ b/views/item-partial-modern.html @@ -131,7 +131,7 @@
- {{ (enable_item_slugs && item.slug) ? item.slug : item.id }} — [{!! item.author_display_name || item.username || 'unknown' !!}] @if(item.is_oc) — OC@endif + {{ (enable_item_slugs && item.slug) ? item.slug : item.id }} — [@if(session || !guest_anonymize){!! item.author_display_name || item.username || 'unknown' !!}@elseanonymous@endif] @if(item.is_oc) — OC@endif @if(halls_enabled && item.primaryHall) {{ item.primaryHall.name }}@if(item.otherHalls && item.otherHalls.length)+{{ item.otherHalls.length }}@each(item.otherHalls as oh){{ oh.name }}@endeach@endif @@ -174,6 +174,8 @@ @each(item.favorites as fav) @if(fav.hide_fav_badge && (!session || session.user !== fav.user)) + @elseif(!session && guest_anonymize) + @else @endif diff --git a/views/snippets/footer.html b/views/snippets/footer.html index 6cb7397..1b47560 100644 --- a/views/snippets/footer.html +++ b/views/snippets/footer.html @@ -139,8 +139,9 @@ @endif @if(private_society && !session) +