redirecter for ao3 that adds opengraph metadata
1import { raw } from "hono/html" 2import { getSeries, getWork } from "@fujocoded/ao3.js" 3import { useEffect, useState, useRef } from "hono/jsx" 4import Image from './Image.jsx' 5import themes from '../themes.js' 6import baseFonts from '../baseFonts.js' 7import titleFonts from '../titleFonts.js' 8 9const Generator = ({ title, description, addr }) => { 10 const [url, setUrl] = useState('') 11 const [workData, setWorkData] = useState(null) 12 const [theme, setTheme] = useState('ao3') 13 const [baseFont, setBaseFont] = useState('verdana') 14 const [titleFont, setTitleFont] = useState('georgia') 15 const [props, setProps] = useState({ 16 category: true, 17 rating: true, 18 warnings: false, 19 charTags: false, 20 relTags: false, 21 freeTags: false, 22 summary: true, 23 wordcount: true, 24 chapters: true, 25 summaryType: 'basic', 26 customSummary: '' 27 }) 28 29 const updateProp = (name, value) => { 30 const newProps = props 31 newProps[name] = value 32 setProps(newProps) 33 } 34 35 useEffect(async () => { 36 const workMatch = /\/works\/(?<workId>[0-9]+)(?:\/chapters\/(?<chapterId>[0-9]+))?$/g 37 const seriesMatch = /\/series\/(?<seriesId>[0-9]+)$/g 38 if (workMatch.test(url)) { 39 const match = url.match(workMatch) 40 const data = match.groups.chapterId ? await getWork({workId: match.groups.workId, chapterId: match.groups.chapterId}) : await getWork({workId: match.groups.workId}) 41 setWorkData(data) 42 } else if (seriesMatch.test(url)) { 43 const match = url.match(seriesMatch) 44 const data = await getSeries({seriesId: match.groups.seriesId}) 45 setWorkData(data) 46 } 47 // otherwise do nothing 48 }, [url]) 49 50 useEffect(async () => { 51 const image = await Image(workData, props); 52 }, [workData]) 53 54 return ( 55 <html lang="en"> 56 <head> 57 <title>fixAO3 | embed card generator</title> 58 <link 59 rel="favicon" 60 href="https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/e1bf3632-3127-4828-0e01-47af78b4c200/public" 61 /> 62 63 </head> 64 <body> 65 <form id="generator"> 66 <div class="input-field"> 67 <label htmlFor="url">Work/Series URL</label> 68 <input type="text" name="url" id="url" onChange={e => setUrl(e.target.value)} /> 69 </div> 70 <details><summary>Style Settings</summary> 71 <div class="input-field"> 72 <label htmlFor="theme">Theme</label> 73 <select name="theme" id="theme" onChange={e => setTheme(e.target.value)}> 74 {Object.entries(themes).map((th) => { 75 return ( 76 <option value={th[0]}>{th[1].name}</option> 77 ) 78 })} 79 </select> 80 </div> 81 <div class="input-field"> 82 <label htmlFor="baseFont">Base Font</label> 83 <select name="baseFont" id="baseFont" onChange={e => setTheme(e.target.value)}> 84 {Object.entries(baseFonts).sort().map((bf) => { 85 return ( 86 <option value={bf[0]}>{bf[1]}</option> 87 ) 88 })} 89 </select> 90 </div> 91 <div class="input-field"> 92 <label htmlFor="titleFont">Title Font</label> 93 <select name="titleFont" id="titleFont" onChange={e => setTheme(e.target.value)}> 94 {Object.entries({...titleFonts, ...baseFonts}).sort().map((tf) => { 95 return ( 96 <option value={tf[0]}>{tf[1]}</option> 97 ) 98 })} 99 </select> 100 </div> 101 <div class="input-field"> 102 <label htmlFor="features">Features:</label> 103 <ul> 104 <li><label><input type="checkbox" name="features[]" value="category" onChange={e => updateProp(e.target.value, e.target.checked)} /> Category</label></li> 105 <li><label><input type="checkbox" name="features[]" value="rating" onChange={e => updateProp(e.target.value, e.target.checked)} /> Rating</label></li> 106 <li><label><input type="checkbox" name="features[]" value="warnings" onChange={e => updateProp(e.target.value, e.target.checked)} /> Archive Warnings</label></li> 107 <li><label><input type="checkbox" name="features[]" value="chartags" onChange={e => updateProp(e.target.value, e.target.checked)} /> Character Tags</label></li> 108 <li><label><input type="checkbox" name="features[]" value="reltags" onChange={e => updateProp(e.target.value, e.target.checked)} /> Relationship Tags</label></li> 109 <li><label><input type="checkbox" name="features[]" value="freetags" onChange={e => updateProp(e.target.value, e.target.checked)} /> Free Tags</label></li> 110 <li><label><input type="checkbox" name="features[]" value="summary" onChange={e => updateProp(e.target.value, e.target.checked)} /> Summary</label></li> 111 <li><label><input type="checkbox" name="features[]" value="wordcount" onChange={e => updateProp(e.target.value, e.target.checked)} /> Wordcount</label></li> 112 <li><label><input type="checkbox" name="features[]" value="chapters" onChange={e => updateProp(e.target.value, e.target.checked)} /> Chapters</label></li> 113 </ul> 114 </div> 115 <div class="input-field"> 116 <label htmlFor="summaryOptions">Summary Type</label> 117 <ul> 118 <li><label><input type="radio" name="summaryType" value="basic" onChange={e => updateProp(e.target.name, e.target.value)} /> Story Summary</label></li> 119 <li><label><input type="radio" name="summaryType" value="chapter" onChange={e => updateProp(e.target.name, e.target.value)} /> Chapter Summary (if available)</label></li> 120 <li><label><input type="radio" name="summaryType" value="custom" onChange={e => updateProp(e.target.name, e.target.value)} /> Custom Summary</label></li> 121 </ul> 122 </div> 123 {props.summaryType === 'custom' && ( 124 <div class="input-field"> 125 <label htmlFor="customSummary">Custom Summary</label> 126 <textarea name="customSummary" id="customSummary" onChange={e => updateProp(e.target.name, e.target.value)}></textarea> 127 </div> 128 )} 129 </details> 130 </form> 131 <div id="output"> 132 </div> 133 </body> 134 </html> 135 ) 136} 137 138export default Generator