'use strict'; const path = require('path'); const { isSpecialKey } = require('../../chapter-identity'); const RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i; function cleanPart(value, max = 120) { let result = String(value || '') .normalize('NFC') .replace(/[<>:"/\\|?*\x00-\x1f]/g, '') .replace(/[\[\]]/g, '') .replace(/\s+/g, ' ') .trim() .replace(/[ .]+$/g, ''); if (!result) result = 'Untitled'; if (RESERVED.test(result)) result += '-manga'; return result.slice(0, max).replace(/[ .]+$/g, ''); } function chapterKey(value) { const raw = String(value).trim(); if (isSpecialKey(raw)) return raw.toLowerCase(); if (/^\d+(?:\.\d+)?$/.test(raw)) { const [integer, decimal = ''] = raw.split('.'); const normalized = decimal.replace(/0+$/g, ''); return `c${integer.padStart(4, '0')}${normalized ? `.${normalized}` : ''}`; } const extra = raw.match(/^x(\d+)$/i); if (extra) return `x${extra[1].padStart(2, '0')}`; throw new Error(`Unsupported chapter number: ${value}`); } function chapterFolder(chapter) { const parts = [chapterKey(chapter.number)]; if (chapter.title) parts.push(cleanPart(chapter.title)); parts.push(`[${cleanPart(chapter.source.name, 50)}]`); return parts.join(' '); } function assertInside(parent, child) { const root = path.resolve(parent); const target = path.resolve(child); if (target !== root && !target.startsWith(root + path.sep)) { throw new Error(`Path escapes library root: ${target}`); } return target; } module.exports = { cleanPart, chapterKey, chapterFolder, assertInside };