add image upload support
This commit is contained in:
parent
653ce2e413
commit
8394920d4d
14 changed files with 697 additions and 40 deletions
|
@ -4,7 +4,7 @@ import { ContextMenu, ContextMenuItem } from './contextmenu.js';
|
||||||
import { AcsAnimation, AcsAnimationFrameInfo } from './structs/animation.js';
|
import { AcsAnimation, AcsAnimationFrameInfo } from './structs/animation.js';
|
||||||
import { AcsImageEntry } from './structs/image.js';
|
import { AcsImageEntry } from './structs/image.js';
|
||||||
import { Point, Size } from './types.js';
|
import { Point, Size } from './types.js';
|
||||||
import { wordballoonDrawText } from './wordballoon.js';
|
import { wordballoonDrawImage, wordballoonDrawText } from './wordballoon.js';
|
||||||
|
|
||||||
// probably should be in a utility module
|
// probably should be in a utility module
|
||||||
function dwAlign(off: number): number {
|
function dwAlign(off: number): number {
|
||||||
|
@ -61,44 +61,19 @@ enum AgentWordBalloonPosition {
|
||||||
BelowCentered
|
BelowCentered
|
||||||
}
|
}
|
||||||
|
|
||||||
class AgentWordBalloonState {
|
abstract class AgentWordBalloonState {
|
||||||
char: Agent;
|
abstract char: Agent;
|
||||||
text: string;
|
abstract hasTip: boolean;
|
||||||
hasTip: boolean;
|
abstract position: AgentWordBalloonPosition;
|
||||||
position: AgentWordBalloonPosition;
|
|
||||||
|
|
||||||
balloonCanvas: HTMLCanvasElement;
|
balloonCanvas: HTMLCanvasElement;
|
||||||
balloonCanvasCtx: CanvasRenderingContext2D;
|
balloonCanvasCtx: CanvasRenderingContext2D;
|
||||||
|
|
||||||
constructor(char: Agent, text: string, hasTip: boolean, position: AgentWordBalloonPosition, textColor: string) {
|
constructor() {
|
||||||
this.char = char;
|
|
||||||
this.text = text;
|
|
||||||
this.hasTip = hasTip;
|
|
||||||
this.position = position;
|
|
||||||
this.balloonCanvas = document.createElement('canvas');
|
this.balloonCanvas = document.createElement('canvas');
|
||||||
this.balloonCanvasCtx = this.balloonCanvas.getContext('2d')!;
|
this.balloonCanvasCtx = this.balloonCanvas.getContext('2d')!;
|
||||||
|
|
||||||
this.balloonCanvas.style.position = 'absolute';
|
this.balloonCanvas.style.position = 'absolute';
|
||||||
|
|
||||||
this.balloonCanvasCtx.font = '14px arial';
|
|
||||||
|
|
||||||
this.balloonCanvas.width = 300;
|
|
||||||
this.balloonCanvas.height = 300;
|
|
||||||
|
|
||||||
// hack fix for above
|
|
||||||
this.balloonCanvas.style.pointerEvents = 'none';
|
this.balloonCanvas.style.pointerEvents = 'none';
|
||||||
|
|
||||||
let rect = wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20, hasTip, textColor);
|
|
||||||
|
|
||||||
// Second pass, actually set the element to the right width and stuffs
|
|
||||||
this.balloonCanvas.width = rect.w;
|
|
||||||
this.balloonCanvas.height = rect.h;
|
|
||||||
|
|
||||||
wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20, hasTip, textColor);
|
|
||||||
|
|
||||||
this.char.getElement().appendChild(this.balloonCanvas);
|
|
||||||
|
|
||||||
this.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
|
@ -129,6 +104,66 @@ class AgentWordBalloonState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AgentTextWordBalloonState extends AgentWordBalloonState {
|
||||||
|
char: Agent;
|
||||||
|
text: string;
|
||||||
|
hasTip: boolean;
|
||||||
|
position: AgentWordBalloonPosition;
|
||||||
|
|
||||||
|
constructor(char: Agent, text: string, hasTip: boolean, position: AgentWordBalloonPosition, textColor: string) {
|
||||||
|
super();
|
||||||
|
this.char = char;
|
||||||
|
this.text = text;
|
||||||
|
this.hasTip = hasTip;
|
||||||
|
this.position = position;
|
||||||
|
this.balloonCanvasCtx.font = '14px arial';
|
||||||
|
|
||||||
|
this.balloonCanvas.width = 300;
|
||||||
|
this.balloonCanvas.height = 300;
|
||||||
|
|
||||||
|
let rect = wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20, hasTip, textColor);
|
||||||
|
|
||||||
|
// Second pass, actually set the element to the right width and stuffs
|
||||||
|
this.balloonCanvas.width = rect.w;
|
||||||
|
this.balloonCanvas.height = rect.h;
|
||||||
|
|
||||||
|
wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20, hasTip, textColor);
|
||||||
|
|
||||||
|
this.char.getElement().appendChild(this.balloonCanvas);
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AgentImageWordBalloonState extends AgentWordBalloonState {
|
||||||
|
char: Agent;
|
||||||
|
img: HTMLImageElement;
|
||||||
|
hasTip: boolean;
|
||||||
|
position: AgentWordBalloonPosition;
|
||||||
|
|
||||||
|
constructor(char: Agent, img: HTMLImageElement, hasTip: boolean, position: AgentWordBalloonPosition) {
|
||||||
|
super();
|
||||||
|
this.char = char;
|
||||||
|
this.img = img;
|
||||||
|
this.hasTip = hasTip;
|
||||||
|
this.position = position;
|
||||||
|
|
||||||
|
this.balloonCanvas.width = 300;
|
||||||
|
this.balloonCanvas.height = 300;
|
||||||
|
|
||||||
|
let rect = wordballoonDrawImage(this.balloonCanvasCtx, { x: 0, y: 0 }, this.img, hasTip);
|
||||||
|
|
||||||
|
// Second pass, actually set the element to the right width and stuffs
|
||||||
|
this.balloonCanvas.width = rect.w;
|
||||||
|
this.balloonCanvas.height = rect.h;
|
||||||
|
|
||||||
|
wordballoonDrawImage(this.balloonCanvasCtx, { x: 0, y: 0 }, this.img, hasTip);
|
||||||
|
|
||||||
|
this.char.getElement().appendChild(this.balloonCanvas);
|
||||||
|
this.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Agent {
|
export class Agent {
|
||||||
private data: AcsData;
|
private data: AcsData;
|
||||||
private charDiv: HTMLDivElement;
|
private charDiv: HTMLDivElement;
|
||||||
|
@ -141,7 +176,7 @@ export class Agent {
|
||||||
|
|
||||||
private animState: AgentAnimationState | null = null;
|
private animState: AgentAnimationState | null = null;
|
||||||
private wordballoonState: AgentWordBalloonState | null = null;
|
private wordballoonState: AgentWordBalloonState | null = null;
|
||||||
private usernameBalloonState: AgentWordBalloonState | null = null;
|
private usernameBalloonState: AgentTextWordBalloonState | null = null;
|
||||||
|
|
||||||
private contextMenu: ContextMenu;
|
private contextMenu: ContextMenu;
|
||||||
|
|
||||||
|
@ -299,7 +334,7 @@ export class Agent {
|
||||||
this.usernameBalloonState = null;
|
this.usernameBalloonState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.usernameBalloonState = new AgentWordBalloonState(this, username, false, AgentWordBalloonPosition.BelowCentered, color);
|
this.usernameBalloonState = new AgentTextWordBalloonState(this, username, false, AgentWordBalloonPosition.BelowCentered, color);
|
||||||
this.usernameBalloonState.show();
|
this.usernameBalloonState.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +343,17 @@ export class Agent {
|
||||||
this.stopSpeaking();
|
this.stopSpeaking();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.wordballoonState = new AgentWordBalloonState(this, text, true, AgentWordBalloonPosition.AboveCentered, '#000000');
|
this.wordballoonState = new AgentTextWordBalloonState(this, text, true, AgentWordBalloonPosition.AboveCentered, '#000000');
|
||||||
|
this.wordballoonState.positionUpdated();
|
||||||
|
this.wordballoonState.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
speakImage(img: HTMLImageElement) {
|
||||||
|
if (this.wordballoonState != null) {
|
||||||
|
this.stopSpeaking();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wordballoonState = new AgentImageWordBalloonState(this, img, true, AgentWordBalloonPosition.AboveCentered);
|
||||||
this.wordballoonState.positionUpdated();
|
this.wordballoonState.positionUpdated();
|
||||||
this.wordballoonState.show();
|
this.wordballoonState.show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,3 +177,24 @@ export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, te
|
||||||
h: rectInner.h + 13 * 3 + 18
|
h: rectInner.h + 13 * 3 + 18
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw the word balloon and get the inner rect
|
||||||
|
let rectInner = wordballoonDraw(ctx, at, size, hasTip);
|
||||||
|
|
||||||
|
// Draw the image
|
||||||
|
ctx.drawImage(img, 6, 6);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: at.x,
|
||||||
|
y: at.y,
|
||||||
|
w: rectInner.w + 12 * 3 + 12,
|
||||||
|
h: rectInner.h + 13 * 3 + 18,
|
||||||
|
};
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ export enum MSAgentProtocolMessageType {
|
||||||
KeepAlive = 'nop',
|
KeepAlive = 'nop',
|
||||||
Join = 'join',
|
Join = 'join',
|
||||||
Talk = 'talk',
|
Talk = 'talk',
|
||||||
|
SendImage = 'img',
|
||||||
Admin = 'admin',
|
Admin = 'admin',
|
||||||
// Server-to-client
|
// Server-to-client
|
||||||
Init = 'init',
|
Init = 'init',
|
||||||
|
@ -36,6 +37,13 @@ export interface MSAgentTalkMessage extends MSAgentProtocolMessage {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MSAgentSendImageMessage extends MSAgentProtocolMessage {
|
||||||
|
op: MSAgentProtocolMessageType.SendImage;
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Server-to-client
|
// Server-to-client
|
||||||
|
|
||||||
export interface MSAgentInitMessage extends MSAgentProtocolMessage {
|
export interface MSAgentInitMessage extends MSAgentProtocolMessage {
|
||||||
|
@ -76,6 +84,14 @@ export interface MSAgentChatMessage extends MSAgentProtocolMessage {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MSAgentImageMessage extends MSAgentProtocolMessage {
|
||||||
|
op: MSAgentProtocolMessageType.SendImage;
|
||||||
|
data: {
|
||||||
|
username: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface MSAgentPromoteMessage extends MSAgentProtocolMessage {
|
export interface MSAgentPromoteMessage extends MSAgentProtocolMessage {
|
||||||
op: MSAgentProtocolMessageType.Promote;
|
op: MSAgentProtocolMessageType.Promote;
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
port = 3000
|
port = 3000
|
||||||
proxied = true
|
proxied = true
|
||||||
|
# Allowed CORS origins.
|
||||||
|
# true = All origins allowed (not recommended in production)
|
||||||
|
# false = Cross-origin requests are not allowed
|
||||||
|
# String or array of strings = Only the specified origin(s) are allowed
|
||||||
|
origins = true
|
||||||
|
|
||||||
[mysql]
|
[mysql]
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
|
@ -46,6 +51,10 @@ tempDir = "/tmp/msac-tts"
|
||||||
transcodeOpus = true
|
transcodeOpus = true
|
||||||
wavExpirySeconds = 60
|
wavExpirySeconds = 60
|
||||||
|
|
||||||
|
[images]
|
||||||
|
maxSize = { width = 300, height = 300 }
|
||||||
|
expirySeconds = 60
|
||||||
|
|
||||||
[[agents]]
|
[[agents]]
|
||||||
friendlyName = "Clippy"
|
friendlyName = "Clippy"
|
||||||
filename = "CLIPPIT.ACS"
|
filename = "CLIPPIT.ACS"
|
||||||
|
|
|
@ -12,13 +12,16 @@
|
||||||
"typescript": "5.4.5"
|
"typescript": "5.4.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fastify/cors": "^9.0.1",
|
||||||
"@fastify/static": "^7.0.4",
|
"@fastify/static": "^7.0.4",
|
||||||
"@fastify/websocket": "^10.0.1",
|
"@fastify/websocket": "^10.0.1",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.15.3",
|
||||||
"fastify": "^4.28.1",
|
"fastify": "^4.28.1",
|
||||||
|
"file-type": "^19.1.1",
|
||||||
"fluent-ffmpeg": "^2.1.3",
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"mysql2": "^3.10.2",
|
"mysql2": "^3.10.2",
|
||||||
|
"sharp": "^0.33.4",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"ws": "^8.17.1"
|
"ws": "^8.17.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
MSAgentAdminMessage,
|
MSAgentAdminMessage,
|
||||||
MSAgentAdminOperation,
|
MSAgentAdminOperation,
|
||||||
MSAgentErrorMessage,
|
MSAgentErrorMessage,
|
||||||
|
MSAgentSendImageMessage,
|
||||||
MSAgentJoinMessage,
|
MSAgentJoinMessage,
|
||||||
MSAgentProtocolMessage,
|
MSAgentProtocolMessage,
|
||||||
MSAgentProtocolMessageType,
|
MSAgentProtocolMessageType,
|
||||||
|
@ -26,6 +27,7 @@ export interface Client {
|
||||||
on(event: 'join', listener: () => void): this;
|
on(event: 'join', listener: () => void): this;
|
||||||
on(event: 'close', listener: () => void): this;
|
on(event: 'close', listener: () => void): this;
|
||||||
on(event: 'talk', listener: (msg: string) => void): this;
|
on(event: 'talk', listener: (msg: string) => void): this;
|
||||||
|
on(event: 'image', listener: (id: string) => void): this;
|
||||||
|
|
||||||
on(event: string, listener: Function): this;
|
on(event: string, listener: Function): this;
|
||||||
}
|
}
|
||||||
|
@ -160,6 +162,12 @@ export class Client extends EventEmitter {
|
||||||
this.emit('talk', talkMsg.data.msg);
|
this.emit('talk', talkMsg.data.msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case MSAgentProtocolMessageType.SendImage: {
|
||||||
|
let imgMsg = msg as MSAgentSendImageMessage;
|
||||||
|
if (!imgMsg.data || !imgMsg.data.id || !this.chatRateLimit.request()) return;
|
||||||
|
this.emit('image', imgMsg.data.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case MSAgentProtocolMessageType.Admin: {
|
case MSAgentProtocolMessageType.Admin: {
|
||||||
let adminMsg = msg as MSAgentAdminMessage;
|
let adminMsg = msg as MSAgentAdminMessage;
|
||||||
if (!adminMsg.data) return;
|
if (!adminMsg.data) return;
|
||||||
|
|
|
@ -3,12 +3,14 @@ export interface IConfig {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
proxied: boolean;
|
proxied: boolean;
|
||||||
|
origins: string | string[] | boolean;
|
||||||
};
|
};
|
||||||
mysql: MySQLConfig;
|
mysql: MySQLConfig;
|
||||||
chat: ChatConfig;
|
chat: ChatConfig;
|
||||||
motd: motdConfig;
|
motd: motdConfig;
|
||||||
discord: DiscordConfig;
|
discord: DiscordConfig;
|
||||||
tts: TTSConfig;
|
tts: TTSConfig;
|
||||||
|
images: ImagesConfig;
|
||||||
agents: AgentConfig[];
|
agents: AgentConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,3 +60,8 @@ export interface DiscordConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
webhookURL: string;
|
webhookURL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImagesConfig {
|
||||||
|
maxSize: { width: number, height: number };
|
||||||
|
expirySeconds: number;
|
||||||
|
}
|
116
server/src/imageuploader.ts
Normal file
116
server/src/imageuploader.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||||
|
import { fileTypeFromBuffer } from "file-type";
|
||||||
|
import * as crypto from "crypto";
|
||||||
|
import Sharp from 'sharp';
|
||||||
|
import { ImagesConfig } from "./config";
|
||||||
|
|
||||||
|
export interface image {
|
||||||
|
img: Buffer;
|
||||||
|
mime: string;
|
||||||
|
timeout: NodeJS.Timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedTypes = [
|
||||||
|
"image/png",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/gif",
|
||||||
|
"image/webp",
|
||||||
|
];
|
||||||
|
|
||||||
|
function randomString(length: number): Promise<string> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
let _len = length;
|
||||||
|
if (_len % 2 !== 0) _len++;
|
||||||
|
crypto.randomBytes(_len / 2, (err, buf) => {
|
||||||
|
if (err) {
|
||||||
|
rej(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let out = buf.toString("hex");
|
||||||
|
if (out.length !== length) out = out.substring(0, length);
|
||||||
|
res(out);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ImageUploader {
|
||||||
|
private images: Map<string, image> = new Map();
|
||||||
|
private config: ImagesConfig;
|
||||||
|
|
||||||
|
constructor(app: FastifyInstance, config: ImagesConfig) {
|
||||||
|
this.config = config;
|
||||||
|
for (let type of allowedTypes) {
|
||||||
|
// i kinda hate this
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleGet(req: FastifyRequest, res: FastifyReply) {
|
||||||
|
let {id} = req.params as {id: string};
|
||||||
|
let img = this.images.get(id);
|
||||||
|
if (!img) {
|
||||||
|
res.status(404);
|
||||||
|
return { success: false, error: "Image not found" };
|
||||||
|
}
|
||||||
|
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)) {
|
||||||
|
res.status(400);
|
||||||
|
return { success: false, error: "Invalid Content-Type" };
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = req.body as Buffer;
|
||||||
|
|
||||||
|
// Check MIME
|
||||||
|
let mime = await fileTypeFromBuffer(data);
|
||||||
|
|
||||||
|
if (mime?.mime !== contentType) {
|
||||||
|
res.status(400);
|
||||||
|
return { success: false, error: "Image is corrupt" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and resize if necessary
|
||||||
|
|
||||||
|
let sharp, meta;
|
||||||
|
try {
|
||||||
|
sharp = Sharp(data);
|
||||||
|
meta = await sharp.metadata();
|
||||||
|
} catch {
|
||||||
|
res.status(400);
|
||||||
|
return { success: false, error: "Image is corrupt" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!meta.width || !meta.height) {
|
||||||
|
res.status(400);
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputImg = await sharp.toBuffer();
|
||||||
|
|
||||||
|
// Add to the map
|
||||||
|
let id;
|
||||||
|
do {
|
||||||
|
id = await randomString(16);
|
||||||
|
} while (this.images.has(id));
|
||||||
|
|
||||||
|
let timeout = setTimeout(() => {
|
||||||
|
this.images.delete(id);
|
||||||
|
}, this.config.expirySeconds * 1000);
|
||||||
|
this.images.set(id, { img: outputImg, mime: mime.mime, timeout });
|
||||||
|
return { success: true, id };
|
||||||
|
}
|
||||||
|
|
||||||
|
has(id: string) {
|
||||||
|
return this.images.has(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import Fastify from 'fastify';
|
import Fastify from 'fastify';
|
||||||
import FastifyWS from '@fastify/websocket';
|
import FastifyWS from '@fastify/websocket';
|
||||||
import FastifyStatic from '@fastify/static';
|
import FastifyStatic from '@fastify/static';
|
||||||
|
import FastifyCors from '@fastify/cors';
|
||||||
import { Client } from './client.js';
|
import { Client } from './client.js';
|
||||||
import { MSAgentChatRoom } from './room.js';
|
import { MSAgentChatRoom } from './room.js';
|
||||||
import * as toml from 'toml';
|
import * as toml from 'toml';
|
||||||
|
@ -12,6 +13,7 @@ import { isIP } from 'net';
|
||||||
import { Database } from './database.js';
|
import { Database } from './database.js';
|
||||||
import { MSAgentErrorMessage, MSAgentProtocolMessageType } from '@msagent-chat/protocol';
|
import { MSAgentErrorMessage, MSAgentProtocolMessageType } from '@msagent-chat/protocol';
|
||||||
import { DiscordLogger } from './discord.js';
|
import { DiscordLogger } from './discord.js';
|
||||||
|
import { ImageUploader } from './imageuploader.js';
|
||||||
|
|
||||||
let config: IConfig;
|
let config: IConfig;
|
||||||
let configPath: string;
|
let configPath: string;
|
||||||
|
@ -35,7 +37,13 @@ let db = new Database(config.mysql);
|
||||||
await db.init();
|
await db.init();
|
||||||
|
|
||||||
const app = Fastify({
|
const app = Fastify({
|
||||||
logger: true
|
logger: true,
|
||||||
|
bodyLimit: 20971520
|
||||||
|
});
|
||||||
|
|
||||||
|
app.register(FastifyCors, {
|
||||||
|
origin: config.http.origins,
|
||||||
|
methods: ['GET', 'POST', 'PUT'],
|
||||||
});
|
});
|
||||||
|
|
||||||
app.register(FastifyWS);
|
app.register(FastifyWS);
|
||||||
|
@ -92,7 +100,10 @@ if (config.discord.enabled) {
|
||||||
discord = new DiscordLogger(config.discord);
|
discord = new DiscordLogger(config.discord);
|
||||||
}
|
}
|
||||||
|
|
||||||
let room = new MSAgentChatRoom(config.chat, config.agents, db, tts, discord);
|
// Image upload
|
||||||
|
let img = new ImageUploader(app, config.images);
|
||||||
|
|
||||||
|
let room = new MSAgentChatRoom(config.chat, config.agents, db, img, tts, discord);
|
||||||
|
|
||||||
app.register(async (app) => {
|
app.register(async (app) => {
|
||||||
app.get('/api/socket', { websocket: true }, async (socket, req) => {
|
app.get('/api/socket', { websocket: true }, async (socket, req) => {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
MSAgentAddUserMessage,
|
MSAgentAddUserMessage,
|
||||||
MSAgentChatMessage,
|
MSAgentChatMessage,
|
||||||
|
MSAgentImageMessage,
|
||||||
MSAgentInitMessage,
|
MSAgentInitMessage,
|
||||||
MSAgentPromoteMessage,
|
MSAgentPromoteMessage,
|
||||||
MSAgentProtocolMessage,
|
MSAgentProtocolMessage,
|
||||||
|
@ -13,6 +14,7 @@ import { AgentConfig, ChatConfig } from './config.js';
|
||||||
import * as htmlentities from 'html-entities';
|
import * as htmlentities from 'html-entities';
|
||||||
import { Database } from './database.js';
|
import { Database } from './database.js';
|
||||||
import { DiscordLogger } from './discord.js';
|
import { DiscordLogger } from './discord.js';
|
||||||
|
import { ImageUploader } from './imageuploader.js';
|
||||||
|
|
||||||
export class MSAgentChatRoom {
|
export class MSAgentChatRoom {
|
||||||
agents: AgentConfig[];
|
agents: AgentConfig[];
|
||||||
|
@ -21,14 +23,16 @@ export class MSAgentChatRoom {
|
||||||
msgId: number = 0;
|
msgId: number = 0;
|
||||||
config: ChatConfig;
|
config: ChatConfig;
|
||||||
db: Database;
|
db: Database;
|
||||||
|
img: ImageUploader;
|
||||||
discord: DiscordLogger | null;
|
discord: DiscordLogger | null;
|
||||||
|
|
||||||
constructor(config: ChatConfig, agents: AgentConfig[], db: Database, tts: TTSClient | null, discord: DiscordLogger | null) {
|
constructor(config: ChatConfig, agents: AgentConfig[], db: Database, img: ImageUploader, tts: TTSClient | null, discord: DiscordLogger | null) {
|
||||||
this.agents = agents;
|
this.agents = agents;
|
||||||
this.clients = [];
|
this.clients = [];
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.tts = tts;
|
this.tts = tts;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.img = img;
|
||||||
this.discord = discord;
|
this.discord = discord;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +100,21 @@ export class MSAgentChatRoom {
|
||||||
}
|
}
|
||||||
this.discord?.logMsg(client.username!, message);
|
this.discord?.logMsg(client.username!, message);
|
||||||
});
|
});
|
||||||
|
client.on('image', async (id) => {
|
||||||
|
if (!this.img.has(id)) return;
|
||||||
|
|
||||||
|
let msg: MSAgentImageMessage = {
|
||||||
|
op: MSAgentProtocolMessageType.SendImage,
|
||||||
|
data: {
|
||||||
|
username: client.username!,
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const _client of this.getActiveClients()) {
|
||||||
|
_client.send(msg);
|
||||||
|
}
|
||||||
|
})
|
||||||
client.on('admin', () => {
|
client.on('admin', () => {
|
||||||
let msg: MSAgentPromoteMessage = {
|
let msg: MSAgentPromoteMessage = {
|
||||||
op: MSAgentProtocolMessageType.Promote,
|
op: MSAgentProtocolMessageType.Promote,
|
||||||
|
|
|
@ -122,6 +122,7 @@
|
||||||
|
|
||||||
<div id="chatBar">
|
<div id="chatBar">
|
||||||
<input type="text" id="chatInput" placeholder="Send a message" />
|
<input type="text" id="chatInput" placeholder="Send a message" />
|
||||||
|
<button id="imageUploadBtn">Image</button>
|
||||||
<button id="chatSendBtn">Send</button>
|
<button id="chatSendBtn">Send</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,12 +11,14 @@ import {
|
||||||
MSAgentAdminOperation,
|
MSAgentAdminOperation,
|
||||||
MSAgentChatMessage,
|
MSAgentChatMessage,
|
||||||
MSAgentErrorMessage,
|
MSAgentErrorMessage,
|
||||||
|
MSAgentImageMessage,
|
||||||
MSAgentInitMessage,
|
MSAgentInitMessage,
|
||||||
MSAgentJoinMessage,
|
MSAgentJoinMessage,
|
||||||
MSAgentPromoteMessage,
|
MSAgentPromoteMessage,
|
||||||
MSAgentProtocolMessage,
|
MSAgentProtocolMessage,
|
||||||
MSAgentProtocolMessageType,
|
MSAgentProtocolMessageType,
|
||||||
MSAgentRemoveUserMessage,
|
MSAgentRemoveUserMessage,
|
||||||
|
MSAgentSendImageMessage,
|
||||||
MSAgentTalkMessage
|
MSAgentTalkMessage
|
||||||
} from '@msagent-chat/protocol';
|
} from '@msagent-chat/protocol';
|
||||||
import { User } from './user';
|
import { User } from './user';
|
||||||
|
@ -49,6 +51,7 @@ export class MSAgentClient {
|
||||||
private charlimit: number = 0;
|
private charlimit: number = 0;
|
||||||
private admin: boolean;
|
private admin: boolean;
|
||||||
private loginCb: (e: KeyboardEvent) => void;
|
private loginCb: (e: KeyboardEvent) => void;
|
||||||
|
private currentMsgId: number = 0;
|
||||||
|
|
||||||
private username: string | null = null;
|
private username: string | null = null;
|
||||||
private agentContainer: HTMLElement;
|
private agentContainer: HTMLElement;
|
||||||
|
@ -172,6 +175,31 @@ export class MSAgentClient {
|
||||||
this.send(talkMsg);
|
this.send(talkMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendImage(img: ArrayBuffer, type: string) {
|
||||||
|
// Upload image
|
||||||
|
let res = await fetch(this.url + '/api/upload', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: img,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': type
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let json = await res.json();
|
||||||
|
if (!json.success) {
|
||||||
|
throw new Error('Failed to upload image: ' + json.error);
|
||||||
|
}
|
||||||
|
let id = json.id as string;
|
||||||
|
|
||||||
|
// Send image
|
||||||
|
let msg: MSAgentSendImageMessage = {
|
||||||
|
op: MSAgentProtocolMessageType.SendImage,
|
||||||
|
data: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
getCharlimit() {
|
getCharlimit() {
|
||||||
return this.charlimit;
|
return this.charlimit;
|
||||||
}
|
}
|
||||||
|
@ -317,10 +345,12 @@ export class MSAgentClient {
|
||||||
|
|
||||||
this.playingAudio.set(user!.username, audio);
|
this.playingAudio.set(user!.username, audio);
|
||||||
|
|
||||||
|
let msgId = ++this.currentMsgId;
|
||||||
|
|
||||||
audio.addEventListener('ended', () => {
|
audio.addEventListener('ended', () => {
|
||||||
// give a bit of time before the wordballoon disappears
|
// give a bit of time before the wordballoon disappears
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.playingAudio.get(user!.username) === audio) {
|
if (this.currentMsgId === msgId) {
|
||||||
user!.agent.stopSpeaking();
|
user!.agent.stopSpeaking();
|
||||||
this.playingAudio.delete(user!.username);
|
this.playingAudio.delete(user!.username);
|
||||||
}
|
}
|
||||||
|
@ -332,6 +362,24 @@ export class MSAgentClient {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case MSAgentProtocolMessageType.SendImage: {
|
||||||
|
let imgMsg = msg as MSAgentImageMessage;
|
||||||
|
let user = this.users.find((u) => u.username === imgMsg.data.username);
|
||||||
|
if (!user || user.muted) return;
|
||||||
|
let img = new Image();
|
||||||
|
let msgId = ++this.currentMsgId;
|
||||||
|
img.addEventListener('load', () => {
|
||||||
|
this.playingAudio.get(user.username)?.pause();
|
||||||
|
user.agent.speakImage(img);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.currentMsgId === msgId) {
|
||||||
|
user.agent.stopSpeaking();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
img.src = `${this.url}/api/image/${imgMsg.data.id}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case MSAgentProtocolMessageType.Admin: {
|
case MSAgentProtocolMessageType.Admin: {
|
||||||
let adminMsg = msg as MSAgentAdminMessage;
|
let adminMsg = msg as MSAgentAdminMessage;
|
||||||
switch (adminMsg.data.action) {
|
switch (adminMsg.data.action) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ const elements = {
|
||||||
|
|
||||||
chatView: document.getElementById('chatView') as HTMLDivElement,
|
chatView: document.getElementById('chatView') as HTMLDivElement,
|
||||||
chatInput: document.getElementById('chatInput') as HTMLInputElement,
|
chatInput: document.getElementById('chatInput') as HTMLInputElement,
|
||||||
|
imageUploadBtn: document.getElementById('imageUploadBtn') as HTMLButtonElement,
|
||||||
chatSendBtn: document.getElementById('chatSendBtn') as HTMLButtonElement,
|
chatSendBtn: document.getElementById('chatSendBtn') as HTMLButtonElement,
|
||||||
|
|
||||||
roomSettingsWindow: document.getElementById('roomSettingsWindow') as HTMLDivElement
|
roomSettingsWindow: document.getElementById('roomSettingsWindow') as HTMLDivElement
|
||||||
|
@ -120,6 +121,24 @@ function talk() {
|
||||||
|
|
||||||
roomInit();
|
roomInit();
|
||||||
|
|
||||||
|
let imgUploadInput = document.createElement('input');
|
||||||
|
imgUploadInput.type = 'file';
|
||||||
|
imgUploadInput.accept = 'image/jpeg,image/png,image/gif,image/webp';
|
||||||
|
|
||||||
|
elements.imageUploadBtn.addEventListener('click', () => {
|
||||||
|
imgUploadInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
imgUploadInput.addEventListener('change', async () => {
|
||||||
|
if (!imgUploadInput.files || imgUploadInput.files.length === 0) return;
|
||||||
|
let file = imgUploadInput.files[0];
|
||||||
|
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;
|
let w = window as any;
|
||||||
|
|
||||||
|
|
338
yarn.lock
338
yarn.lock
|
@ -113,6 +113,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@emnapi/runtime@npm:^1.1.1":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "@emnapi/runtime@npm:1.2.0"
|
||||||
|
dependencies:
|
||||||
|
tslib: "npm:^2.4.0"
|
||||||
|
checksum: 10c0/7005ff8b67724c9e61b6cd79a3decbdb2ce25d24abd4d3d187472f200ee6e573329c30264335125fb136bd813aa9cf9f4f7c9391d04b07dd1e63ce0a3427be57
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@fastify/accept-negotiator@npm:^1.0.0":
|
"@fastify/accept-negotiator@npm:^1.0.0":
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
resolution: "@fastify/accept-negotiator@npm:1.1.0"
|
resolution: "@fastify/accept-negotiator@npm:1.1.0"
|
||||||
|
@ -131,6 +140,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@fastify/cors@npm:^9.0.1":
|
||||||
|
version: 9.0.1
|
||||||
|
resolution: "@fastify/cors@npm:9.0.1"
|
||||||
|
dependencies:
|
||||||
|
fastify-plugin: "npm:^4.0.0"
|
||||||
|
mnemonist: "npm:0.39.6"
|
||||||
|
checksum: 10c0/4db9d3d02edbca741c8ed053819bf3b235ecd70e07c640ed91ba0fc1ee2dc8abedbbffeb79ae1a38ccbf59832e414cad90a554ee44227d0811d5a2d062940611
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@fastify/error@npm:^3.3.0, @fastify/error@npm:^3.4.0":
|
"@fastify/error@npm:^3.3.0, @fastify/error@npm:^3.4.0":
|
||||||
version: 3.4.1
|
version: 3.4.1
|
||||||
resolution: "@fastify/error@npm:3.4.1"
|
resolution: "@fastify/error@npm:3.4.1"
|
||||||
|
@ -194,6 +213,181 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-darwin-arm64@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-darwin-arm64@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "npm:1.0.2"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-darwin-arm64":
|
||||||
|
optional: true
|
||||||
|
conditions: os=darwin & cpu=arm64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-darwin-x64@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-darwin-x64@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-darwin-x64": "npm:1.0.2"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-darwin-x64":
|
||||||
|
optional: true
|
||||||
|
conditions: os=darwin & cpu=x64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-darwin-arm64@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.2"
|
||||||
|
conditions: os=darwin & cpu=arm64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-darwin-x64@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.2"
|
||||||
|
conditions: os=darwin & cpu=x64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-arm64@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.2"
|
||||||
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-arm@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@img/sharp-libvips-linux-arm@npm:1.0.2"
|
||||||
|
conditions: os=linux & cpu=arm & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-s390x@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.2"
|
||||||
|
conditions: os=linux & cpu=s390x & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-x64@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@img/sharp-libvips-linux-x64@npm:1.0.2"
|
||||||
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2"
|
||||||
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.2"
|
||||||
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-linux-arm64@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-linux-arm64@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-linux-arm64": "npm:1.0.2"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-linux-arm64":
|
||||||
|
optional: true
|
||||||
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-linux-arm@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-linux-arm@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-linux-arm": "npm:1.0.2"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-linux-arm":
|
||||||
|
optional: true
|
||||||
|
conditions: os=linux & cpu=arm & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-linux-s390x@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-linux-s390x@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-linux-s390x": "npm:1.0.2"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-linux-s390x":
|
||||||
|
optional: true
|
||||||
|
conditions: os=linux & cpu=s390x & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-linux-x64@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-linux-x64@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-linux-x64": "npm:1.0.2"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-linux-x64":
|
||||||
|
optional: true
|
||||||
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-linuxmusl-arm64@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.2"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64":
|
||||||
|
optional: true
|
||||||
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-linuxmusl-x64@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-linuxmusl-x64@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.2"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64":
|
||||||
|
optional: true
|
||||||
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-wasm32@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-wasm32@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@emnapi/runtime": "npm:^1.1.1"
|
||||||
|
conditions: cpu=wasm32
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-win32-ia32@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-win32-ia32@npm:0.33.4"
|
||||||
|
conditions: os=win32 & cpu=ia32
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@img/sharp-win32-x64@npm:0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "@img/sharp-win32-x64@npm:0.33.4"
|
||||||
|
conditions: os=win32 & cpu=x64
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@isaacs/cliui@npm:^8.0.2":
|
"@isaacs/cliui@npm:^8.0.2":
|
||||||
version: 8.0.2
|
version: 8.0.2
|
||||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||||
|
@ -306,6 +500,7 @@ __metadata:
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@msagent-chat/server@workspace:server"
|
resolution: "@msagent-chat/server@workspace:server"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@fastify/cors": "npm:^9.0.1"
|
||||||
"@fastify/static": "npm:^7.0.4"
|
"@fastify/static": "npm:^7.0.4"
|
||||||
"@fastify/websocket": "npm:^10.0.1"
|
"@fastify/websocket": "npm:^10.0.1"
|
||||||
"@types/fluent-ffmpeg": "npm:^2.1.24"
|
"@types/fluent-ffmpeg": "npm:^2.1.24"
|
||||||
|
@ -313,9 +508,11 @@ __metadata:
|
||||||
"@types/ws": "npm:^8.5.10"
|
"@types/ws": "npm:^8.5.10"
|
||||||
discord.js: "npm:^14.15.3"
|
discord.js: "npm:^14.15.3"
|
||||||
fastify: "npm:^4.28.1"
|
fastify: "npm:^4.28.1"
|
||||||
|
file-type: "npm:^19.1.1"
|
||||||
fluent-ffmpeg: "npm:^2.1.3"
|
fluent-ffmpeg: "npm:^2.1.3"
|
||||||
html-entities: "npm:^2.5.2"
|
html-entities: "npm:^2.5.2"
|
||||||
mysql2: "npm:^3.10.2"
|
mysql2: "npm:^3.10.2"
|
||||||
|
sharp: "npm:^0.33.4"
|
||||||
toml: "npm:^3.0.0"
|
toml: "npm:^3.0.0"
|
||||||
typescript: "npm:5.4.5"
|
typescript: "npm:5.4.5"
|
||||||
ws: "npm:^8.17.1"
|
ws: "npm:^8.17.1"
|
||||||
|
@ -1453,6 +1650,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tokenizer/token@npm:^0.3.0":
|
||||||
|
version: 0.3.0
|
||||||
|
resolution: "@tokenizer/token@npm:0.3.0"
|
||||||
|
checksum: 10c0/7ab9a822d4b5ff3f5bca7f7d14d46bdd8432528e028db4a52be7fbf90c7f495cc1af1324691dda2813c6af8dc4b8eb29de3107d4508165f9aa5b53e7d501f155
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@trysound/sax@npm:0.2.0":
|
"@trysound/sax@npm:0.2.0":
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
resolution: "@trysound/sax@npm:0.2.0"
|
resolution: "@trysound/sax@npm:0.2.0"
|
||||||
|
@ -2065,7 +2269,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1":
|
"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.3":
|
||||||
version: 2.0.3
|
version: 2.0.3
|
||||||
resolution: "detect-libc@npm:2.0.3"
|
resolution: "detect-libc@npm:2.0.3"
|
||||||
checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7
|
checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7
|
||||||
|
@ -2401,6 +2605,17 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"file-type@npm:^19.1.1":
|
||||||
|
version: 19.1.1
|
||||||
|
resolution: "file-type@npm:19.1.1"
|
||||||
|
dependencies:
|
||||||
|
strtok3: "npm:^7.1.0"
|
||||||
|
token-types: "npm:^6.0.0"
|
||||||
|
uint8array-extras: "npm:^1.3.0"
|
||||||
|
checksum: 10c0/621f6f5ac9f2c0fad1c5ac0a77d715953fbe7e5441e595443857b7aede0ae8dfb80e29b4f1d7000c6bca49a6e1d5c711c5265fd3d6f81c40361fc61a0ba415c4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fill-range@npm:^7.1.1":
|
"fill-range@npm:^7.1.1":
|
||||||
version: 7.1.1
|
version: 7.1.1
|
||||||
resolution: "fill-range@npm:7.1.1"
|
resolution: "fill-range@npm:7.1.1"
|
||||||
|
@ -3273,6 +3488,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"mnemonist@npm:0.39.6":
|
||||||
|
version: 0.39.6
|
||||||
|
resolution: "mnemonist@npm:0.39.6"
|
||||||
|
dependencies:
|
||||||
|
obliterator: "npm:^2.0.1"
|
||||||
|
checksum: 10c0/a538945ea547976136ee6e16f224c0a50983143619941f6c4d2c82159e36eb6f8ee93d69d3a1267038fc5b16f88e2d43390023de10dfb145fa15c5e2befa1cdf
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ms@npm:2.1.2":
|
"ms@npm:2.1.2":
|
||||||
version: 2.1.2
|
version: 2.1.2
|
||||||
resolution: "ms@npm:2.1.2"
|
resolution: "ms@npm:2.1.2"
|
||||||
|
@ -3504,6 +3728,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"obliterator@npm:^2.0.1":
|
||||||
|
version: 2.0.4
|
||||||
|
resolution: "obliterator@npm:2.0.4"
|
||||||
|
checksum: 10c0/ff2c10d4de7d62cd1d588b4d18dfc42f246c9e3a259f60d5716f7f88e5b3a3f79856b3207db96ec9a836a01d0958a21c15afa62a3f4e73a1e0b75f2c2f6bab40
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"on-exit-leak-free@npm:^2.1.0":
|
"on-exit-leak-free@npm:^2.1.0":
|
||||||
version: 2.1.2
|
version: 2.1.2
|
||||||
resolution: "on-exit-leak-free@npm:2.1.2"
|
resolution: "on-exit-leak-free@npm:2.1.2"
|
||||||
|
@ -3605,6 +3836,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"peek-readable@npm:^5.1.1":
|
||||||
|
version: 5.1.2
|
||||||
|
resolution: "peek-readable@npm:5.1.2"
|
||||||
|
checksum: 10c0/015d7f50c17f07efde09a1507d4be67ff01d5819533bbfdbb79232f280aca81ea34053988f49ce9f0e77876487182bbd23ce9178aabe58515f06a91df0b028f0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1":
|
"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "picocolors@npm:1.0.1"
|
resolution: "picocolors@npm:1.0.1"
|
||||||
|
@ -3967,7 +4205,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.4":
|
"semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.4, semver@npm:^7.6.0":
|
||||||
version: 7.6.2
|
version: 7.6.2
|
||||||
resolution: "semver@npm:7.6.2"
|
resolution: "semver@npm:7.6.2"
|
||||||
bin:
|
bin:
|
||||||
|
@ -4014,6 +4252,75 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"sharp@npm:^0.33.4":
|
||||||
|
version: 0.33.4
|
||||||
|
resolution: "sharp@npm:0.33.4"
|
||||||
|
dependencies:
|
||||||
|
"@img/sharp-darwin-arm64": "npm:0.33.4"
|
||||||
|
"@img/sharp-darwin-x64": "npm:0.33.4"
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "npm:1.0.2"
|
||||||
|
"@img/sharp-libvips-darwin-x64": "npm:1.0.2"
|
||||||
|
"@img/sharp-libvips-linux-arm": "npm:1.0.2"
|
||||||
|
"@img/sharp-libvips-linux-arm64": "npm:1.0.2"
|
||||||
|
"@img/sharp-libvips-linux-s390x": "npm:1.0.2"
|
||||||
|
"@img/sharp-libvips-linux-x64": "npm:1.0.2"
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.2"
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.2"
|
||||||
|
"@img/sharp-linux-arm": "npm:0.33.4"
|
||||||
|
"@img/sharp-linux-arm64": "npm:0.33.4"
|
||||||
|
"@img/sharp-linux-s390x": "npm:0.33.4"
|
||||||
|
"@img/sharp-linux-x64": "npm:0.33.4"
|
||||||
|
"@img/sharp-linuxmusl-arm64": "npm:0.33.4"
|
||||||
|
"@img/sharp-linuxmusl-x64": "npm:0.33.4"
|
||||||
|
"@img/sharp-wasm32": "npm:0.33.4"
|
||||||
|
"@img/sharp-win32-ia32": "npm:0.33.4"
|
||||||
|
"@img/sharp-win32-x64": "npm:0.33.4"
|
||||||
|
color: "npm:^4.2.3"
|
||||||
|
detect-libc: "npm:^2.0.3"
|
||||||
|
semver: "npm:^7.6.0"
|
||||||
|
dependenciesMeta:
|
||||||
|
"@img/sharp-darwin-arm64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-darwin-x64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-libvips-darwin-arm64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-libvips-darwin-x64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-libvips-linux-arm":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-libvips-linux-arm64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-libvips-linux-s390x":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-libvips-linux-x64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-linux-arm":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-linux-arm64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-linux-s390x":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-linux-x64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-linuxmusl-arm64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-linuxmusl-x64":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-wasm32":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-win32-ia32":
|
||||||
|
optional: true
|
||||||
|
"@img/sharp-win32-x64":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/428c5c6a84ff8968effe50c2de931002f5f30b9f263e1c026d0384e581673c13088a49322f7748114d3d9be4ae9476a74bf003a3af34743e97ef2f880d1cfe45
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"shebang-command@npm:^2.0.0":
|
"shebang-command@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "shebang-command@npm:2.0.0"
|
resolution: "shebang-command@npm:2.0.0"
|
||||||
|
@ -4229,6 +4536,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"strtok3@npm:^7.1.0":
|
||||||
|
version: 7.1.0
|
||||||
|
resolution: "strtok3@npm:7.1.0"
|
||||||
|
dependencies:
|
||||||
|
"@tokenizer/token": "npm:^0.3.0"
|
||||||
|
peek-readable: "npm:^5.1.1"
|
||||||
|
checksum: 10c0/7a67ba59d348c2e8afe5d9b6de03e8c7527bb348e2d6b7abd3b7277839d9a36c9f1c0e49b89f3b5b32e11865f2c078bf3d14c567e962c1bf458df4ac203216ca
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"supports-color@npm:^5.3.0":
|
"supports-color@npm:^5.3.0":
|
||||||
version: 5.5.0
|
version: 5.5.0
|
||||||
resolution: "supports-color@npm:5.5.0"
|
resolution: "supports-color@npm:5.5.0"
|
||||||
|
@ -4349,6 +4666,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"token-types@npm:^6.0.0":
|
||||||
|
version: 6.0.0
|
||||||
|
resolution: "token-types@npm:6.0.0"
|
||||||
|
dependencies:
|
||||||
|
"@tokenizer/token": "npm:^0.3.0"
|
||||||
|
ieee754: "npm:^1.2.1"
|
||||||
|
checksum: 10c0/5bf5eba51d63f71f301659ff70ce10ca43e7038364883437d8b4541cc98377e3e56109b11720e25fe51047014efaccdff90eaf6de9a78270483578814b838ab9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"toml@npm:^3.0.0":
|
"toml@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "toml@npm:3.0.0"
|
resolution: "toml@npm:3.0.0"
|
||||||
|
@ -4433,6 +4760,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"uint8array-extras@npm:^1.3.0":
|
||||||
|
version: 1.3.0
|
||||||
|
resolution: "uint8array-extras@npm:1.3.0"
|
||||||
|
checksum: 10c0/facc6eedc38f9db4879c3d60ab5c8d89c7be7b9487f1c812cdfa46194517b104de844a2128875cbdc9af3d4f52ac94c816ed6bb825f5184f270cfef9269fdd82
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"undici-types@npm:~5.26.4":
|
"undici-types@npm:~5.26.4":
|
||||||
version: 5.26.5
|
version: 5.26.5
|
||||||
resolution: "undici-types@npm:5.26.5"
|
resolution: "undici-types@npm:5.26.5"
|
||||||
|
|
Loading…
Reference in a new issue