import DOM from "fauxdom" import { readFile } from 'node:fs/promises' import querystring from 'node:querystring' import { join } from 'node:path' import themes from '@/lib/themes.js' import baseFonts from '@/lib/baseFonts.js' import titleFonts from '@/lib/titleFonts.js' import { checkItem, checkToggle } from '@/lib/propUtils.js' const getWork = async (workId, archive = null) => { const domainParam = (archive && archive !== process.env.ARCHIVE) ? `?archive=${archive}` : '' const data = await fetch(`http://${process.env.DOMAIN}/api/works/${workId}${domainParam}`) const work = await data.json() return work } const getHighestRating = async (works, archive = null) => { const ratings = await Promise.all(works.map(async (w) => { const work = await getWork(w.id, archive) return work.rating })) if (ratings.includes("Not Rated")) { return "NR" } else if (ratings.includes("Explicit")) { return "E" } else if (ratings.includes("Mature")) { return "M" } else if (ratings.includes("Teen")) { return "T" } return "G" } const getHighestWarning = async (works, archive = null) => { const warnings = await Promise.all(works.map(async (w) => { const work = await getWork(w.id, archive) return work.tags.warnings })) const warningsUnique = warnings.reduce((a, b) => { return a.concat(b) }).filter((w, i) => { return i === warnings.indexOf(w) }) if (warningsUnique.length === 1 && warningsUnique[0] === "Creator Chose Not To Use Archive Warnings") { return "CNTW" } else if (warningsUnique.length === 1 && warningsUnique[0] === "No Archive Warnings Apply") { return "NW" } return "W" } const getCategory = async (works, archive = null) => { const categories = await Promise.all(works.map(async (w) => { const work = await getWork(w.id, archive) return work.category })) const categoriesJoined = categories.reduce((a, b) => { return a.concat(b) }) const categoriesUnique = categoriesJoined.filter((w, i) => { return i === categoriesJoined.indexOf(w) }) if (categoriesUnique.length === 1) { if (categoriesUnique[0] === "F/F") return "F" if (categoriesUnique[0] === "M/M") return "M" if (categoriesUnique[0] === "F/M") return "FM" if (categoriesUnique[0] === "Gen") return "G" if (categoriesUnique[0] === "Multi") return "MX" if (categoriesUnique[0] === "Other") return "O" } return "MX" } const sanitizeProps = (props) => { let propsParsed = {} Object.keys(props).forEach((pr) => { if (props[pr] === 'true') { propsParsed[pr] = true return } else if (props[pr] === 'false') { propsParsed[pr] = false return } else if (typeof parseInt(props[pr]) === 'Number') { propsParsed[pr] = parseInt(props[pr]) return } propsParsed[pr] = props[pr] }) return propsParsed } export default async function sanitizeData ({ type, data, props}) { const archive = props && checkItem('archive', props) ? props.get('archive') : process.env.ARCHIVE console.log(props) const baseFont = checkItem('baseFont', props) ? props.get('baseFont') : process.env.DEFAULT_BASE_FONT const baseFontData = baseFonts[baseFont] const titleFont = checkItem('titleFont', props) ? props.get('titleFont') : process.env.DEFAULT_TITLE_FONT const titleFontData = titleFonts[titleFont] const archClean = checkItem('archive', props) ? props.get('archive').replace("https://", '').replace('/', '') : null const theme = checkItem('theme', props) ? props.get('theme') : (checkItem('archive', props) && !["ao3.org", "archiveofourown.org", "archive.transformativeworks.org"].includes(archClean) && Object.values(siteMap).includes(archClean) ? Object.keys(siteMap)[Object.values(siteMap).indexOf(archClean)] : process.env.DEFAULT_THEME) const themeData = themes[theme] const parentWork = type === 'work' && data.chapterInfo ? await getWork(data.id, archive) : null const bfs = await Promise.all(baseFontData.defs.map(async (bf) => { return { name: baseFontData.displayName, data: await readFile( join(process.cwd(), bf.path) ), style: bf.style, weight: bf.weight } })).then(x => x) const tfs = await Promise.all(titleFontData.defs.map(async (tf) => { return { name: titleFontData.displayName, data: await readFile( join(process.cwd(), tf.path) ), style: tf.style, weight: tf.weight } })).then(x => x) const authorsFormatted = data.authors ? data.authors.map((a) => { if (a.anonymous) return "Anonymous" if (a.pseud !== a.username) return `${a.pseud} (${a.username})` return a.username }) : [] const rating = type === 'work' ? data.rating : await getHighestRating(data.works, archive) const warning = type === 'work' ? await getHighestWarning([data], archive) : await getHighestWarning(data.works, archive) const category = type === 'work' ? await getCategory([data], archive) : await getCategory(data.works, archive) const authorString = (authorsFormatted.length > 1 ? authorsFormatted.slice(0, -1).join(", ") + " & " + authorsFormatted.slice(-1)[0] : authorsFormatted[0]) const summaryContent = type === 'work' ? (props.get('summaryType') === 'chapter' && data.chapterInfo && data.chapterInfo.summary ? data.chapterInfo.summary : (props.get('summaryType') === 'custom' && props.get('customSummary') !== '' ? props.get('customSummary') : (data.summary ? data.summary : (parentWork ? parentWork.summary : '')))) : (props.get('summaryType') === 'custom' && props.get('customSummary') !== '' ? props.get('customSummary') : data.notes) const formatter = new Intl.NumberFormat('en-US') const words = formatter.format(data.words) const summaryDOM = new DOM(summaryContent, {decodeEntities: true}) const summaryFormatted = summaryDOM.innerHTML.replace(/\/g, "\n").replace( /(<([^>]+)>)/ig, "", ).split("\n") const titleString = type === 'work' ? data.title : data.name const chapterString = data.chapterInfo ? (data.chapterInfo.name ? data.chapterInfo.name : "Chapter " + data.chapterInfo.index) : null const chapterCountString = type === 'work' ? (data.chapters ? data.chapters.published+'/'+( data.chapters.total ? data.chapters.total : '?' ) : '') : null const fandomString = type === 'work' ? ( data.fandoms.length > 1 ? ( data.fandoms.length <= 2 ? data.fandoms.slice(0, -1).join(", ")+" & "+data.fandoms.slice(-1) : data.fandoms.join(", ")+" (+"+(data.fandoms.length - 2)+")" ) : data.fandoms[0] ) : ( '' ) const charTags = type === 'work' ? data.tags.characters : data.works.map(w => w.tags.characters).reduce((a, b) => { return b ? (a ? a.concat(b) : []) : (a ? a : []) }).filter((w, i) => { return i === data.works.indexOf(w) }) const relTags = type === 'work' ? data.tags.relationships : data.works.map(w => w.tags.relationships).reduce((a, b) => { return b ? (a ? a.concat(b) : []) : (a ? a : []) }).filter((w, i) => { return i === data.works.indexOf(w) }) const freeTags = type === 'work' ? data.tags.additional : data.works.map(w => w.tags.additional).reduce((a, b) => { return b ? (a ? a.concat(b) : []) : (a ? a : []) }).filter((w, i) => { return i === data.works.indexOf(w) }) const warnings = type === 'work' ? data.tags.warnings : data.works.map(w => w.tags.warnings).reduce((a, b) => { return b ? (a ? a.concat(b) : []) : (a ? a : []) }).filter((w, i) => { return i === data.works.indexOf(w) }) const ret = { topLine: fandomString, titleLine: titleString, authorLine: authorString, chapterLine: chapterString, chapterCount: chapterCountString, words: words, rating: rating, warning: warning, category: category, summary: summaryFormatted, theme: themeData, charTags: charTags, relTags: relTags, freeTags: freeTags, postedAt: type === 'work' ? data.publishedAt : data.startedAt, updatedAt: data.updatedAt, baseFont: baseFont, titleFont: titleFont, props: props, opts: { fonts: bfs.concat(tfs) } } return ret; }