This commit is contained in:
2026-09-11 23:07:03 +02:00
parent 802a59d090
commit 4b615428de
14 changed files with 243 additions and 174 deletions
+23 -6
View File
@@ -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\/(?<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: {
+13
View File
@@ -12,6 +12,10 @@ const auth = async (req, res, next) => {
export default (router, tpl) => {
router.get(/\/user\/(?<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' : '?'));
+13 -1
View File
@@ -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);
+13
View File
@@ -43,6 +43,9 @@ export default (router, tpl) => {
// List halls for a user
router.get(/^\/user\/(?<owner>[^/]+)\/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\/(?<owner>[^/]+)\/hall\/(?<slug>[^/]+)(?:\/p\/(?<page>\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\/(?<owner>[^/]+)\/hall\/(?<slug>[^/]+)\/(?<itemid>\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
+3
View File
@@ -158,6 +158,9 @@ export default (router, tpl) => {
// Main tags page
router.get(/^\/user\/(?<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`
+2
View File
@@ -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,