Per-Channel Emotes Implemented.

This commit is contained in:
rainbow napkin 2024-12-21 11:01:00 -05:00
parent 163cecf9f0
commit c3d016e1af
14 changed files with 483 additions and 20 deletions

View file

@ -56,6 +56,7 @@ module.exports = class{
//await this.sendClientMetadata(userDB, socket);
await userObj.sendClientMetadata();
await userObj.sendSiteEmotes();
await userObj.sendChanEmotes();
//Send out the userlist
this.broadcastUserList(socket.chan);
@ -101,4 +102,18 @@ module.exports = class{
this.server.io.in(this.name).emit("userList", userList);
}
async broadcastChanEmotes(chanDB){
//if we wherent handed a channel document
if(chanDB == null){
//Pull it based on channel name
chanDB = await channelModel.findOne({name: this.name});
}
//Get emote list from channel document
const emoteList = chanDB.getEmotes();
//Broadcast that sumbitch
this.server.io.in(this.name).emit('chanEmotes', emoteList);
}
}

View file

@ -15,9 +15,10 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//local imports
const channelModel = require('../../schemas/channel/channelSchema');
const permissionModel = require('../../schemas/permissionSchema');
const flairModel = require('../../schemas/flairSchema');
const emoteModel = require('../../schemas/emoteSchema');
const permissionModel = require('../../schemas/permissionSchema');
module.exports = class{
constructor(userDB, chanRank, channel, socket){
@ -96,6 +97,20 @@ module.exports = class{
this.emit('siteEmotes', emoteList);
}
async sendChanEmotes(chanDB){
//if we wherent handed a channel document
if(chanDB == null){
//Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
//Pull emotes from channel
const emoteList = chanDB.getEmotes();
//Send it off to the user
this.emit('chanEmotes', emoteList);
}
updateFlair(flair){
this.flair = flair;

View file

@ -0,0 +1,167 @@
/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.*/
//npm imports
const {validationResult, matchedData} = require('express-validator');
//local imports
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
const channelModel = require('../../../schemas/channel/channelSchema');
const linkUtils = require('../../../utils/linkUtils');
module.exports.get = async function(req, res){
try{
//get validation error results
const validResult = validationResult(req);
//if they're empty
if(validResult.isEmpty()){
//Pull sanatized/validated input
const data = matchedData(req);
//Get the requested channel
const chanDB = await channelModel.findOne({name: data.chanName});
//If the requested channel doesn't exist
if(chanDB == null){
//ACK
return errorHandler(res, "Channel not found.", "Bad Query");
}
//Return found channel's emotes
res.status(200);
return res.send(chanDB.getEmotes());
}else{
//otherwise scream
res.status(400);
return res.send({errors: validResult.array()})
}
}catch(err){
return exceptionHandler(res, err);
}
}
module.exports.post = async function(req, res){
try{
//get validation error results
const validResult = validationResult(req);
//if they're empty
if(validResult.isEmpty()){
//get matched data
const {chanName, emoteName, link} = matchedData(req);
//query for requested channel
const chanDB = await channelModel.findOne({name: chanName});
//If the requested channel doesn't exist
if(chanDB == null){
//ACK
return errorHandler(res, "Channel not found.", "Bad Query");
}
//Filter emote list for...
const foundEmotes = chanDB.emotes.filter((emote) => {
//emotes with a name that matches emoteName
return emote.name == emoteName;
});
//If we found one
if(foundEmotes.length > 0){
//ACK
return errorHandler(res, "Emote already exists!");
}
//Initialize the emote object from linkUtils
var emoteObj = await linkUtils.markLink(link);
//If we didn't get a valid image/video file
if(emoteObj.type != 'image' && emoteObj.type != 'video'){
//AAAAAAAAAAAAAAAAAA
return errorHandler(res, 'URL either does not lead to a valid image/video file, or leads to one that is larger than 4MB');
}
//Add the name to the emoteObj
emoteObj.name = emoteName;
//Push the newly created emote object into the channels emotes list
chanDB.emotes.push(emoteObj);
//Save the channel DB document
chanDB.save();
//Return the updated command list
res.status(200);
return res.send(chanDB.getEmotes());
}else{
//otherwise scream
res.status(400);
return res.send({errors: validResult.array()})
}
}catch(err){
return exceptionHandler(res, err);
}
}
module.exports.delete = async function(req, res){
try{
//get validation error results
const validResult = validationResult(req);
//if they're empty
if(validResult.isEmpty()){
//get matched data
const {chanName, emoteName, link} = matchedData(req);
//query for requested channel
const chanDB = await channelModel.findOne({name: chanName});
//If the requested channel doesn't exist
if(chanDB == null){
//ACK
return errorHandler(res, "Channel not found.", "Bad Query");
}
//Filter emote list for...
const foundEmotes = chanDB.emotes.filter((emote, emoteIndex) => {
//If we find an emote that matches the emote name
if(emote.name == emoteName){
//Splice out the emote
chanDB.emotes.splice(emoteIndex, 1);
//Return true to signify we found and deleted one
return true;
}
});
//If didn't find one
if(foundEmotes.length <= 0){
//ACK
return errorHandler(res, "Cannot delete non-existant emote!");
}
//Save the channel DB document
chanDB.save();
//Return the updated command list
res.status(200);
return res.send(chanDB.getEmotes());
}else{
//otherwise scream
res.status(400);
return res.send({errors: validResult.array()})
}
}catch(err){
return exceptionHandler(res, err);
}
}

View file

@ -19,12 +19,16 @@ const { body, checkExact } = require('express-validator');
const { Router } = require('express');
//local imports
const permissionSchema = require("../../schemas/permissionSchema");
//Models
const permissionModel = require("../../schemas/permissionSchema");
const channelModel = require("../../schemas/channel/channelSchema");
//Valudators
const channelValidator = require("../../validators/channelValidator");
const accountValidator = require("../../validators/accountValidator");
const {channelPermissionValidator} = require("../../validators/permissionsValidator");
const tokebotValidator = require("../../validators/tokebotValidator");
const emoteValidator = require("../../validators/emoteValidator");
//Controllers
const registerController = require("../../controllers/api/channel/registerController");
const listController = require("../../controllers/api/channel/listController");
const settingsController = require("../../controllers/api/channel/settingsController");
@ -33,18 +37,20 @@ const rankController = require("../../controllers/api/channel/rankController");
const deleteController = require("../../controllers/api/channel/deleteController");
const banController = require("../../controllers/api/channel/banController");
const tokeCommandController = require("../../controllers/api/channel/tokeCommandController");
const emoteController = require('../../controllers/api/channel/emoteController');
//globals
const router = Router();
//user authentication middleware
router.use("/register",permissionSchema.reqPermCheck("registerChannel"));
router.use("/register",permissionModel.reqPermCheck("registerChannel"));
router.use("/settings", channelValidator.name('chanName'));
router.use("/permissions", channelValidator.name('chanName'));
router.use("/rank", channelValidator.name('chanName'));
router.use("/delete", channelValidator.name('chanName'));
router.use("/ban", channelValidator.name('chanName'));
router.use("/tokeCommand", channelValidator.name('chanName'));
router.use("/emote", channelValidator.name('chanName'));
//routing functions
//register
@ -70,5 +76,9 @@ router.delete('/ban', channelModel.reqPermCheck("banUser"), accountValidator.u
router.get('/tokeCommand', channelModel.reqPermCheck("manageChannel"), tokeCommandController.get);
router.post('/tokeCommand', tokebotValidator.command(), channelModel.reqPermCheck("editTokeCommands"), tokeCommandController.post);
router.delete('/tokeCommand', tokebotValidator.command(), channelModel.reqPermCheck("editTokeCommands"), tokeCommandController.delete);
//emote
router.get('/emote', channelModel.reqPermCheck("manageChannel"), emoteController.get);
router.post('/emote', channelModel.reqPermCheck("editEmotes"), emoteValidator.name('emoteName'), emoteValidator.link(), emoteController.post);
router.delete('/emote', channelModel.reqPermCheck("editEmotes"), emoteValidator.name('emoteName'), emoteController.delete);
module.exports = router;

View file

@ -76,6 +76,12 @@ const channelPermissionSchema = new mongoose.Schema({
default: "admin",
required: true
},
editEmotes: {
type: mongoose.SchemaTypes.String,
enum: rankEnum,
default: "admin",
required: true
},
deleteChannel: {
type: mongoose.SchemaTypes.String,
enum: rankEnum,

View file

@ -23,6 +23,7 @@ const server = require('../../server');
const statModel = require('../statSchema');
const {userModel} = require('../userSchema');
const permissionModel = require('../permissionSchema');
const emoteModel = require('../emoteSchema');
const channelPermissionSchema = require('./channelPermissionSchema');
const channelBanSchema = require('./channelBanSchema');
const { exceptionHandler, errorHandler } = require('../../utils/loggerUtils');
@ -76,6 +77,23 @@ const channelSchema = new mongoose.Schema({
type: mongoose.SchemaTypes.String,
required: true
}],
//Not re-using the site-wide schema because post save should call different functions
emotes: [{
name:{
type: mongoose.SchemaTypes.String,
required: true
},
link:{
type: mongoose.SchemaTypes.String,
required: true
},
type:{
type: mongoose.SchemaTypes.String,
required: true,
enum: emoteModel.typeEnum,
default: emoteModel.typeEnum[0]
}
}],
//Thankfully we don't have to keep track of alts, ips, or deleted users so this should be a lot easier than site-wide bans :P
banList: [channelBanSchema]
});
@ -160,6 +178,17 @@ channelSchema.pre('save', async function (next){
}
}
if(this.isModified('emotes')){
//Get the active Channel object from the application side of the house
const activeChannel = server.channelManager.activeChannels.get(this.name);
//If the channel is active
if(activeChannel != null){
//Broadcast the emote list
activeChannel.broadcastChanEmotes(this);
}
}
next();
});
@ -439,6 +468,24 @@ channelSchema.methods.checkBanByUserDoc = async function(userDB){
return foundBan;
}
channelSchema.methods.getEmotes = function(){
//Create an empty array to hold our emote list
const emoteList = [];
//For each channel emote
this.emotes.forEach((emote) => {
//Push an object with select information from the emote to the emote list
emoteList.push({
name: emote.name,
link: emote.link,
type: emote.type
});
});
//return the emote list
return emoteList;
}
channelSchema.methods.getChanBans = async function(){
//Create an empty list to hold our found bans
var banList = [];

View file

@ -40,7 +40,7 @@ const emoteSchema = new mongoose.Schema({
}
});
//pre-save function
//post-save function
emoteSchema.post('save', async function (next){
//broadcast updated emotes
server.channelManager.broadcastSiteEmotes();

View file

@ -30,6 +30,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.-->
<%- include('partial/channelSettings/settings.ejs'); %>
<%- include('partial/channelSettings/permList.ejs'); %>
<%- include('partial/channelSettings/tokeCommandList.ejs'); %>
<%- include('partial/channelSettings/emoteList.ejs'); %>
</div>
<button href="javascript:" class="danger-button" id="chan-delete">Delete Channel</button>
<footer>

View file

@ -0,0 +1,28 @@
<!--Canopy - The next generation of stoner streaming software
Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.-->
<div id="emote-list-div" class="admin-list-div">
<h3>Emote List:</h3>
<div class="control-prompt">
<!-- Probably not the cleanest way to do this but fuggit -->
[<input placeholder="Emote Name..." id="new-emote-name-input" class="control-prompt">]
<button id="new-emote-button" class="positive-button">Add</button>
</div>
<div class="control-prompt">
<input placeholder="Emote Link..." id="new-emote-link-input" class="control-prompt">
</div>
<div class="dynamic-container" id="emote-list">
</div>
</div>