redirecter for ao3 that adds opengraph metadata

normalize data better

Changed files
+42 -31
src
app
api
series
[seriesId]
series
[seriesId]
preview
works
lib
+2
src/app/api/series/[seriesId]/route.js
···
export async function GET(req, ctx) {
const { seriesId } = await ctx.params
const { archive } = await req.nextUrl.searchParams
+
console.log(seriesId)
+
console.log(archive)
const domain = await req.nextUrl.hostname
const subdomain = domain.split(".").length > 1 ? domain.split(".")[0] : null
if (subdomain) setArchiveBaseUrl('https://'+subdomain)
+4 -4
src/app/series/[seriesId]/preview/route.js
···
const p = await req.nextUrl.searchParams
const props = querystring.parse(p.toString())
const addr = `series/${seriesId}`
-
if (props.archive) setArchiveBaseUrl('https://'+props.archive)
-
const data = await getSeries({seriesId: seriesId})
-
if (props.archive) resetArchiveBaseUrl()
-
const imageParams = await sanitizeData({type: 'series', data: data, props: props})
+
const domainParam = p && p.has('archive') ? `?archive=${p.get('archive')}` : ''
+
const work = await fetch(`http://${process.env.DOMAIN}/api/series/${seriesId}${domainParam}`)
+
const data = await work.json()
+
const imageParams = await sanitizeData({type: 'series', data: data, props: p})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
+1 -1
src/app/works/[workId]/opengraph-image.jsx
···
const addr = `works/${workId}`
const data = await getWork({workId: workId})
if (data.locked) return OGImageLocked({theme: process.env.DEFAULT_THEME})
-
const imageParams = await sanitizeData({type: 'work', data: data, props: defaults})
+
const imageParams = await sanitizeData({type: 'work', data: data, props: new URLSearchParams(defaults)})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
+20 -19
src/lib/ogimage.js
···
import NoWarnings from "@/icons/nowarnings.js"
import Warnings from "@/icons/warnings.js"
import ChoseNotToWarn from "@/icons/chosenottowarn.js"
+
import { checkItem, checkToggle } from '@/lib/propUtils.js'
export default async function OGImage ({ theme, baseFont, titleFont, image, addr, opts }) {
console.log(image)
···
gap: 10
}}
>
-
{image.props.get('rating') && image.rating === 'E' && (<Explicit fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('rating') && image.rating === 'M' && (<Mature fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('rating') && image.rating === 'T' && (<Teen fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('rating') && image.rating === 'G' && (<General fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('rating') && image.rating === 'NR' && (<NotRated fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'E' && (<Explicit fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'M' && (<Mature fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'T' && (<Teen fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'G' && (<General fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'NR' && (<NotRated fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('warnings') && image.warning === 'NW' && (<NoWarnings fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
-
{image.props.get('warnings') && image.warning === 'CNTW' && (<ChoseNotToWarn fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
-
{image.props.get('warnings') && image.warning === 'W' && (<Warnings fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
+
{checkToggle('warnings', image.props) && image.warning === 'NW' && (<NoWarnings fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
+
{checkToggle('warnings', image.props) && image.warning === 'CNTW' && (<ChoseNotToWarn fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
+
{checkToggle('warnings', image.props) && image.warning === 'W' && (<Warnings fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
-
{image.props.get('category') && image.category === 'F' && (<Yuri fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') && image.category === 'M' && (<Yaoi fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') && image.category === 'FM' && (<Het fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') && image.category === 'G' && (<Gen fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') && image.category === 'MX' && (<MultiShip fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') && image.category === 'O' && (<OtherShip fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'F' && (<Yuri fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props)&& image.category === 'M' && (<Yaoi fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'FM' && (<Het fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'G' && (<Gen fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'MX' && (<MultiShip fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'O' && (<OtherShip fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
</div>
<div
style={{
···
alignItems: "flex-end"
}}
>
-
{image.props.get('charTags') && (<div
+
{checkToggle('charTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
</span>
))}
</div>)}
-
{image.props.get('relTags') && (<div
+
{checkToggle('relTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
</span>
))}
</div>)}
-
{image.props.freeTags && (<div
+
{checkToggle('freeTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
</span>
))}
</div>)}
-
{image.props.get('summary') && (<div
+
{checkToggle('summary', image.props) && (<div
style={{
display: "flex",
flexDirection: "column",
···
color: theme.accent2
}}
>
-
{image.props.get('wordcount') && `${image.words} words • `}{(image.props.get('chapters') && image.chapterCount !== null) && `${image.chapterCount} chapters • `}{image.props.get('postedAt') && `posted on ${image.postedAt} • `}{image.props.get('updatedAt') && `updated on ${image.updatedAt} • `}{addr}
+
{checkToggle('wordcount', image.props) && `${image.words} words • `}{(checkToggle('chapters', image.props) && image.chapterCount !== null) && `${image.chapterCount} chapters • `}{checkToggle('postedAt', image.props) && `posted on ${image.postedAt} • `}{checkToggle('updatedAt', image.props) && `updated on ${image.updatedAt} • `}{addr}
</div>
</div>
</div>
+7
src/lib/propUtils.js
···
+
export function checkItem (key, props) {
+
return props.has(key) ? props.get(key) !== '' : false
+
}
+
+
export function checkToggle (key, props) {
+
return props.has(key) ? props.get(key) === 'true' : false
+
}
+8 -7
src/lib/sanitizeData.js
···
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}` : ''
···
}
export default async function sanitizeData ({ type, data, props}) {
-
const archive = props && props.archive ? props.archive : process.env.ARCHIVE
+
const archive = props && checkItem('archive', props) ? props.get('archive') : process.env.ARCHIVE
console.log(props)
-
const baseFont = props.baseFont ? props.baseFont : process.env.DEFAULT_BASE_FONT
+
const baseFont = checkItem('baseFont', props) ? props.get('baseFont') : process.env.DEFAULT_BASE_FONT
const baseFontData = baseFonts[baseFont]
-
const titleFont = props.titleFont ? props.titleFont : process.env.DEFAULT_TITLE_FONT
+
const titleFont = checkItem('titleFont', props) ? props.get('titleFont') : process.env.DEFAULT_TITLE_FONT
const titleFontData = titleFonts[titleFont]
-
const archClean = props.has('archive') ? props.get('archive').replace("https://", '').replace('/', '') : null
-
const theme = props.has('theme') ? props.get('theme') : (props.has('archive') && !["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 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) => {
···
authorsFormatted.slice(-1)[0]
: authorsFormatted[0])
const summaryContent = type === 'work'
-
? (props.summaryType === 'chapter' && data.chapterInfo && data.chapterInfo.summary ? data.chapterInfo.summary : (props.summaryType === 'custom' && props.customSummary !== '' ? props.customSummary : (data.summary ? data.summary : (parentWork ? parentWork.summary : ''))))
-
: (props.summaryType === 'custom' && props.customSummary !== '' ? props.customSummary : data.notes)
+
? (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})