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 { AcsImageEntry } from './structs/image.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
|
||||
function dwAlign(off: number): number {
|
||||
|
@ -61,44 +61,19 @@ enum AgentWordBalloonPosition {
|
|||
BelowCentered
|
||||
}
|
||||
|
||||
class AgentWordBalloonState {
|
||||
char: Agent;
|
||||
text: string;
|
||||
hasTip: boolean;
|
||||
position: AgentWordBalloonPosition;
|
||||
abstract class AgentWordBalloonState {
|
||||
abstract char: Agent;
|
||||
abstract hasTip: boolean;
|
||||
abstract position: AgentWordBalloonPosition;
|
||||
|
||||
balloonCanvas: HTMLCanvasElement;
|
||||
balloonCanvasCtx: CanvasRenderingContext2D;
|
||||
|
||||
constructor(char: Agent, text: string, hasTip: boolean, position: AgentWordBalloonPosition, textColor: string) {
|
||||
this.char = char;
|
||||
this.text = text;
|
||||
this.hasTip = hasTip;
|
||||
this.position = position;
|
||||
constructor() {
|
||||
this.balloonCanvas = document.createElement('canvas');
|
||||
this.balloonCanvasCtx = this.balloonCanvas.getContext('2d')!;
|
||||
|
||||
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';
|
||||
|
||||
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() {
|
||||
|
@ -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 {
|
||||
private data: AcsData;
|
||||
private charDiv: HTMLDivElement;
|
||||
|
@ -141,7 +176,7 @@ export class Agent {
|
|||
|
||||
private animState: AgentAnimationState | null = null;
|
||||
private wordballoonState: AgentWordBalloonState | null = null;
|
||||
private usernameBalloonState: AgentWordBalloonState | null = null;
|
||||
private usernameBalloonState: AgentTextWordBalloonState | null = null;
|
||||
|
||||
private contextMenu: ContextMenu;
|
||||
|
||||
|
@ -299,7 +334,7 @@ export class Agent {
|
|||
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();
|
||||
}
|
||||
|
||||
|
@ -308,7 +343,17 @@ export class Agent {
|
|||
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.show();
|
||||
}
|
||||
|
|
|
@ -177,3 +177,24 @@ export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, te
|
|||
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',
|
||||
Join = 'join',
|
||||
Talk = 'talk',
|
||||
SendImage = 'img',
|
||||
Admin = 'admin',
|
||||
// Server-to-client
|
||||
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
|
||||
|
||||
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 {
|
||||
op: MSAgentProtocolMessageType.Promote;
|
||||
data: {
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
host = "127.0.0.1"
|
||||
port = 3000
|
||||
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]
|
||||
host = "127.0.0.1"
|
||||
|
@ -46,6 +51,10 @@ tempDir = "/tmp/msac-tts"
|
|||
transcodeOpus = true
|
||||
wavExpirySeconds = 60
|
||||
|
||||
[images]
|
||||
maxSize = { width = 300, height = 300 }
|
||||
expirySeconds = 60
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Clippy"
|
||||
filename = "CLIPPIT.ACS"
|
||||
|
|
|
@ -12,13 +12,16 @@
|
|||
"typescript": "5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^9.0.1",
|
||||
"@fastify/static": "^7.0.4",
|
||||
"@fastify/websocket": "^10.0.1",
|
||||
"discord.js": "^14.15.3",
|
||||
"fastify": "^4.28.1",
|
||||
"file-type": "^19.1.1",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"html-entities": "^2.5.2",
|
||||
"mysql2": "^3.10.2",
|
||||
"sharp": "^0.33.4",
|
||||
"toml": "^3.0.0",
|
||||
"ws": "^8.17.1"
|
||||
},
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
MSAgentAdminMessage,
|
||||
MSAgentAdminOperation,
|
||||
MSAgentErrorMessage,
|
||||
MSAgentSendImageMessage,
|
||||
MSAgentJoinMessage,
|
||||
MSAgentProtocolMessage,
|
||||
MSAgentProtocolMessageType,
|
||||
|
@ -26,6 +27,7 @@ export interface Client {
|
|||
on(event: 'join', listener: () => void): this;
|
||||
on(event: 'close', listener: () => void): this;
|
||||
on(event: 'talk', listener: (msg: string) => void): this;
|
||||
on(event: 'image', listener: (id: string) => void): this;
|
||||
|
||||
on(event: string, listener: Function): this;
|
||||
}
|
||||
|
@ -160,6 +162,12 @@ export class Client extends EventEmitter {
|
|||
this.emit('talk', talkMsg.data.msg);
|
||||
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: {
|
||||
let adminMsg = msg as MSAgentAdminMessage;
|
||||
if (!adminMsg.data) return;
|
||||
|
|
|
@ -3,12 +3,14 @@ export interface IConfig {
|
|||
host: string;
|
||||
port: number;
|
||||
proxied: boolean;
|
||||
origins: string | string[] | boolean;
|
||||
};
|
||||
mysql: MySQLConfig;
|
||||
chat: ChatConfig;
|
||||
motd: motdConfig;
|
||||
discord: DiscordConfig;
|
||||
tts: TTSConfig;
|
||||
images: ImagesConfig;
|
||||
agents: AgentConfig[];
|
||||
}
|
||||
|
||||
|
@ -58,3 +60,8 @@ export interface DiscordConfig {
|
|||
enabled: boolean;
|
||||
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 FastifyWS from '@fastify/websocket';
|
||||
import FastifyStatic from '@fastify/static';
|
||||
import FastifyCors from '@fastify/cors';
|
||||
import { Client } from './client.js';
|
||||
import { MSAgentChatRoom } from './room.js';
|
||||
import * as toml from 'toml';
|
||||
|
@ -12,6 +13,7 @@ import { isIP } from 'net';
|
|||
import { Database } from './database.js';
|
||||
import { MSAgentErrorMessage, MSAgentProtocolMessageType } from '@msagent-chat/protocol';
|
||||
import { DiscordLogger } from './discord.js';
|
||||
import { ImageUploader } from './imageuploader.js';
|
||||
|
||||
let config: IConfig;
|
||||
let configPath: string;
|
||||
|
@ -35,7 +37,13 @@ let db = new Database(config.mysql);
|
|||
await db.init();
|
||||
|
||||
const app = Fastify({
|
||||
logger: true
|
||||
logger: true,
|
||||
bodyLimit: 20971520
|
||||
});
|
||||
|
||||
app.register(FastifyCors, {
|
||||
origin: config.http.origins,
|
||||
methods: ['GET', 'POST', 'PUT'],
|
||||
});
|
||||
|
||||
app.register(FastifyWS);
|
||||
|
@ -92,7 +100,10 @@ if (config.discord.enabled) {
|
|||
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.get('/api/socket', { websocket: true }, async (socket, req) => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
MSAgentAddUserMessage,
|
||||
MSAgentChatMessage,
|
||||
MSAgentImageMessage,
|
||||
MSAgentInitMessage,
|
||||
MSAgentPromoteMessage,
|
||||
MSAgentProtocolMessage,
|
||||
|
@ -13,6 +14,7 @@ import { AgentConfig, ChatConfig } from './config.js';
|
|||
import * as htmlentities from 'html-entities';
|
||||
import { Database } from './database.js';
|
||||
import { DiscordLogger } from './discord.js';
|
||||
import { ImageUploader } from './imageuploader.js';
|
||||
|
||||
export class MSAgentChatRoom {
|
||||
agents: AgentConfig[];
|
||||
|
@ -21,14 +23,16 @@ export class MSAgentChatRoom {
|
|||
msgId: number = 0;
|
||||
config: ChatConfig;
|
||||
db: Database;
|
||||
img: ImageUploader;
|
||||
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.clients = [];
|
||||
this.config = config;
|
||||
this.tts = tts;
|
||||
this.db = db;
|
||||
this.img = img;
|
||||
this.discord = discord;
|
||||
}
|
||||
|
||||
|
@ -96,6 +100,21 @@ export class MSAgentChatRoom {
|
|||
}
|
||||
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', () => {
|
||||
let msg: MSAgentPromoteMessage = {
|
||||
op: MSAgentProtocolMessageType.Promote,
|
||||
|
|
|
@ -122,6 +122,7 @@
|
|||
|
||||
<div id="chatBar">
|
||||
<input type="text" id="chatInput" placeholder="Send a message" />
|
||||
<button id="imageUploadBtn">Image</button>
|
||||
<button id="chatSendBtn">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,12 +11,14 @@ import {
|
|||
MSAgentAdminOperation,
|
||||
MSAgentChatMessage,
|
||||
MSAgentErrorMessage,
|
||||
MSAgentImageMessage,
|
||||
MSAgentInitMessage,
|
||||
MSAgentJoinMessage,
|
||||
MSAgentPromoteMessage,
|
||||
MSAgentProtocolMessage,
|
||||
MSAgentProtocolMessageType,
|
||||
MSAgentRemoveUserMessage,
|
||||
MSAgentSendImageMessage,
|
||||
MSAgentTalkMessage
|
||||
} from '@msagent-chat/protocol';
|
||||
import { User } from './user';
|
||||
|
@ -49,6 +51,7 @@ export class MSAgentClient {
|
|||
private charlimit: number = 0;
|
||||
private admin: boolean;
|
||||
private loginCb: (e: KeyboardEvent) => void;
|
||||
private currentMsgId: number = 0;
|
||||
|
||||
private username: string | null = null;
|
||||
private agentContainer: HTMLElement;
|
||||
|
@ -172,6 +175,31 @@ export class MSAgentClient {
|
|||
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() {
|
||||
return this.charlimit;
|
||||
}
|
||||
|
@ -317,10 +345,12 @@ export class MSAgentClient {
|
|||
|
||||
this.playingAudio.set(user!.username, audio);
|
||||
|
||||
let msgId = ++this.currentMsgId;
|
||||
|
||||
audio.addEventListener('ended', () => {
|
||||
// give a bit of time before the wordballoon disappears
|
||||
setTimeout(() => {
|
||||
if (this.playingAudio.get(user!.username) === audio) {
|
||||
if (this.currentMsgId === msgId) {
|
||||
user!.agent.stopSpeaking();
|
||||
this.playingAudio.delete(user!.username);
|
||||
}
|
||||
|
@ -332,6 +362,24 @@ export class MSAgentClient {
|
|||
}
|
||||
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: {
|
||||
let adminMsg = msg as MSAgentAdminMessage;
|
||||
switch (adminMsg.data.action) {
|
||||
|
|
|
@ -17,6 +17,7 @@ const elements = {
|
|||
|
||||
chatView: document.getElementById('chatView') as HTMLDivElement,
|
||||
chatInput: document.getElementById('chatInput') as HTMLInputElement,
|
||||
imageUploadBtn: document.getElementById('imageUploadBtn') as HTMLButtonElement,
|
||||
chatSendBtn: document.getElementById('chatSendBtn') as HTMLButtonElement,
|
||||
|
||||
roomSettingsWindow: document.getElementById('roomSettingsWindow') as HTMLDivElement
|
||||
|
@ -120,6 +121,24 @@ function talk() {
|
|||
|
||||
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;
|
||||
|
||||
|
|
338
yarn.lock
338
yarn.lock
|
@ -113,6 +113,15 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 1.1.0
|
||||
resolution: "@fastify/accept-negotiator@npm:1.1.0"
|
||||
|
@ -131,6 +140,16 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 3.4.1
|
||||
resolution: "@fastify/error@npm:3.4.1"
|
||||
|
@ -194,6 +213,181 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 8.0.2
|
||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||
|
@ -306,6 +500,7 @@ __metadata:
|
|||
version: 0.0.0-use.local
|
||||
resolution: "@msagent-chat/server@workspace:server"
|
||||
dependencies:
|
||||
"@fastify/cors": "npm:^9.0.1"
|
||||
"@fastify/static": "npm:^7.0.4"
|
||||
"@fastify/websocket": "npm:^10.0.1"
|
||||
"@types/fluent-ffmpeg": "npm:^2.1.24"
|
||||
|
@ -313,9 +508,11 @@ __metadata:
|
|||
"@types/ws": "npm:^8.5.10"
|
||||
discord.js: "npm:^14.15.3"
|
||||
fastify: "npm:^4.28.1"
|
||||
file-type: "npm:^19.1.1"
|
||||
fluent-ffmpeg: "npm:^2.1.3"
|
||||
html-entities: "npm:^2.5.2"
|
||||
mysql2: "npm:^3.10.2"
|
||||
sharp: "npm:^0.33.4"
|
||||
toml: "npm:^3.0.0"
|
||||
typescript: "npm:5.4.5"
|
||||
ws: "npm:^8.17.1"
|
||||
|
@ -1453,6 +1650,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 0.2.0
|
||||
resolution: "@trysound/sax@npm:0.2.0"
|
||||
|
@ -2065,7 +2269,7 @@ __metadata:
|
|||
languageName: node
|
||||
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
|
||||
resolution: "detect-libc@npm:2.0.3"
|
||||
checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7
|
||||
|
@ -2401,6 +2605,17 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 7.1.1
|
||||
resolution: "fill-range@npm:7.1.1"
|
||||
|
@ -3273,6 +3488,15 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 2.1.2
|
||||
resolution: "ms@npm:2.1.2"
|
||||
|
@ -3504,6 +3728,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 2.1.2
|
||||
resolution: "on-exit-leak-free@npm:2.1.2"
|
||||
|
@ -3605,6 +3836,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 1.0.1
|
||||
resolution: "picocolors@npm:1.0.1"
|
||||
|
@ -3967,7 +4205,7 @@ __metadata:
|
|||
languageName: node
|
||||
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
|
||||
resolution: "semver@npm:7.6.2"
|
||||
bin:
|
||||
|
@ -4014,6 +4252,75 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 2.0.0
|
||||
resolution: "shebang-command@npm:2.0.0"
|
||||
|
@ -4229,6 +4536,16 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 5.5.0
|
||||
resolution: "supports-color@npm:5.5.0"
|
||||
|
@ -4349,6 +4666,16 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 3.0.0
|
||||
resolution: "toml@npm:3.0.0"
|
||||
|
@ -4433,6 +4760,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 5.26.5
|
||||
resolution: "undici-types@npm:5.26.5"
|
||||
|
|
Loading…
Reference in a new issue