diff --git a/src/app/channel/activeChannel.js b/src/app/channel/activeChannel.js index 5e9ebd7..1e62f42 100644 --- a/src/app/channel/activeChannel.js +++ b/src/app/channel/activeChannel.js @@ -16,6 +16,7 @@ along with this program. If not, see .*/ //local imports const connectedUser = require('./connectedUser'); +const chatBuffer = require('./chatBuffer'); const queue = require('./media/queue'); const channelModel = require('../../schemas/channel/channelSchema'); const playlistHandler = require('./media/playlistHandler') @@ -30,7 +31,7 @@ module.exports = class{ this.queue = new queue(server, chanDB, this); this.playlistHandler = new playlistHandler(server, chanDB, this); //Define the chat buffer - this.chatBuffer = []; + this.chatBuffer = new chatBuffer(server, chanDB, this); } async handleConnection(userDB, chanDB, socket){ diff --git a/src/app/channel/chatBuffer.js b/src/app/channel/chatBuffer.js new file mode 100644 index 0000000..f51745e --- /dev/null +++ b/src/app/channel/chatBuffer.js @@ -0,0 +1,109 @@ +/*Canopy - The next generation of stoner streaming software +Copyright (C) 2024-2025 Rainbownapkin and the TTN Community + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see .*/ +const config = require('../../../config.json'); +const channelModel = require('../../schemas/channel/channelSchema'); + +class chatBuffer{ + constructor(server, chanDB, channel){ + //Grab parent server and chan objects + this.server = server; + this.channel = channel; + + //If we have no chanDB.chatBuffer + if(chanDB == null || chanDB.chatBuffer == null){ + //Create RAM-based buffer array + this.buffer = []; + //Otherwise + }else{ + //Pull buffer from DB + this.buffer = chanDB.chatBuffer; + } + + //Create variables to hold timers for deciding when to write RAM buffer to DB + //Goes off 'this.inactivityDelay' seconds after the last chat was sent, assuming it isn't interrupted by new chats + this.inactivityTimer = null; + this.inactivityDelay = 10; + //Goes off 'this.busyDelay' minutes after the first chat message in the current volley of messages. Get's cancelled before being called if this.inactivityTimer goes off. + this.busyTimer = null; + this.busyDelay = 5; + } + + push(chat){ + //push chat into RAM buffer + this.buffer.push(chat); + + //clear existing inactivity timer + clearTimeout(this.inactivityTimer); + + //reset inactivity timer + this.inactivityTimer = setTimeout(this.handleInactivity.bind(this), 1000 * this.inactivityDelay); + + //If busy timer is unset + if(this.busyTimer == null){ + this.busyTimer = setTimeout(this.handleBusyRoom.bind(this), 1000 * 60 * this.busyDelay); + } + } + + shift(){ + //remove chat from RAM buffer, no need to handle DB timing here, since push should be called when this is called. + this.buffer.shift(); + } + + //Called after 10 seconds of chat room inactivity + handleInactivity(){ + this.saveDB(`${this.inactivityDelay} seconds of inactivity.`); + } + + //Called after 5 minutes of solid activity + handleBusyRoom(){ + this.saveDB(`${this.busyDelay} minutes of activity.`); + } + + async saveDB(reason, chanDB){ + //clear existing timers + clearTimeout(this.inactivityTimer); + clearTimeout(this.busyTimer); + this.inactivityTimer = null; + this.busyTimer = null; + + //if the server is in screamy boi mode + if(config.verbose){ + //This should eventually be replaced by a per-channel logging feature that provides access to chan admins via web front-end + console.log(`Saving chat buffer to channel ${this.channel.name} after ${reason}.`); + } + + + //If we wheren't handed a channel + if(chanDB == null){ + //Now that everything is clean, we can take our time with the DB :P + chanDB = await channelModel.findOne({name:this.channel.name}); + } + + //If we couldn't find the channel + if(chanDB == null){ + //FUCK + throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while saving chat buffer!`, "chat"); + } + + //Set chan doc buffer to RAM buffer + chanDB.chatBuffer = this.buffer; + + //save chan doc to DB. + await chanDB.save(); + } +} + +module.exports = chatBuffer; \ No newline at end of file diff --git a/src/app/channel/chatHandler.js b/src/app/channel/chatHandler.js index 26dea9d..17266e1 100644 --- a/src/app/channel/chatHandler.js +++ b/src/app/channel/chatHandler.js @@ -141,7 +141,7 @@ module.exports = class{ const channel = this.server.activeChannels.get(chan); //If chat buffer length is over mandated size - if(channel.chatBuffer.length >= this.chatBufferSize){ + if(channel.chatBuffer.buffer.length >= this.chatBufferSize){ //Take out oldest chat channel.chatBuffer.shift(); } diff --git a/src/app/channel/connectedUser.js b/src/app/channel/connectedUser.js index f7c3176..659fb69 100644 --- a/src/app/channel/connectedUser.js +++ b/src/app/channel/connectedUser.js @@ -160,7 +160,7 @@ module.exports = class{ const queueLock = this.channel.queue.locked; //Get chat buffer - const chatBuffer = this.channel.chatBuffer; + const chatBuffer = this.channel.chatBuffer.buffer; //Send off the metadata to our user's clients this.emit("clientMetadata", {user: userObj, flairList, queue, queueLock, chatBuffer}); diff --git a/src/schemas/channel/chatSchema.js b/src/schemas/channel/chatSchema.js index b54f9b6..e5ac423 100644 --- a/src/schemas/channel/chatSchema.js +++ b/src/schemas/channel/chatSchema.js @@ -17,6 +17,11 @@ along with this program. If not, see .*/ //NPM Imports const {mongoose} = require('mongoose'); +const linkSchema = new mongoose.Schema({ + link: mongoose.SchemaTypes.String, + type: mongoose.SchemaTypes.String +}); + const chatSchema = new mongoose.Schema({ user: { type: mongoose.SchemaTypes.String, @@ -39,10 +44,9 @@ const chatSchema = new mongoose.Schema({ required: true, }, links: { - type: [mongoose.SchemaTypes.Number], + type: [linkSchema], required: true, }, }); - module.exports = chatSchema; \ No newline at end of file diff --git a/src/utils/loggerUtils.js b/src/utils/loggerUtils.js index 6265dac..b6daae9 100644 --- a/src/utils/loggerUtils.js +++ b/src/utils/loggerUtils.js @@ -84,6 +84,9 @@ module.exports.socketCriticalExceptionHandler = function(socket, err){ //yell at the browser for fucking up, and tell it what it did wrong. socket.emit("kick", {type: "Disconnected", reason: `Server Error: ${err.message}`}); }else{ + //Locally handle the exception + module.exports.localExceptionHandler(err); + //yell at the browser for fucking up socket.emit("kick", {type: "Disconnected", reason: "An unexpected server crash was just prevented. You should probably report this to an admin."}); } diff --git a/www/js/channel/chat.js b/www/js/channel/chat.js index 5ba1b3b..a6cfa84 100644 --- a/www/js/channel/chat.js +++ b/www/js/channel/chat.js @@ -145,6 +145,8 @@ class chatBox{ chatBody.classList.add("chat-panel-buffer","chat-entry-body"); chatEntry.appendChild(chatBody); + console.log(data); + //Append the post-processed chat-body to the chat buffer this.chatBuffer.appendChild(this.chatPostprocessor.postprocess(chatEntry, data));