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