Spaces:
Sleeping
Sleeping
Download artifacts/api-server/src/lib/articleImages.ts from Tahasaif3/DataExtract: direct link, hf CLI and curl.
- Browser
- Download file 2.36 kB
-
https://huggingface.co/spaces/Tahasaif3/DataExtract/resolve/main/artifacts/api-server/src/lib/articleImages.ts
- Command line
-
hf download hf://spaces/Tahasaif3/DataExtract/artifacts/api-server/src/lib/articleImages.ts
-
curl -L -o articleImages.ts https://huggingface.co/spaces/Tahasaif3/DataExtract/resolve/main/artifacts/api-server/src/lib/articleImages.ts
2.36 kB
| import { GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; | |
| import { randomUUID } from 'node:crypto'; | |
| import { PassThrough, type Readable } from 'node:stream'; | |
| const bucket = process.env.R2_BUCKET; | |
| const s3 = new S3Client({ | |
| region: 'auto', | |
| endpoint: process.env.R2_ACCOUNT_ID ? `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com` : undefined, | |
| credentials: { | |
| accessKeyId: process.env.R2_ACCESS_KEY_ID || '', | |
| secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || '', | |
| }, | |
| }); | |
| function objectKey(id: string) { | |
| return `article-images/${id}`; | |
| } | |
| export function articleImageFile(id: string) { | |
| if (!bucket) throw new Error('App Storage is not configured.'); | |
| const key = objectKey(id); | |
| return { | |
| async getMetadata() { | |
| const head = await s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key })); | |
| return [{ contentType: head.ContentType }] as const; | |
| }, | |
| createReadStream() { | |
| const stream = new PassThrough(); | |
| s3.send(new GetObjectCommand({ Bucket: bucket, Key: key })) | |
| .then((object) => { | |
| const body = object.Body as Readable; | |
| body.on('error', (err) => stream.emit('error', err)); | |
| body.pipe(stream); | |
| }) | |
| .catch((err) => stream.emit('error', err)); | |
| return stream; | |
| }, | |
| async save(bytes: Buffer, options: { contentType: string }) { | |
| await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: bytes, ContentType: options.contentType })); | |
| }, | |
| }; | |
| } | |
| // Reject renamed HTML/SVG and executable files, not just incorrect MIME labels. | |
| export function imageMime(bytes: Buffer) { | |
| if (bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) return 'image/png'; | |
| if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return 'image/jpeg'; | |
| if (/^GIF8[79]a$/.test(bytes.subarray(0, 6).toString())) return 'image/gif'; | |
| if (bytes.subarray(0, 4).toString() === 'RIFF' && bytes.subarray(8, 12).toString() === 'WEBP') return 'image/webp'; | |
| return null; | |
| } | |
| export async function saveArticleImage(bytes: Buffer) { | |
| const contentType = imageMime(bytes); | |
| if (!contentType) throw new Error('Choose a JPG, PNG, WEBP or GIF image.'); | |
| const id = randomUUID(); | |
| await articleImageFile(id).save(bytes, { contentType }); | |
| return `/api/article-images/${id}`; | |
| } | |