shack/src/thumbnails.js

81 lines
1.8 KiB
JavaScript

import bhttp from 'bhttp';
import unprint from 'unprint';
import fs from 'fs';
import sharp from 'sharp';
import knex from './knex';
import initLogger from './logger';
const logger = initLogger();
async function createThumbnail(data, post) {
await fs.promises.mkdir('media/thumbnails', { recursive: true });
await sharp(data)
.resize(300, 300)
.jpeg({
quality: 80,
})
.toFile(`media/thumbnails/${post.id}.jpeg`);
await knex('posts')
.where('id', post.id)
.update('thumbnail', true);
logger.debug(`Saved thumbnail for ${post.id}`);
}
async function generateThumbnail(postId) {
const post = await knex('posts').where('id', postId).first();
if (!post) {
logger.warn(`Cannot generate thumbnail for non-existent post ${postId}`);
return;
}
if (!post.link) {
logger.debug(`Skipped thumbnail generation for text-only post ${postId}`);
return;
}
const res = await bhttp.get(post.link);
if (res.statusCode !== 200) {
logger.warn(`Failed thumbnail generation for ${post.link} from ${postId} (${res.statusCode})`);
return;
}
if (res.headers['content-type'].includes('image/')) {
await createThumbnail(res.body, post);
return;
}
const { query } = unprint.init(res.body.toString());
const ogHeader = query.attribute('meta[property="og:image"]', 'content');
if (ogHeader) {
const imgRes = await bhttp.get(ogHeader);
if (imgRes.statusCode === 200 && imgRes.headers['content-type'].includes('image/')) {
await createThumbnail(imgRes.body, post);
}
}
}
async function generateMissingThumbnails(force = false) {
const postsWithoutThumbnail = await knex('posts')
.select('id')
.modify((builder) => {
if (!force) {
builder.where('thumbnail', false);
}
});
await Promise.all(postsWithoutThumbnail.map(async (post) => generateThumbnail(post.id)));
}
export {
generateThumbnail,
generateMissingThumbnails,
};