Improve opengraph compatibility.

This commit is contained in:
Cameron Redmore 2025-02-15 12:38:58 +00:00
parent 4b685ad01e
commit df5396cff8
7 changed files with 361 additions and 12 deletions

View file

@ -2,6 +2,8 @@ FROM node:alpine
WORKDIR /app WORKDIR /app
RUN apk add ffmpeg
RUN npm i -g pnpm RUN npm i -g pnpm
COPY . . COPY . .

BIN
assets/music.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View file

@ -30,14 +30,63 @@ fastify.register(fastifyStatic, {
// Handle CSS file, by sending `styles.css` file from the current directory // Handle CSS file, by sending `styles.css` file from the current directory
fastify.get('/styles.css', async (request, reply) => { fastify.get('/styles.css', async (request, reply) => {
const cssContent = await fs.promises.readFile('styles.css', 'utf8') const cssContent = await fs.promises.readFile('assets/styles.css', 'utf8')
reply.header('Content-Type', 'text/css').send(cssContent) reply.header('Content-Type', 'text/css').send(cssContent)
}); });
fastify.get('/music.jpg', async (request, reply) => {
const musicImage = await fs.promises.readFile('assets/music.jpg')
reply.header('Content-Type', 'image/jpeg').send(musicImage)
});
const assetSizeCache = new Map()
//Find the width and height of a file either an image or video, and cache the result
async function getAssetSize(filePath) {
if (assetSizeCache.has(filePath)) {
return assetSizeCache.get(filePath)
}
const mimeType = mime.lookup(filePath) || 'application/octet-stream'
let assetSize = { width: 0, height: 0 }
//Use ffprobe to get the width and height of a video file
if (mimeType.startsWith('video/')) {
const { exec } = require('child_process')
const { promisify } = require('util')
const execAsync = promisify(exec)
try {
const { stdout } = await execAsync(`ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 ${filePath}`)
const [width, height] = stdout.trim().split('x').map(Number)
assetSize = { width, height }
} catch (error) {
console.error(error)
}
//Use sharp to get the width and height of an image file
} else if (mimeType.startsWith('image/')) {
const sharp = require('sharp')
try {
const metadata = await sharp(filePath).metadata()
assetSize = { width: metadata.width, height: metadata.height }
} catch (error) {
console.error(error)
}
}
assetSizeCache.set(filePath, assetSize)
return assetSize
}
// Serve HTML page with embedded file (public) // Serve HTML page with embedded file (public)
fastify.get('/s/:date/:filename', async (request, reply) => { fastify.get('/s/:date/:filename', async (request, reply) => {
let { filename, date } = request.params let { filename, date } = request.params
//Log the user agent
console.log(request.headers['user-agent'])
// Sanitize filename & date // Sanitize filename & date
filename = sanitize(filename) filename = sanitize(filename)
date = sanitize(date) date = sanitize(date)
@ -53,21 +102,36 @@ fastify.get('/s/:date/:filename', async (request, reply) => {
//Check if the file exists, if not, return 404 //Check if the file exists, if not, return 404
await fs.promises.access(filePath) await fs.promises.access(filePath)
const {width, height} = await getAssetSize(filePath);
const mimeType = mime.lookup(filename) || 'application/octet-stream' const mimeType = mime.lookup(filename) || 'application/octet-stream'
let fileContent = ''; let fileContent = '';
let ogTags = ''; let ogTags = `<meta property="og:title" content="${process.env.SITE_NAME || 'CamDN'} - ${filename}" />
<meta property="og:url" content="${process.env.HOST || ''}/s/${date}/${filename}" />\n`;
if (mimeType.startsWith('image/')) { if (mimeType.startsWith('image/')) {
fileContent = `<img src="/f/${joinedPath}" alt="${filename}" />` fileContent = `<img src="/f/${joinedPath}" alt="${filename}" width="${width}" height="${height} />`
ogTags = `<meta property="og:image" content="/f/${joinedPath}" />` ogTags += `<meta property="og:image" content="/f/${joinedPath}" />
<meta property="og:image:type" content="${mimeType}" />
<meta property="og:image:width" content="${width}" />
<meta property="og:image:height" content="${height}" />
<meta property="og:type" content="website" />
`
} else if (mimeType.startsWith('video/')) { } else if (mimeType.startsWith('video/')) {
fileContent = `<video controls><source src="/f/${joinedPath}" type="${mimeType}"></video>` fileContent = `<video controls width="${width}" height="${height}><source src="/f/${joinedPath}" type="${mimeType}"></video>`
ogTags = `<meta property="og:video" content="/f/${joinedPath}" /> ogTags += `<meta property="og:video" content="/f/${joinedPath}" />
<meta property="og:video:type" content="${mimeType}" />` <meta property="og:video:type" content="${mimeType}" />
<meta property="og:video:width" content="${width}" />
<meta property="og:video:height" content="${height}" />
<meta property="og:image" content="/f/${joinedPath}.thumb.jpg" />
<meta property="og:type" content="video.other" />
`
} else if (mimeType.startsWith('audio/')) { } else if (mimeType.startsWith('audio/')) {
fileContent = `<audio controls><source src="/f/${joinedPath}" type="${mimeType}"></audio>` fileContent = `<audio controls><source src="/f/${joinedPath}" type="${mimeType}"></audio>`
ogTags = `<meta property="og:audio" content="/f/${joinedPath}" /> ogTags += `<meta property="og:audio" content="/f/${joinedPath}" />
<meta property="og:audio:type" content="${mimeType}" />` <meta property="og:audio:type" content="${mimeType}" />
<meta property="og:image" content="/music.jpg" />
<meta property="og:type" content="music.song" />`
} else if (mimeType.startsWith('text/')) { } else if (mimeType.startsWith('text/')) {
const fileData = await fs.promises.readFile(filePath, 'utf8'); const fileData = await fs.promises.readFile(filePath, 'utf8');
fileContent = `<pre>${fileData}</pre>` fileContent = `<pre>${fileData}</pre>`
@ -75,7 +139,7 @@ fastify.get('/s/:date/:filename', async (request, reply) => {
fileContent = `<a href="/f/$${joinedPath}">Download File</a>` fileContent = `<a href="/f/$${joinedPath}">Download File</a>`
} }
const htmlContent = await fs.promises.readFile('view.html', 'utf8'); const htmlContent = await fs.promises.readFile('assets/view.html', 'utf8');
//Replace placeholders with actual content //Replace placeholders with actual content
const html = htmlContent const html = htmlContent
@ -137,6 +201,21 @@ fastify.put('/upload', {
message: 'File uploaded successfully', message: 'File uploaded successfully',
fileName: host.replace(/\/$/, '') + '/s/' + path.join(dateFolder, fileName) fileName: host.replace(/\/$/, '') + '/s/' + path.join(dateFolder, fileName)
}) })
//If the file is a video, generate a thumbnail
const mimeType = mime.lookup(fileName) || 'application/octet-stream'
if (mimeType.startsWith('video/')) {
const { exec } = require('child_process')
const { promisify } = require('util')
const execAsync = promisify(exec)
const thumbnailPath = path.join(fullUploadDir, fileName + '.thumb.jpg')
try {
await execAsync(`ffmpeg -i ${filePath} -ss 00:00:01 -vframes 1 ${thumbnailPath}`)
} catch (error) {
console.error(error)
}
}
} catch (err) { } catch (err) {
reply.code(500).send({ error: 'Error uploading file' }) reply.code(500).send({ error: 'Error uploading file' })
} }

View file

@ -15,7 +15,13 @@
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"fastify": "^5.2.1", "fastify": "^5.2.1",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"sanitize-filename": "^1.6.3" "sanitize-filename": "^1.6.3",
"sharp": "^0.33.5"
}, },
"type": "module" "type": "module",
"pnpm": {
"onlyBuiltDependencies": [
"sharp"
]
}
} }

262
pnpm-lock.yaml generated
View file

@ -26,9 +26,15 @@ importers:
sanitize-filename: sanitize-filename:
specifier: ^1.6.3 specifier: ^1.6.3
version: 1.6.3 version: 1.6.3
sharp:
specifier: ^0.33.5
version: 0.33.5
packages: packages:
'@emnapi/runtime@1.3.1':
resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
'@fastify/accept-negotiator@2.0.1': '@fastify/accept-negotiator@2.0.1':
resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
@ -65,6 +71,111 @@ packages:
'@fastify/static@8.1.0': '@fastify/static@8.1.0':
resolution: {integrity: sha512-lPb8+1ulvbGSUSQ0/adBDyp/Ye/MX+pBwhkLAr8/GU88kNnJlSu7KXdyW6CCOROcr5BgrqJD01lEOosozFAegw==} resolution: {integrity: sha512-lPb8+1ulvbGSUSQ0/adBDyp/Ye/MX+pBwhkLAr8/GU88kNnJlSu7KXdyW6CCOROcr5BgrqJD01lEOosozFAegw==}
'@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.33.5':
resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.0.4':
resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.0.4':
resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.0.4':
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linux-arm@1.0.5':
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
cpu: [arm]
os: [linux]
'@img/sharp-libvips-linux-s390x@1.0.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x]
os: [linux]
'@img/sharp-libvips-linux-x64@1.0.4':
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
cpu: [x64]
os: [linux]
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
cpu: [x64]
os: [linux]
'@img/sharp-linux-arm64@0.33.5':
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linux-arm@0.33.5':
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
'@img/sharp-linux-s390x@0.33.5':
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
'@img/sharp-linux-x64@0.33.5':
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-linuxmusl-arm64@0.33.5':
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linuxmusl-x64@0.33.5':
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-wasm32@0.33.5':
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-ia32@0.33.5':
resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.33.5':
resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -123,6 +234,13 @@ packages:
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
content-disposition@0.5.4: content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -143,6 +261,10 @@ packages:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'} engines: {node: '>=6'}
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
dotenv@16.4.7: dotenv@16.4.7:
resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -211,6 +333,9 @@ packages:
resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
is-fullwidth-code-point@3.0.0: is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -333,6 +458,10 @@ packages:
setprototypeof@1.2.0: setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
shebang-command@2.0.0: shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -345,6 +474,9 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'} engines: {node: '>=14'}
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
sonic-boom@4.2.0: sonic-boom@4.2.0:
resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
@ -386,6 +518,9 @@ packages:
truncate-utf8-bytes@1.0.2: truncate-utf8-bytes@1.0.2:
resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
utf8-byte-length@1.0.5: utf8-byte-length@1.0.5:
resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==}
@ -404,6 +539,11 @@ packages:
snapshots: snapshots:
'@emnapi/runtime@1.3.1':
dependencies:
tslib: 2.8.1
optional: true
'@fastify/accept-negotiator@2.0.1': {} '@fastify/accept-negotiator@2.0.1': {}
'@fastify/ajv-compiler@4.0.2': '@fastify/ajv-compiler@4.0.2':
@ -458,6 +598,81 @@ snapshots:
fastq: 1.19.0 fastq: 1.19.0
glob: 11.0.1 glob: 11.0.1
'@img/sharp-darwin-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.4
optional: true
'@img/sharp-darwin-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.0.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.0.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm@1.0.5':
optional: true
'@img/sharp-libvips-linux-s390x@1.0.4':
optional: true
'@img/sharp-libvips-linux-x64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
optional: true
'@img/sharp-linux-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.0.4
optional: true
'@img/sharp-linux-arm@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.0.5
optional: true
'@img/sharp-linux-s390x@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.0.4
optional: true
'@img/sharp-linux-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.0.4
optional: true
'@img/sharp-linuxmusl-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
optional: true
'@img/sharp-linuxmusl-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
optional: true
'@img/sharp-wasm32@0.33.5':
dependencies:
'@emnapi/runtime': 1.3.1
optional: true
'@img/sharp-win32-ia32@0.33.5':
optional: true
'@img/sharp-win32-x64@0.33.5':
optional: true
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
dependencies: dependencies:
string-width: 5.1.2 string-width: 5.1.2
@ -511,6 +726,16 @@ snapshots:
color-name@1.1.4: {} color-name@1.1.4: {}
color-string@1.9.1:
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
color@4.2.3:
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
content-disposition@0.5.4: content-disposition@0.5.4:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
@ -527,6 +752,8 @@ snapshots:
dequal@2.0.3: {} dequal@2.0.3: {}
detect-libc@2.0.3: {}
dotenv@16.4.7: {} dotenv@16.4.7: {}
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}
@ -614,6 +841,8 @@ snapshots:
ipaddr.js@2.2.0: {} ipaddr.js@2.2.0: {}
is-arrayish@0.3.2: {}
is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@3.0.0: {}
isexe@2.0.0: {} isexe@2.0.0: {}
@ -715,6 +944,32 @@ snapshots:
setprototypeof@1.2.0: {} setprototypeof@1.2.0: {}
sharp@0.33.5:
dependencies:
color: 4.2.3
detect-libc: 2.0.3
semver: 7.7.1
optionalDependencies:
'@img/sharp-darwin-arm64': 0.33.5
'@img/sharp-darwin-x64': 0.33.5
'@img/sharp-libvips-darwin-arm64': 1.0.4
'@img/sharp-libvips-darwin-x64': 1.0.4
'@img/sharp-libvips-linux-arm': 1.0.5
'@img/sharp-libvips-linux-arm64': 1.0.4
'@img/sharp-libvips-linux-s390x': 1.0.4
'@img/sharp-libvips-linux-x64': 1.0.4
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
'@img/sharp-linux-arm': 0.33.5
'@img/sharp-linux-arm64': 0.33.5
'@img/sharp-linux-s390x': 0.33.5
'@img/sharp-linux-x64': 0.33.5
'@img/sharp-linuxmusl-arm64': 0.33.5
'@img/sharp-linuxmusl-x64': 0.33.5
'@img/sharp-wasm32': 0.33.5
'@img/sharp-win32-ia32': 0.33.5
'@img/sharp-win32-x64': 0.33.5
shebang-command@2.0.0: shebang-command@2.0.0:
dependencies: dependencies:
shebang-regex: 3.0.0 shebang-regex: 3.0.0
@ -723,6 +978,10 @@ snapshots:
signal-exit@4.1.0: {} signal-exit@4.1.0: {}
simple-swizzle@0.2.2:
dependencies:
is-arrayish: 0.3.2
sonic-boom@4.2.0: sonic-boom@4.2.0:
dependencies: dependencies:
atomic-sleep: 1.0.0 atomic-sleep: 1.0.0
@ -763,6 +1022,9 @@ snapshots:
dependencies: dependencies:
utf8-byte-length: 1.0.5 utf8-byte-length: 1.0.5
tslib@2.8.1:
optional: true
utf8-byte-length@1.0.5: {} utf8-byte-length@1.0.5: {}
which@2.0.2: which@2.0.2: