import cfg from "./config.mjs"; import db from "./sql.mjs"; import lib from "./lib.mjs"; let manual_approval = true; let min_tags = 3; let registration_open = false; let trusted_uploads = 0; let bypass_duplicate_check = false; let protect_files = false; let private_messages = true; let dm_attachments = true; let dm_unencrypted = false; let default_layout = 'modern'; let enable_pdf = false; let enable_cleanup = false; let cleanup_start_date = ''; let cleanup_end_date = ''; let cleanup_include_engaged = false; export const getShitpostMode = () => !!cfg.websrv.shitpost_mode; export const setShitpostMode = (val) => {}; // No-op, strictly config-based export const getEnableExpiringUploads = () => { if (cfg.enable_expiring_uploads === false || cfg.websrv?.enable_expiring_uploads === false) return false; return true; }; export const getEnableItemSlugs = () => { if (cfg.enable_item_slugs === false || cfg.websrv?.enable_item_slugs === false) return false; return true; }; export const getEnableAnonymousAccess = () => { if (cfg.enable_anonymous_access === false || cfg.anonymous_access === false || cfg.websrv?.enable_anonymous_access === false || cfg.websrv?.anonymous_access === false) return false; return true; }; export const DEFAULT_ANON_PERMISSIONS = Object.freeze({ upload: false, comment: true, comment_attachments: false, comment_vote: true, poll_vote: true, tag: true, tag_vote: true, favorite: true, rate_item: false, filter: true, exclude_tags: true, anonymize_users: false, allowed_modes: ['sfw', 'nsfw', 'untagged', 'all', 'nsfl'], allowed_mimes: ['image', 'video', 'audio', 'flash', 'pdf'] }); export const getAnonPermissions = () => { const fromConfig = cfg.anonymous_permissions || cfg.websrv?.anonymous_permissions || {}; return { ...DEFAULT_ANON_PERMISSIONS, ...fromConfig }; }; export const getAnonAnonymize = () => { const perms = getAnonPermissions(); if (typeof perms.anonymize_users === 'boolean') return perms.anonymize_users; if (typeof perms.anon_anonymize === 'boolean') return perms.anon_anonymize; if (cfg.main && typeof cfg.main.anon_anonymize === 'boolean') return cfg.main.anon_anonymize; if (cfg.main && typeof cfg.main.anonymous_anonymize === 'boolean') return cfg.main.anonymous_anonymize; if (typeof cfg.anon_anonymize === 'boolean') return cfg.anon_anonymize; if (typeof cfg.anonymous_anonymize === 'boolean') return cfg.anonymous_anonymize; return false; }; export const isAnonymizeSession = (session) => { if (!session || typeof session !== 'object' || !session.user) { return !!(cfg.main?.guest_anonymize ?? cfg.guest_anonymize); } if (session.is_anon || session.user === 'anonymous' || (typeof session.user === 'string' && session.user.startsWith('anon_'))) { return getAnonAnonymize(); } return false; }; export const canAnonDo = (action) => { if (!getEnableAnonymousAccess()) return false; const perms = getAnonPermissions(); if (action === 'exclude_tags' || action === 'exclude_tag' || action === 'tag_exclude') { if (perms.exclude_tags !== undefined) return !!perms.exclude_tags; if (perms.exclude_tag !== undefined) return !!perms.exclude_tag; if (perms.tag_exclude !== undefined) return !!perms.tag_exclude; return perms.filter !== undefined ? !!perms.filter : true; } return perms[action] !== undefined ? !!perms[action] : !!DEFAULT_ANON_PERMISSIONS[action]; }; export const getAnonAllowedModes = () => { const perms = getAnonPermissions(); return Array.isArray(perms.allowed_modes) ? perms.allowed_modes.map(m => String(m).toLowerCase()) : DEFAULT_ANON_PERMISSIONS.allowed_modes; }; export const getAnonAllowedMimes = () => { const perms = getAnonPermissions(); return Array.isArray(perms.allowed_mimes) ? perms.allowed_mimes.map(m => String(m).toLowerCase()) : DEFAULT_ANON_PERMISSIONS.allowed_mimes; }; export const canAnonMode = (mode) => { if (!canAnonDo('filter')) return false; const allowed = getAnonAllowedModes(); const modeNames = ['sfw', 'nsfw', 'untagged', 'all', 'nsfl']; const name = typeof mode === 'number' ? modeNames[mode] : String(mode).toLowerCase(); return allowed.includes(name); }; export const canAnonMime = (mime) => { if (!canAnonDo('filter')) return false; const allowed = getAnonAllowedMimes(); return allowed.includes(String(mime).toLowerCase()); }; export const isAnonSession = (session) => { if (!session) return true; if (session === true) return false; if (typeof session !== 'object' || !session.user) return true; return !!(session.is_anon || session.user === 'anonymous' || (typeof session.user === 'string' && session.user.startsWith('anon_'))); }; export const checkAnonPermission = (session, action) => { if (!isAnonSession(session)) return true; return canAnonDo(action); }; export const ensureAllItemsHaveSlugs = async () => { try { const rows = await db`SELECT id FROM items WHERE slug IS NULL OR slug = ''`; if (!rows || rows.length === 0) return; console.log(`[SLUG_BACKFILL] Found ${rows.length} item(s) missing slugs. Backfilling...`); for (const row of rows) { const newSlug = lib.generateSlug(11); await db`UPDATE items SET slug = ${newSlug} WHERE id = ${row.id} AND (slug IS NULL OR slug = '')`; } console.log(`[SLUG_BACKFILL] Successfully backfilled ${rows.length} item slug(s).`); } catch (err) { console.error('[SLUG_BACKFILL] Error during slug backfill:', err.message); } }; export const ensureAllAlbumItemsHaveSlugs = async () => { try { const rows = await db`SELECT id FROM album_items WHERE slug IS NULL OR slug = ''`; if (!rows || rows.length === 0) return; console.log(`[ALBUM_SLUG_BACKFILL] Found ${rows.length} album item(s) missing slugs. Backfilling...`); for (const row of rows) { const newSlug = lib.generateSlug(11); await db`UPDATE album_items SET slug = ${newSlug} WHERE id = ${row.id} AND (slug IS NULL OR slug = '')`; } console.log(`[ALBUM_SLUG_BACKFILL] Successfully backfilled ${rows.length} album item slug(s).`); } catch (err) { console.error('[ALBUM_SLUG_BACKFILL] Error during album item slug backfill:', err.message); } }; export const getEnableCleanup = () => { if (cfg.websrv.enable_cleanup === false) return false; return enable_cleanup; }; export const setEnableCleanup = (val) => enable_cleanup = !!val; export const getCleanupStartDate = () => cleanup_start_date; export const setCleanupStartDate = (val) => cleanup_start_date = val || ''; export const getCleanupEndDate = () => cleanup_end_date; export const setCleanupEndDate = (val) => cleanup_end_date = val || ''; export const getCleanupIncludeEngaged = () => cleanup_include_engaged; export const setCleanupIncludeEngaged = (val) => cleanup_include_engaged = !!val; export const getEnablePdf = () => enable_pdf; export const setEnablePdf = (val) => enable_pdf = !!val; export const getManualApproval = () => manual_approval; export const setManualApproval = (val) => manual_approval = !!val; export const getMinTags = () => min_tags; export const setMinTags = (val) => { const parsed = parseInt(val); min_tags = isNaN(parsed) ? 3 : Math.max(0, parsed); }; export const getRegistrationOpen = () => { if (cfg.websrv.open_registration_web_toggle === false) { return !!cfg.websrv.open_registration; } return registration_open; }; export const setRegistrationOpen = (val) => registration_open = !!val; // When false (default): open_registration=true means anyone can register with just username+password, activated immediately. // When true: even in open registration, a valid email OR invite token is required. export const getRegistrationRequireMailAndorToken = () => !!cfg.websrv.open_registration_require_mail_andor_token; export const setRegistrationRequireMailAndorToken = (val) => {}; // No-op, strictly config-based export const getTrustedUploads = () => trusted_uploads; export const setTrustedUploads = (val) => trusted_uploads = Math.max(0, parseInt(val) ?? 3); export const getBypassDuplicateCheck = () => bypass_duplicate_check; export const setBypassDuplicateCheck = (val) => bypass_duplicate_check = !!val; export const getProtectFiles = () => protect_files; export const setProtectFiles = (val) => protect_files = !!val; export const getPrivateMessages = () => private_messages; export const setPrivateMessages = (val) => private_messages = !!val; export const getDmAttachments = () => dm_attachments; export const setDmAttachments = (val) => dm_attachments = !!val; export const getDmUnencrypted = () => dm_unencrypted; export const setDmUnencrypted = (val) => dm_unencrypted = !!val; export const getDmAttachmentExpiryDays = () => { const v = parseInt(cfg.websrv.dm_attachment_expiry_days); return (Number.isFinite(v) && v > 0) ? v : 90; }; export const getDefaultLayout = () => default_layout; export const setDefaultLayout = (val) => default_layout = (val === 'legacy' ? 'legacy' : 'modern'); export const getLogUserIps = () => !!cfg.websrv.log_user_ips; export const setLogUserIps = (val) => {}; // No-op, strictly config-based export const getHashUserIps = () => !!cfg.websrv.hash_user_ips; export const setHashUserIps = (val) => {}; // No-op, strictly config-based export const getAllowCommentDeletion = () => !!cfg.websrv.allow_comment_deletion; export const setAllowCommentDeletion = (val) => {}; // No-op, strictly config-based // Live-editable NSFP tag ID list — seeded from config.json, can be overridden by DB setting let nsfp_ids = Array.isArray(cfg.nsfp) ? [...cfg.nsfp.map(Number).filter(n => !isNaN(n))] : []; export const getNsfpIds = () => nsfp_ids; export const setNsfpIds = (ids) => { nsfp_ids = Array.isArray(ids) ? ids.map(Number).filter(n => !isNaN(n) && n > 0) : []; // Also sync to cfg.nsfp so all code reading cfg.nsfp directly still works cfg.nsfp = [...nsfp_ids]; }; // Brand image URL — stored in site_settings DB, not config.json // Falls back to cfg.websrv.custom_brand_image (array or string) on first boot let brand_image_url = (() => { const raw = cfg.websrv?.custom_brand_image; return Array.isArray(raw) ? (raw[0] || '') : (raw || ''); })(); export const getBrandImageUrl = () => brand_image_url; export const setBrandImageUrl = (val) => { brand_image_url = val || ''; };