change: open in booru site icon appended in anchor element.
new: support open graph link preview.
fix: post booru link and file link copy wrong url.
This commit is contained in:
defaultkavy 2024-10-12 01:17:33 +08:00
parent acbb242c97
commit acf412de6b
Signed by: defaultkavy
GPG Key ID: DFBB22C4E69D7826
9 changed files with 105 additions and 19 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@ -16,8 +16,8 @@
gtag('config', 'G-59HBGP98WR'); gtag('config', 'G-59HBGP98WR');
</script> </script>
<script type="module" crossorigin src="/assets/index-B0cSv4EX.js"></script> <script type="module" crossorigin src="/assets/index-Dg3fAkPK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-GbkvssuE.css"> <link rel="stylesheet" crossorigin href="/assets/index-CPDn8S3u.css">
</head> </head>
<body> <body>
</body> </body>

View File

@ -128,6 +128,12 @@ nav {
display: none; display: none;
} }
a {
display: flex;
justify-content: center;
align-items: center;
}
div.account { div.account {
height: 2rem; height: 2rem;
width: 2rem; width: 2rem;

View File

@ -2,7 +2,7 @@
"name": "danbooru-viewer", "name": "danbooru-viewer",
"module": "index.ts", "module": "index.ts",
"type": "module", "type": "module",
"version": "0.6.0", "version": "0.6.1",
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
"vite": "^5.4.8", "vite": "^5.4.8",
@ -15,8 +15,9 @@
"typescript": "^5.0.0" "typescript": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"elysia": "^1.1.20",
"@elysiajs/cors": "^1.1.1", "@elysiajs/cors": "^1.1.1",
"cheerio": "^1.0.0" "@elysiajs/html": "^1.1.1",
"cheerio": "^1.0.0",
"elysia": "^1.1.20"
} }
} }

View File

@ -1,9 +1,69 @@
import cors from "@elysiajs/cors"; import cors from "@elysiajs/cors";
import Elysia from "elysia"; import Elysia from "elysia";
import * as cheerio from 'cheerio';
import html from "@elysiajs/html";
import type { PostData } from "./src/structure/Post";
const list_format = new Intl.ListFormat('en', {type: 'conjunction', style: 'long'})
const app = new Elysia() const app = new Elysia()
.use(cors()) .use(cors())
.use(html())
.get('*', async ({path}) => { .get('*', async ({path}) => {
return Bun.file('./dist/index.html') const $ = cheerio.load(Buffer.from(await Bun.file('./dist/index.html').arrayBuffer()));
if (path.match(/posts\/(\d+)/)) {
const post_id = path.match(/posts\/(\d+)/)?.at(1);
const data = await fetch(`https://danbooru.donmai.us/posts/${post_id}.json`).then(res => res.json()) as PostData;
switch (data.file_ext) {
case 'png':
case 'webp':
case 'jpg':
case 'gif': {
$('head')
.append(og("og:image", data.file_url))
.append(og("og:image:secure_url", data.file_url))
.append(og('og:image:type', `image/${data.file_ext}`))
.append(og('og:image:height', data.image_height.toString()))
.append(og('og:image:width', data.image_width.toString()))
.append(og('twitter:image', data.file_url))
break;
}
case 'zip': $('head').append(og("og:video", data.media_asset.variants.find(v => v.file_ext === 'webm')?.url ?? '')); break;
case 'mp4':
case 'webm': {
$('head')
.append(og("og:video", data.file_url))
.append(og("og:video:secure_url", data.file_url))
.append(og("og:video:type", `video/${data.file_ext}`))
.append(og("og:video:height", data.image_height.toString()))
.append(og("og:video:width", data.image_width.toString()))
.append(og("og:image", data.media_asset.variants.find(v => v.file_ext === 'webp')?.url ?? ''))
break;
}
}
const byArtist = `${list_format.format(data.tag_string_artist.split(' '))}`;
const characters = data.tag_string_character.split(' ').map(str => {
const matched = str.match(/([a-z-_]+)(?:\((\w+)\))?/)
console.debug(str)
return matched?.at(1)?.replaceAll('_', ' ')
}).filter(str => str !== undefined);
const copyrights = data.tag_string_copyright.split(' ').map(str => {
const matched = str.match(/([a-z-_]+)(?:\((\w+)\))?/)
return matched?.at(1)?.replaceAll('_', ' ')
}).filter(str => str !== undefined);
const copyright0 = copyrights.at(0);
const title = `${list_format.format(characters)}${copyright0 ? ` (${copyright0}${copyrights.length > 1 ? ` and ${copyrights.length - 1} more` : ''})` : '' }${byArtist ? ` drawn by ${byArtist}` : ''} | Danbooru Viewer`;
const description = `${data.file_ext.toUpperCase()} | ${data.image_width}x${data.image_height} | ${digitalUnit(data.file_size)}`;
$('head')
.append(og('og:title', title))
.append(og('og:description', description))
.append(og('og:site_name', 'Danbooru Viewer'))
.append(og('og:type', 'website'))
.append(og('og:url', `https://danbooru.defaultkavy.com/${path}`))
.append(og('twitter:site', '@defaultkavy_dev'))
.append(og('twitter:title', title))
.append(og('twitter:description', description))
.append(og('twitter:card', 'summary_large_image'))
}
return $.html()
}) })
.get('/assets/*', (res) => { .get('/assets/*', (res) => {
return Bun.file(`./dist/${res.path}`) return Bun.file(`./dist/${res.path}`)
@ -11,10 +71,29 @@ const app = new Elysia()
.group('/api', app => { return app .group('/api', app => { return app
.delete('/favorites/:id', async ({params, query}) => { .delete('/favorites/:id', async ({params, query}) => {
const data = await fetch(`${query.origin}/favorites/${params.id}.json?login=${query.login}&api_key=${query.api_key}`, {method: "DELETE"}).then(res => res.ok); const data = await fetch(`${query.origin}/favorites/${params.id}.json?login=${query.login}&api_key=${query.api_key}`, {method: "DELETE"}).then(res => res.ok);
console.debug(data)
return data return data
}) })
}) })
.listen(3030); .listen(3030);
console.log('Start listening: 3030') console.log('Start listening: 3030')
export type Server = typeof app; export type Server = typeof app;
function og(property: string, content: string) {
return `<meta property=${property} content="${content}">`
}
export function digitalUnit(bytes: number) {
if (bytes < 1000) return `${bytes}B`
const kb = bytes / 1000;
if (kb < 1000) return `${kb.toFixed(2)}kB`;
const mb = bytes / (1000 ** 2);
if (mb < 1000) return `${mb.toFixed(2)}MB`;
const gb = bytes / (1000 ** 3);
if (gb < 1000) return `${gb.toFixed(2)}GB`;
const tb = bytes / (1000 ** 4);
if (tb < 1000) return `${tb.toFixed(2)}TB`;
const pb = bytes / (1000 ** 5);
if (pb < 1000) return `${pb.toFixed(2)}PB`;
const eb = bytes / (1000 * 6);
return `${eb.toFixed(2)}EB`;
}

View File

@ -61,8 +61,7 @@ $(document.body).content([
else Booru.set(danbooru); else Booru.set(danbooru);
}), }),
// Open Booru // Open Booru
$('ion-icon').class('open').name('open-outline').title('Open in Original Site') $('a').content($('ion-icon').class('open').name('open-outline').title('Open in Original Site')).href(location.href.replace(location.origin, Booru.used.origin)).target('_blank'),
.on('click', () => $.open(location.href.replace(location.origin, Booru.used.origin))),
// Menu Button // Menu Button
$('ion-icon').class('menu').name('menu-outline').title('Menu').hide(false) $('ion-icon').class('menu').name('menu-outline').title('Menu').hide(false)
.self(($icon) => { Booru.events.on('login', () => $icon.hide(true)).on('logout', () => $icon.hide(false)) }) .self(($icon) => { Booru.events.on('login', () => $icon.hide(true)).on('logout', () => $icon.hide(false)) })

View File

@ -80,17 +80,17 @@ export const post_route = $('route').path('/posts/:id').id('post').builder(({$ro
]), ]),
new $Property('file-url').name('File').content([ new $Property('file-url').name('File').content([
$('a').href(post.file_url$).content(post.file_url$.convert((value) => value.replace('https://', ''))).target('_blank'), $('a').href(post.file_url$).content(post.file_url$.convert((value) => value.replace('https://', ''))).target('_blank'),
$('ion-icon').name('clipboard').on('click', (e, $ion) => copyButtonHandler($ion, post.source)) $('ion-icon').name('clipboard').on('click', (e, $ion) => copyButtonHandler($ion, post.file_url))
]), ]),
new $Property('source-url').name('Source').content([ new $Property('source-url').name('Source').content([
$('a').href(post.source$).content(post.source$.convert((value) => value.replace('https://', ''))).target('_blank'), $('a').href(post.source$).content(post.source$.convert((value) => value.replace('https://', ''))).target('_blank'),
$('ion-icon').name('clipboard').on('click', (e, $ion) => copyButtonHandler($ion, post.source)) $('ion-icon').name('clipboard').on('click', (e, $ion) => copyButtonHandler($ion, post.source))
]), ]),
new $Property('booru-url').name(Booru.name$).content([ new $Property('booru-url').name(Booru.name$).content([
$('a').href(post.url$).content(post.url$.convert((value) => value.replace('https://', ''))).target('_blank'), $('a').href(post.booruUrl$).content(post.booruUrl$.convert((value) => value.replace('https://', ''))).target('_blank'),
$('ion-icon').name('clipboard').on('click', (e, $ion) => copyButtonHandler($ion, post.source)) $('ion-icon').name('clipboard').on('click', (e, $ion) => copyButtonHandler($ion, post.booruUrl))
]), ]),
new $Property('booru-url').name('Webm').hide(true).self(async ($property) => { new $Property('webm-url').name('Webm').hide(true).self(async ($property) => {
await post.ready; await post.ready;
if (post.isUgoria) $property.content($('a').href(post.webm_url$).content(post.webm_url$.convert((value) => value.replace('https://', ''))).target('_blank')).hide(false); if (post.isUgoria) $property.content($('a').href(post.webm_url$).content(post.webm_url$.convert((value) => value.replace('https://', ''))).target('_blank')).hide(false);
}), }),

View File

@ -21,7 +21,7 @@ export class Post extends $EventManager<{update: []}> {
file_url$ = $.state(LOADING_STRING); file_url$ = $.state(LOADING_STRING);
source$ = $.state(LOADING_STRING); source$ = $.state(LOADING_STRING);
dimension$ = $.state(LOADING_STRING); dimension$ = $.state(LOADING_STRING);
url$ = $.state(LOADING_STRING); booruUrl$ = $.state(LOADING_STRING);
createdDate = new Date(this.created_at); createdDate = new Date(this.created_at);
ready?: Promise<this>; ready?: Promise<this>;
webm_url$ = $.state(LOADING_STRING); webm_url$ = $.state(LOADING_STRING);
@ -84,7 +84,7 @@ export class Post extends $EventManager<{update: []}> {
this.file_url$.set(this.file_url); this.file_url$.set(this.file_url);
this.source$.set(this.source); this.source$.set(this.source);
this.dimension$.set(`${this.image_width}x${this.image_height}`); this.dimension$.set(`${this.image_width}x${this.image_height}`);
this.url$.set(`${this.url}`); this.booruUrl$.set(`${this.booruUrl}`);
if (this.isUgoria) this.webm_url$.set(this.large_file_url); if (this.isUgoria) this.webm_url$.set(this.large_file_url);
this.createdDate = new Date(this.created_at); this.createdDate = new Date(this.created_at);
this.fire('update'); this.fire('update');
@ -133,7 +133,8 @@ export class Post extends $EventManager<{update: []}> {
return [...this.booru.tags.values()].filter(tag => tag_list.includes(tag.name)) return [...this.booru.tags.values()].filter(tag => tag_list.includes(tag.name))
} }
get previewURL() { return this.media_asset.variants?.find(variant => variant.file_ext === 'webp')?.url ?? this.large_file_url } get previewURL() { return this.media_asset.variants?.find(variant => variant.file_ext === 'webp')?.url ?? this.large_file_url }
get url() { return `${this.booru.origin}/posts/${this.id}` } get booruUrl() { return `${this.booru.origin}/posts/${this.id}` }
get url() { return `https://danbooru.defaultkavy.com/posts/${this.id}` }
get isFileSource() { return this.source.startsWith('file://') } get isFileSource() { return this.source.startsWith('file://') }
} }