Compare commits

...

2 commits

Author SHA1 Message Date
2de60a98a3 reformat with prettier 2024-07-14 20:33:47 -04:00
1d4a2673b0 add image upload from clipboard 2024-07-14 20:33:41 -04:00
6 changed files with 110 additions and 104 deletions

View file

@ -181,8 +181,8 @@ export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, te
export function wordballoonDrawImage(ctx: CanvasRenderingContext2D, at: Point, img: HTMLImageElement, hasTip: boolean = true): Rect {
// Round the image size up to the nearest 12x12, plus 12px padding.
let size = {
w: (Math.ceil(img.width / 12) * 12) + 6,
h: (Math.ceil(img.height / 12) * 12) + 6
w: Math.ceil(img.width / 12) * 12 + 6,
h: Math.ceil(img.height / 12) * 12 + 6
};
// Draw the word balloon and get the inner rect
@ -195,6 +195,6 @@ export function wordballoonDrawImage(ctx: CanvasRenderingContext2D, at: Point, i
x: at.x,
y: at.y,
w: rectInner.w + 12 * 3 + 12,
h: rectInner.h + 13 * 3 + 18,
h: rectInner.h + 13 * 3 + 18
};
}

View file

@ -62,6 +62,6 @@ export interface DiscordConfig {
}
export interface ImagesConfig {
maxSize: { width: number, height: number };
maxSize: { width: number; height: number };
expirySeconds: number;
}

View file

@ -1,8 +1,8 @@
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { fileTypeFromBuffer } from "file-type";
import * as crypto from "crypto";
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { fileTypeFromBuffer } from 'file-type';
import * as crypto from 'crypto';
import Sharp from 'sharp';
import { ImagesConfig } from "./config";
import { ImagesConfig } from './config';
export interface image {
img: Buffer;
@ -10,12 +10,7 @@ export interface image {
timeout: NodeJS.Timeout;
}
const allowedTypes = [
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
];
const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
function randomString(length: number): Promise<string> {
return new Promise((res, rej) => {
@ -26,7 +21,7 @@ function randomString(length: number): Promise<string> {
rej(err);
return;
}
let out = buf.toString("hex");
let out = buf.toString('hex');
if (out.length !== length) out = out.substring(0, length);
res(out);
});
@ -41,10 +36,10 @@ export class ImageUploader {
this.config = config;
for (let type of allowedTypes) {
// i kinda hate this
app.addContentTypeParser(type, {parseAs: "buffer"}, (req, body, done) => done(null, body));
app.addContentTypeParser(type, { parseAs: 'buffer' }, (req, body, done) => done(null, body));
}
app.put("/api/upload", async (req, res) => await this.handleRequest(req, res));
app.get("/api/image/:id", (req, res) => this.handleGet(req, res));
app.put('/api/upload', async (req, res) => await this.handleRequest(req, res));
app.get('/api/image/:id', (req, res) => this.handleGet(req, res));
}
private handleGet(req: FastifyRequest, res: FastifyReply) {
@ -52,17 +47,17 @@ export class ImageUploader {
let img = this.images.get(id);
if (!img) {
res.status(404);
return { success: false, error: "Image not found" };
return { success: false, error: 'Image not found' };
}
res.header("Content-Type", img.mime);
res.header('Content-Type', img.mime);
return img.img;
}
private async handleRequest(req: FastifyRequest, res: FastifyReply) {
let contentType;
if ((contentType = req.headers["content-type"]) === undefined || !allowedTypes.includes(contentType)) {
if ((contentType = req.headers['content-type']) === undefined || !allowedTypes.includes(contentType)) {
res.status(400);
return { success: false, error: "Invalid Content-Type" };
return { success: false, error: 'Invalid Content-Type' };
}
let data = req.body as Buffer;
@ -72,7 +67,7 @@ export class ImageUploader {
if (mime?.mime !== contentType) {
res.status(400);
return { success: false, error: "Image is corrupt" };
return { success: false, error: 'Image is corrupt' };
}
// Parse and resize if necessary
@ -83,16 +78,16 @@ export class ImageUploader {
meta = await sharp.metadata();
} catch {
res.status(400);
return { success: false, error: "Image is corrupt" };
return { success: false, error: 'Image is corrupt' };
}
if (!meta.width || !meta.height) {
res.status(400);
return { success: false, error: "Image is corrupt" };
return { success: false, error: 'Image is corrupt' };
}
if (meta.width > this.config.maxSize.width || meta.height > this.config.maxSize.height) {
sharp.resize(this.config.maxSize.width, this.config.maxSize.height, { fit: "inside" });
sharp.resize(this.config.maxSize.width, this.config.maxSize.height, { fit: 'inside' });
}
let outputImg = await sharp.toBuffer();

View file

@ -43,7 +43,7 @@ const app = Fastify({
app.register(FastifyCors, {
origin: config.http.origins,
methods: ['GET', 'POST', 'PUT'],
methods: ['GET', 'POST', 'PUT']
});
app.register(FastifyWS);

View file

@ -114,7 +114,7 @@ export class MSAgentChatRoom {
for (const _client of this.getActiveClients()) {
_client.send(msg);
}
})
});
client.on('admin', () => {
let msg: MSAgentPromoteMessage = {
op: MSAgentProtocolMessageType.Promote,

View file

@ -129,19 +129,30 @@ elements.imageUploadBtn.addEventListener('click', () => {
imgUploadInput.click();
});
document.addEventListener('paste', (e) => {
if (e.clipboardData?.files.length === 0) return;
let file = e.clipboardData!.files[0];
if (!file.type.startsWith('image/')) return;
if (!window.confirm(`Upload ${file.name} (${Math.round(file.size / 1000)}KB)?`)) return;
uploadFile(file);
});
imgUploadInput.addEventListener('change', async () => {
if (!imgUploadInput.files || imgUploadInput.files.length === 0) return;
let file = imgUploadInput.files[0];
uploadFile(imgUploadInput.files[0]);
});
function uploadFile(file: File) {
let reader = new FileReader();
reader.onload = async () => {
let buffer = reader.result as ArrayBuffer;
await Room?.sendImage(buffer, file.type);
};
reader.readAsArrayBuffer(file);
});
}
let w = window as any;
w.agentchat = {
getRoom: () => Room,
}
getRoom: () => Room
};