feat: worst-scan fansub web — initial release
Posts engine (SQLite + auto-publisher via poller), public feed with clickable tags, reader, admin panel, submit/search/queue tools. BFF proxy to pipeline API. Clean dark design. Docker-ready.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import JSZip from "jszip"
|
||||
|
||||
const imageExts = new Set([".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp"])
|
||||
|
||||
export async function loadPagesFromCbz(
|
||||
url: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{ pages: string[]; pageCount: number }> {
|
||||
const response = await fetch(url, { signal })
|
||||
if (!response.ok) throw new Error(`Failed to fetch CBZ: ${response.status}`)
|
||||
|
||||
const blob = await response.blob()
|
||||
const zip = await JSZip.loadAsync(blob)
|
||||
|
||||
const imageEntries = Object.entries(zip.files)
|
||||
.filter(([name, file]) => {
|
||||
const ext = name.toLowerCase().slice(name.lastIndexOf("."))
|
||||
return !file.dir && imageExts.has(ext)
|
||||
})
|
||||
.sort(([a], [b]) => {
|
||||
const numA = parseInt(a.match(/(\d+)/)?.[1] || "0", 10)
|
||||
const numB = parseInt(b.match(/(\d+)/)?.[1] || "0", 10)
|
||||
return numA - numB
|
||||
})
|
||||
|
||||
const pageCount = imageEntries.length
|
||||
const pages: string[] = []
|
||||
|
||||
for (const [, file] of imageEntries) {
|
||||
const blob = await file.async("blob")
|
||||
pages.push(URL.createObjectURL(blob))
|
||||
}
|
||||
|
||||
return { pages, pageCount }
|
||||
}
|
||||
|
||||
export function revokePageUrls(urls: string[]) {
|
||||
for (const url of urls) {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user