Compare commits
3 commits
main
...
verbose-qu
| Author | SHA1 | Date | |
|---|---|---|---|
| 3426d6764a | |||
| 91c89ba28f | |||
| e1528c0155 |
265 changed files with 2338 additions and 14055 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -1,13 +1,7 @@
|
|||
node_modules/
|
||||
log/*
|
||||
www/doc/*/*
|
||||
package-lock.json
|
||||
config.json
|
||||
config.json.old
|
||||
state.json
|
||||
chatexamples.txt
|
||||
server.cert
|
||||
server.key
|
||||
www/nonfree/*
|
||||
migration/*
|
||||
www/hrt.zip
|
||||
server.key
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
|||
All Original Canopy Code is Covered under the Gnu Affero General Public License v3 - 2024 - 2026 Rainbownapkin
|
||||
All Original Canopy Code is Covered under the Gnu Affero General Public License v3 - 2024 - 2025 Rainbownapkin
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
|
|
|||
78
README.md
78
README.md
|
|
@ -1,44 +1,36 @@
|
|||
Canopy
|
||||
======
|
||||
|
||||
<img src="https://img.shields.io/badge/developed-while%20high-339933">
|
||||
<img src="https://pride-badges.pony.workers.dev/static/v1?label=enbyware&labelColor=%23555&stripeWidth=8&stripeColors=FCF434%2CFFFFFF%2C9C59D1%2C2C2C2C">
|
||||
<img src="https://pride-badges.pony.workers.dev/static/v1?label=trans%20rights&stripeWidth=6&stripeColors=5BCEFA,F5A9B8,FFFFFF,F5A9B8,5BCEFA">
|
||||
<img src="https://pride-badges.pony.workers.dev/static/v1?label=Sponsored+by+the+Gay+Agenda&labelColor=%23555&stripeWidth=8&stripeColors=E40303%2CFF8C00%2CFFED00%2C008026%2C24408E%2C732982">
|
||||
<a href="https://git.ourfore.st/rainbownapkin/canopy/issues" target="_blank"><img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/open.svg"></a>
|
||||
<a href="https://git.ourfore.st/rainbownapkin/canopy/issues" target="_blank"><img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/closed.svg"></a>
|
||||
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank"><img src="https://img.shields.io/badge/License-AGPL_v3-663366.svg"></a>
|
||||
|
||||
0.2-Alpha (Durban Poison)
|
||||
=========
|
||||
|
||||
Canopy - /ˈkæ.nə.pi/:
|
||||
- The upper layer of foliage and branches of a forest, containing the majority of animal life.
|
||||
- An honest attempt at a freedom/privacy respecting, libre, and open-source refrence implementation of what a stoner streaming service can be.
|
||||
|
||||
Canopy is a community chat & synced video embedding web application, intended to replace fore.st as the server software for ourfore.st.
|
||||
This new codebase intends to solve the following issues with the current CyTube based software:
|
||||
|
||||
- Unmaintained upstream codebase.
|
||||
- Different goals.
|
||||
- Different coding styles.
|
||||
- Obsolete Technology and Dependencies.
|
||||
- General Clunk
|
||||
- Less Unique Community Identity
|
||||
|
||||
Canopy is a simple node/express.js app, leveraging yt-dlp and the internet archive REST api for metadata gathering. Persistant storage is handled by mongodb, as it's document based nature inherintly works well for cleanly storing large config documents for user/channel settings, and the low use of inter-collection references within the canopy software. All hardcore security functions like server-side input sanatization, session handling, CSRF mitigation, and password hashing are handled by industry-standard open source libraries such as validator/express-validator, express-sessions, csrf-sync, and argon2, however it IS hobbiest software, and it should be treated as such.
|
||||
|
||||
The Canopy codebase does not, nor will it ever contain:
|
||||
- Advertisements (targetted or otherwise)
|
||||
- Proprietary Code
|
||||
- Cryptocurrency/Blockchain integration
|
||||
- 'Analytics/Telemtry' spyware
|
||||
- The use of video sources which require proprietary 'Digital ~~Rights Management~~ Ristricitons Malware' such as Widevine.
|
||||
- The use of large language models, stable diffusion, or generative AI in either development or function.
|
||||
|
||||
Thirdparty media providers may or may not contain all of the above atrocities :P (though browser-side DRM extensions will never be required), always use an ad-blocker!
|
||||
|
||||
Our current goal is to create a cleaner, more modern, purpose-built back-end that has feature-parity with the current version of fore.st, writing improvements where possible. Paired with this functionality, are a mix of engineering and artistic choices which attempt to re-create the power-user friendly UX of desktop sites from the early 2010's, with the 'aged like wine' looks that late oughts/early web 2.0 designs graced us with. Making sure that pageloads are low, and GPU use is non-existant along the way, to ensure everything is usable, even on low-end machines.
|
||||
|
||||
## License
|
||||
Canopy - 0.2.1-INDEV (verbose queue)
|
||||
======
|
||||
|
||||
---
|
||||
|
||||
This branch exists to seperate the extra shit I added to the queue for bug-hunting so it wouldn't make a mess of the main development branch :P
|
||||
|
||||
---
|
||||
|
||||
Canopy - /ˈkæ.nə.pi/:
|
||||
- The upper layer of foliage and branches of a forest, containing the majority of animal life.
|
||||
|
||||
Canopy is a community chat & synced video embedding web application, intended to replace fore.st as the server software for ourfore.st.
|
||||
This new codebase intends to solve the following issues with the current CyTube based software:
|
||||
|
||||
- Unmaintained upstream codebase.
|
||||
- Different goals.
|
||||
- Different coding styles.
|
||||
- Obsolete Technology and Dependencies.
|
||||
- General Clunk
|
||||
- Less Unique Community Identity
|
||||
|
||||
Canopy intends to be a simple node/express.js app. It leverages the piped and the internet archive REST api's, as well as yt-dlp, for metadata gathering. Persistant storage is handled by mongodb, as it's document based nature inherintly works well for cleanly storing large config documents for user/channel settings, and the low use of inter-collection references within the canopy software. All hardcore security functions like session handling, CSRF mitigation, and password hashing are handled by industry-standard open source libraries such as express-sessions, csrf-sync, and bcrypt, however it IS hobbiest software, and it should be treated as such.
|
||||
|
||||
The Canopy codebase does not, nor will it ever contain:
|
||||
- Advertisements (targetted or otherwise)
|
||||
- Proprietary Code
|
||||
- 'Analytics/Telemtry' spyware
|
||||
- The use of video sources which require proprietary 'Digital ~~Rights Management~~ Ristricitons Malware' such as Widevine.
|
||||
|
||||
Thirdparty media providers may or may not contain all of the above atrocities :P, always use an ad-blocker!
|
||||
|
||||
Our current goal is to create a cleaner, more modern, purpose-built codebase that has feature-parity with the current version of fore.st, while writing improvements where possible. Once this is accomplished, and ourfore.st has been migrated, work will continue to re-create features from TTN, while also building completely new ones as well.
|
||||
|
||||
## License
|
||||
Canopy is written by the community, and provided under the GNU Affero General Public License v3 in order to prevent Canopy from being used in proprietary software or shitcoin scams.
|
||||
|
|
@ -1,21 +1,11 @@
|
|||
{
|
||||
"instanceName": "Canopy",
|
||||
"verbose": false,
|
||||
"port": 8443,
|
||||
"proxied": true,
|
||||
"protocol": "https",
|
||||
"port": 8080,
|
||||
"protocol": "http",
|
||||
"domain": "localhost",
|
||||
"ytdlpPath": "/home/canopy/.local/pipx/venvs/yt-dlp/bin/yt-dlp",
|
||||
"migrate": false,
|
||||
"dropLegacyTokes": false,
|
||||
"debug": false,
|
||||
"secrets":{
|
||||
"passwordSecret": "CHANGE_ME",
|
||||
"rememberMeSecret": "CHANGE_ME",
|
||||
"sessionSecret": "CHANGE_ME",
|
||||
"altchaSecret": "CHANGE_ME",
|
||||
"ipSecret": "CHANGE_ME"
|
||||
},
|
||||
"sessionSecret": "CHANGE_ME",
|
||||
"altchaSecret": "CHANGE_ME",
|
||||
"ssl":{
|
||||
"cert": "./server.cert",
|
||||
"key": "./server.key"
|
||||
|
|
@ -33,11 +23,5 @@
|
|||
"secure": true,
|
||||
"address": "toke@42069.weed",
|
||||
"pass": "CHANGE_ME"
|
||||
},
|
||||
"links":{
|
||||
"About": "/about",
|
||||
"Code": "https://git.ourfore.st/rainbownapkin/canopy",
|
||||
"HRT": "/hrt"
|
||||
},
|
||||
"aboutText":"<a href=\"https://ourfore.st/\">ourfore.st</a> is the one and only original canopy instance. Setup, ran, and administered by rainbownapkin herself. This site exists to provide a featureful, preformant, and comfy replacement for the TTN community."
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
//WARNING DO NOT USE THIS CONFIG, USE THE REGULAR EXAMPLE, CANOPY DOES NOT SUPPORT JSONC CONFIGS!
|
||||
//This file merely exists to help you configure your actual config file
|
||||
{
|
||||
//Display name on the nav bar and a coupla other places
|
||||
"instanceName": "Canopy",
|
||||
//Scream about exceptions in the console
|
||||
"verbose": false,
|
||||
//Port to bind to (most linux/unix systems req root for ports below 1000, you should probably use nginx if you want port 80 or 443)
|
||||
"port": 8443,
|
||||
//Lets the server know it's sitting behind a reverse-proxy
|
||||
"proxied": true,
|
||||
//Protocol (either HTTP or HTTPS)
|
||||
"protocol": "http",
|
||||
//Domain the server is available at, used for server-side link generation
|
||||
"domain": "localhost",
|
||||
//Path to YT-DLP Executable for scraping youtube, dailymotion, and vimeo
|
||||
//Dailymotion and Vimeo could work using official apis w/o keys, but you wouldn't have any raw file playback options :P
|
||||
"ytdlpPath": "/home/canopy/.local/pipx/venvs/yt-dlp/bin/yt-dlp",
|
||||
//Enable to migrate legacy DB and toke files dumped into the ./migration/ directory
|
||||
//WARNING: The migration folder is cleared after server boot, whether or not a migration took place or this option is enabled.
|
||||
//Keep your backups in a safe place, preferably a machine that DOESN'T have open inbound ports exposed to the internet/a publically accessible reverse proxy!
|
||||
"migrate": false,
|
||||
//Drops all legacy tokes out of the statistics file since doing so manually from mongosh is a lot of typing out obnoxious parentha-glyphics.
|
||||
//Requires migration to be disabled before it takes effect.
|
||||
//WARNING: this does NOT affect user toke counts, migrated or otherwise. Use carefully!
|
||||
"dropLegacyTokes": false,
|
||||
//Enters the server into debug mode, allows specific commands to be emitted from the client-side dev console
|
||||
//Usually to get the server to dump some sort of internal data for debugging purposes.
|
||||
//Obviously, this means enabling this can have some gnar implications.
|
||||
//Probably don't enable this on your production server unless you REALLY REALLY have to, and you probably don't.
|
||||
"debug": false,
|
||||
//Server Secrets
|
||||
//Be careful with what you keep in secrets, you should use special chars, but test your deployment, as some chars may break account registration
|
||||
//An update to either kill the server and bitch about the issue in console is planned so it's not so confusing for new admins
|
||||
"secrets":{
|
||||
//Password secret used to pepper password hashes
|
||||
"passwordSecret": "CHANGE_ME",
|
||||
//Password secret used to pepper rememberMe token hashes
|
||||
"rememberMeSecret": "CHANGE_ME",
|
||||
//Session secret used to secure session keys
|
||||
"sessionSecret": "CHANGE_ME",
|
||||
//Altacha secret used to generate altcha challenges
|
||||
"altchaSecret": "CHANGE_ME",
|
||||
//IP Secret used to pepper IP Hashes
|
||||
"ipSecret": "CHANGE_ME"
|
||||
},
|
||||
//SSL cert and key locations
|
||||
"ssl":{
|
||||
"cert": "./server.cert",
|
||||
"key": "./server.key"
|
||||
},
|
||||
//DB configuration
|
||||
"db":{
|
||||
"address": "127.0.0.1",
|
||||
"port": "27017",
|
||||
"database": "canopy",
|
||||
"user": "canopy",
|
||||
"pass": "CHANGE_ME"
|
||||
},
|
||||
//maintainence email configuration
|
||||
"mail":{
|
||||
"host": "mail.42069.weed",
|
||||
"port": 465,
|
||||
"secure": true,
|
||||
"address": "toke@42069.weed",
|
||||
"pass": "CHANGE_ME"
|
||||
},
|
||||
//Provides customizable links for navbar
|
||||
"links":{
|
||||
"About": "/about",
|
||||
"Code": "https://git.ourfore.st/rainbownapkin/canopy",
|
||||
"HRT": "/hrt"
|
||||
},
|
||||
//Fills the 'about ${instanceName}' section on the /about page, lets users know about your specific instance
|
||||
"aboutText":"<a href=\"https://ourfore.st/\">ourfore.st</a> is the one and only original canopy instance. Setup, ran, and administered by rainbownapkin herself. This site exists to provide a featureful, preformant, and comfy replacement for the TTN community."
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
We're a pretty small and informal community, so I think I can make this small and informal:
|
||||
|
||||
- Registration on git.ourfore.st is locked for anti-bot reasons. Bug bowie if you want access.
|
||||
- Start a new branch either here or on another gitforge (preferably https://codeburg.org), and submit a PR
|
||||
- Don't feel the need to limit yourself to already open issues, those are simply my current goals. Feel free to fuck around and make cool shit.
|
||||
- No stolen and/or LLM-Generated code
|
||||
- Thank you and much love for peeking at, using, and maybe even contributing to Canopy! <3
|
||||
|
||||
-Bowie
|
||||
25
package.json
25
package.json
|
|
@ -1,36 +1,25 @@
|
|||
{
|
||||
"name": "canopy-of-alpha",
|
||||
"version": "0.2.0",
|
||||
"canopyDisplayVersion": "0.2-Alpha (Durban Poison)",
|
||||
"name": "canopy-of",
|
||||
"version": "0.2.1",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^7.1.1",
|
||||
"altcha": "^2.3.0",
|
||||
"altcha": "^1.0.7",
|
||||
"altcha-lib": "^1.2.0",
|
||||
"argon2": "^0.44.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"connect-mongo": "^5.1.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"csrf-sync": "^4.0.3",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.18.0",
|
||||
"express-validator": "^7.2.0",
|
||||
"hls.js": "^1.6.2",
|
||||
"mongoose": "^8.4.3",
|
||||
"node-cron": "^3.0.3",
|
||||
"nodemailer": "^8.0.7",
|
||||
"socket.io": "^4.2.0",
|
||||
"youtube-dl-exec": "^3.0.20"
|
||||
"nodemailer": "^6.9.16",
|
||||
"socket.io": "^4.8.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./src/server.js",
|
||||
"start:dev": "nodemon ./src/server.js",
|
||||
"build": "node node_modules/jsdoc/jsdoc.js --verbose -r src/ -R README.md -d www/doc/server/ && node node_modules/jsdoc/jsdoc.js --verbose -r www/js/channel -r README.md -d www/doc/client/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jsdoc": "^4.0.4",
|
||||
"nodemon": "^3.1.10"
|
||||
"start:dev": "nodemon ./src/server.js"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//local includes
|
||||
const loggerUtils = require("../utils/loggerUtils");
|
||||
const socketUtils = require("../utils/socketUtils");
|
||||
|
||||
/**
|
||||
* Class containg global server-side logic for handling namespaces outside of the main one
|
||||
*/
|
||||
class auxServer{
|
||||
/**
|
||||
* Instantiates object containing global server-side private message relay logic
|
||||
* @param {Socket.io} io - Socket.io server instanced passed down from server.js
|
||||
* @param {channelManager} chanServer - Sister channel management server object
|
||||
*/
|
||||
constructor(io, chanServer, namespace){
|
||||
/**
|
||||
* Socket.io server instance passed down from server.js
|
||||
*/
|
||||
this.io = io;
|
||||
|
||||
/**
|
||||
* Sister channel management server object
|
||||
*/
|
||||
this.chanServer = chanServer;
|
||||
|
||||
/**
|
||||
* Socket.io server namespace for handling messaging
|
||||
*/
|
||||
this.namespace = io.of(namespace);
|
||||
|
||||
//Handle connections from private messaging namespace
|
||||
this.namespace.on("connection", this.handleConnection.bind(this) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles global server-side initialization for new connections to aux server
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
*/
|
||||
async handleConnection(socket, lite = true){
|
||||
try{
|
||||
//Define empty value to hold result
|
||||
let result = null;
|
||||
|
||||
//ensure unbanned ip and valid CSRF token
|
||||
if(!(await socketUtils.validateSocket(socket))){
|
||||
socket.disconnect();
|
||||
return null;
|
||||
}
|
||||
|
||||
if(lite){
|
||||
result = await socketUtils.authSocketLite(socket);
|
||||
}else{
|
||||
result = await socketUtils.authSocket(socket);
|
||||
}
|
||||
|
||||
//If the socket wasn't authorized
|
||||
if(result == null){
|
||||
socket.disconnect();
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}catch(err){
|
||||
//Flip a table if something fucks up
|
||||
return loggerUtils.socketCriticalExceptionHandler(socket, err);
|
||||
}
|
||||
}
|
||||
|
||||
defineListeners(socket){
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = auxServer;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -16,71 +16,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
|
||||
//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')
|
||||
|
||||
/**
|
||||
* Class representing a single active channel
|
||||
*/
|
||||
class activeChannel{
|
||||
|
||||
/**
|
||||
* Instantiates an activeChannel object
|
||||
* @param {channelManager} server - Parent Server Object
|
||||
* @param {Mongoose.Document} chanDB - chanDB to rehydrate buffer from
|
||||
*/
|
||||
module.exports = class{
|
||||
constructor(server, chanDB){
|
||||
/**
|
||||
* Parent Server Object
|
||||
*/
|
||||
this.server = server;
|
||||
|
||||
/**
|
||||
* Current Channel Name
|
||||
*/
|
||||
this.name = chanDB.name;
|
||||
|
||||
/**
|
||||
* List of channel-wide toke commands
|
||||
*/
|
||||
this.tokeCommands = chanDB.tokeCommands;
|
||||
|
||||
/**
|
||||
* Internal Database Identifier for the channel
|
||||
*/
|
||||
this._id = chanDB._id;
|
||||
|
||||
/**
|
||||
* List of connected users
|
||||
*/
|
||||
//Keeping these in a map was originally a vestige but it's more preformant than an array or object so :P
|
||||
this.userList = new Map();
|
||||
|
||||
/**
|
||||
* Child Queue Object
|
||||
*/
|
||||
this.queue = new queue(server, chanDB, this);
|
||||
|
||||
/**
|
||||
* Child Playlist Handler Object
|
||||
*/
|
||||
this.playlistHandler = new playlistHandler(server, this);
|
||||
|
||||
/**
|
||||
* Child Chat Buffer Object
|
||||
*/
|
||||
this.chatBuffer = new chatBuffer(server, chanDB, this);
|
||||
this.playlistHandler = new playlistHandler(server, chanDB, this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles server-side initialization for new connections to the channel
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
* @returns {activeUser} active user object generated by the new connection
|
||||
*/
|
||||
async handleConnection(userDB, chanDB, socket){
|
||||
//get current user object from the userlist
|
||||
var userObj = this.userList.get(userDB.user);
|
||||
|
|
@ -110,25 +60,18 @@ class activeChannel{
|
|||
this.playlistHandler.defineListeners(socket);
|
||||
|
||||
//Hand off the connection initiation to it's user object
|
||||
const activeUser = await userObj.handleConnection(userDB, chanDB, socket)
|
||||
await userObj.handleConnection(userDB, chanDB, socket)
|
||||
|
||||
//Send out the userlist
|
||||
this.broadcastUserList(socket.chan);
|
||||
|
||||
//Return active user connection object for use by the channelManager object
|
||||
return activeUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles server-side initialization for disconnecting from the channel
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
*/
|
||||
handleDisconnect(socket){
|
||||
//temporarily store userObj
|
||||
let userObj = this.userList.get(socket.user.user);
|
||||
|
||||
handleDisconnect(socket, reason){
|
||||
//If we have more than one active connection
|
||||
if(userObj.sockets.length > 1){
|
||||
if(this.userList.get(socket.user.user).sockets.length > 1){
|
||||
//temporarily store userObj
|
||||
var userObj = this.userList.get(socket.user.user);
|
||||
|
||||
//Filter out disconnecting socket from socket list, and set as current socket list for user
|
||||
userObj.sockets = userObj.sockets.filter((id) => {
|
||||
return id != socket.id;
|
||||
|
|
@ -136,11 +79,7 @@ class activeChannel{
|
|||
|
||||
//Update the userlist
|
||||
this.userList.set(socket.user.user, userObj);
|
||||
//If this is the last one
|
||||
}else{
|
||||
//Tell the server to handle the disconnection of this user object
|
||||
this.server.handleUserDisconnect(userObj);
|
||||
|
||||
//If this is the last connection for this user, remove them from the userlist
|
||||
this.userList.delete(socket.user.user);
|
||||
}
|
||||
|
|
@ -149,9 +88,6 @@ class activeChannel{
|
|||
this.broadcastUserList(socket.chan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts user list to all users
|
||||
*/
|
||||
broadcastUserList(){
|
||||
//Create a userlist object with the tokebot user pre-loaded
|
||||
var userList = [{
|
||||
|
|
@ -171,10 +107,6 @@ class activeChannel{
|
|||
this.server.io.in(this.name).emit("userList", userList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts channel emote list to connected users
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async broadcastChanEmotes(chanDB){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
|
|
@ -188,6 +120,4 @@ class activeChannel{
|
|||
//Broadcast that sumbitch
|
||||
this.server.io.in(this.name).emit('chanEmotes', emoteList);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = activeChannel;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -14,70 +14,37 @@ 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/>.*/
|
||||
|
||||
//Config
|
||||
const config = require('../../../config.json');
|
||||
|
||||
//Local Imports
|
||||
const channelModel = require('../../schemas/channel/channelSchema');
|
||||
const emoteModel = require('../../schemas/emoteSchema');
|
||||
const socketUtils = require('../../utils/socketUtils');
|
||||
const {userModel} = require('../../schemas/user/userSchema');
|
||||
const userBanModel = require('../../schemas/user/userBanSchema');
|
||||
const loggerUtils = require('../../utils/loggerUtils');
|
||||
const presenceUtils = require('../../utils/presenceUtils');
|
||||
const csrfUtils = require('../../utils/csrfUtils');
|
||||
const activeChannel = require('./activeChannel');
|
||||
const chatHandler = require('./chatHandler');
|
||||
const queueBroadcastManager = require('./media/queueBroadcastManager');
|
||||
//const mediaYanker = require('./media/yanker');
|
||||
|
||||
/**
|
||||
* Class containing global server-side channel connection management logic
|
||||
*/
|
||||
class channelManager{
|
||||
/**
|
||||
* Instantiates object containing global server-side channel conection management logic
|
||||
* @param {Socket.io} io - Socket.io server instanced passed down from server.js
|
||||
*/
|
||||
module.exports = class{
|
||||
constructor(io){
|
||||
/**
|
||||
* Socket.io server instance passed down from server.js
|
||||
*/
|
||||
//Set the socket.io server
|
||||
this.io = io;
|
||||
|
||||
/**
|
||||
* Map containing all active channels running on the server
|
||||
*/
|
||||
//Load
|
||||
this.activeChannels = new Map;
|
||||
|
||||
/**
|
||||
* Map containing all active users. This may be redundant, however it improves preformance for user-specific inter-channel functionality
|
||||
*/
|
||||
this.activeUsers = new Map;
|
||||
|
||||
/**
|
||||
* Global Chat Handler Object
|
||||
*/
|
||||
//Load server components
|
||||
this.chatHandler = new chatHandler(this);
|
||||
|
||||
/**
|
||||
* Global Chat Handler Object
|
||||
*/
|
||||
this.chatHandler = new chatHandler(this);
|
||||
|
||||
/**
|
||||
* Global Auxiliary Server for Authenticated Queue Metadata Brodcasting
|
||||
*/
|
||||
this.queueBroadcastManager = new queueBroadcastManager(this.io, this)
|
||||
//this.mediaYanker = new mediaYanker(this);
|
||||
|
||||
//Handle connections from socket.io
|
||||
io.on("connection", this.handleConnection.bind(this) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles global server-side initialization for new connections to any channel
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
*/
|
||||
async handleConnection(socket){
|
||||
try{
|
||||
//ensure unbanned ip and valid CSRF token
|
||||
if(!(await socketUtils.validateSocket(socket))){
|
||||
if(!(await this.validateSocket(socket))){
|
||||
socket.disconnect();
|
||||
return;
|
||||
}
|
||||
|
|
@ -85,7 +52,7 @@ class channelManager{
|
|||
//Prevent logged out connections and authenticate socket
|
||||
if(socket.request.session.user != null){
|
||||
//Authenticate socket
|
||||
const userDB = await socketUtils.authSocket(socket);
|
||||
const userDB = await this.authSocket(socket);
|
||||
|
||||
//Get the active channel based on the socket
|
||||
var {activeChan, chanDB} = await this.getActiveChan(socket);
|
||||
|
|
@ -103,48 +70,13 @@ class channelManager{
|
|||
return;
|
||||
}
|
||||
|
||||
//Connection accepted past this point
|
||||
|
||||
//Define listeners for inter-channel classes
|
||||
this.defineListeners(socket);
|
||||
this.chatHandler.defineListeners(socket);
|
||||
|
||||
//Hand off the connection to it's given active channel object
|
||||
//Lil' hacky to pass chanDB like that, but why double up on DB calls?
|
||||
const activeUser = await activeChan.handleConnection(userDB, chanDB, socket);
|
||||
|
||||
//Pull status from server-wide activeUsers map
|
||||
let status = this.activeUsers.get(activeUser.user);
|
||||
|
||||
//If this user isn't connected anywhere else
|
||||
if(status == null){
|
||||
//initiate the entry
|
||||
this.activeUsers.set(activeUser.user, [activeUser]);
|
||||
//otherwise
|
||||
}else{
|
||||
//Push user to array by default
|
||||
let pushUser = true;
|
||||
|
||||
//For each active connection within the status map
|
||||
for(let curUser of status){
|
||||
//If we're already listing this active user (we're splitting a user connection amongst several sockets)
|
||||
if(curUser.channel.name == activeUser.channel.name){
|
||||
//don't need to push it again
|
||||
pushUser = false;
|
||||
}
|
||||
}
|
||||
|
||||
//if the user is flagged as un-added
|
||||
if(pushUser){
|
||||
//Add their connection object into the status array we pulled
|
||||
status.push(activeUser);
|
||||
|
||||
//Set status entry to updated array
|
||||
this.activeUsers.set(activeUser.set, status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
activeChan.handleConnection(userDB, chanDB, socket);
|
||||
}else{
|
||||
//Toss out anon's
|
||||
socket.emit("kick", {type: "disconnected", reason: "You must log-in to join this channel!"});
|
||||
|
|
@ -155,20 +87,54 @@ class channelManager{
|
|||
//Flip a table if something fucks up
|
||||
return loggerUtils.socketCriticalExceptionHandler(socket, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateSocket(socket){
|
||||
//Look for ban by IP
|
||||
const ipBanDB = await userBanModel.checkBanByIP(socket.handshake.address);
|
||||
|
||||
//If this ip is randy bobandy
|
||||
if(ipBanDB != null){
|
||||
//tell it to fuck off
|
||||
socket.emit("kick", {type: "kicked", reason: "The IP address you are trying to connect from has been banned!"});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//Check for Cross-Site Request Forgery
|
||||
if(!csrfUtils.isRequestValid(socket.request)){
|
||||
socket.emit("kick", {type: "disconnected", reason: "Invalid CSRF Token!"});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async authSocket(socket){
|
||||
//Find the user in the Database since the session won't store enough data to fulfill our needs :P
|
||||
const userDB = await userModel.findOne({user: socket.request.session.user.user});
|
||||
|
||||
if(userDB == null){
|
||||
throw new Error("User not found!");
|
||||
}
|
||||
|
||||
//Set socket user and channel values
|
||||
socket.user = {
|
||||
id: userDB.id,
|
||||
user: userDB.user,
|
||||
};
|
||||
|
||||
return userDB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets active channel from a given socket
|
||||
* @param {Socket} socket - Socket to check
|
||||
* @returns {Object} Object containing users active channel name and channel document object
|
||||
*/
|
||||
async getActiveChan(socket){
|
||||
socket.chan = socketUtils.getChannelName(socket);
|
||||
const chanDB = (await channelModel.findOne({name: socket.chan}));
|
||||
socket.chan = socket.handshake.headers.referer.split('/c/')[1].split('/')[0];
|
||||
const chanDB = (await channelModel.findOne({name: socket.chan}))
|
||||
|
||||
//Check if channel exists
|
||||
if(chanDB == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found", "validation");
|
||||
throw new Error("Channel not found!");
|
||||
}
|
||||
|
||||
//Check if current channel is active
|
||||
|
|
@ -182,73 +148,24 @@ class channelManager{
|
|||
|
||||
//Return whatever the active channel is (new or old)
|
||||
return {activeChan, chanDB};
|
||||
//return activeChan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define Global Server-Side socket event listeners
|
||||
* @param {Socket} socket - Socket to check
|
||||
*/
|
||||
defineListeners(socket){
|
||||
//Socket Listeners
|
||||
socket.conn.on("close", (reason) => {this.handleDisconnect(socket, reason)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Global server-side logic for handling disconncted sockets
|
||||
* @param {Socket} socket - Socket to check
|
||||
* @param {String} reason - Reason for disconnection
|
||||
*/
|
||||
handleDisconnect(socket, reason){
|
||||
var activeChan = this.activeChannels.get(socket.chan);
|
||||
activeChan.handleDisconnect(socket, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a disconnection event for a single active user within a given channel (when all sockets disconnect)
|
||||
* @param {connectedUser} userObj - Connected user object to handle disconnection of
|
||||
*/
|
||||
handleUserDisconnect(userObj){
|
||||
//Create array to hold
|
||||
let stillConnected = [];
|
||||
|
||||
//Crawl through all known user connections
|
||||
this.crawlConnections(userObj.user, (curUser)=>{
|
||||
//If we have a matching username from a different channel
|
||||
if(curUser.user == userObj.user && userObj.channel.name != curUser.channel.name){
|
||||
//Keep current user
|
||||
stillConnected.push(curUser);
|
||||
}
|
||||
});
|
||||
|
||||
//If we have anyone left
|
||||
if(stillConnected.length > 0){
|
||||
//save the remainder to the status map, otherwise unset the value.
|
||||
this.activeUsers.set(userObj.user, stillConnected);
|
||||
//Otherwise
|
||||
}else{
|
||||
//Delete the user from the status map
|
||||
this.activeUsers.delete(userObj.user);
|
||||
|
||||
//Mark last disconnection as user activity, as they'll no longer be marked as streaming.
|
||||
presenceUtils.handlePresence(userObj.user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls user information by socket
|
||||
* @param {Socket} socket - Socket to check
|
||||
* @return returns related user info
|
||||
*/
|
||||
getSocketInfo(socket){
|
||||
const channel = this.activeChannels.get(socket.chan);
|
||||
return channel.userList.get(socket.user.user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls user information by socket
|
||||
* @param {Socket} socket - Socket to check
|
||||
* @return returns related user info
|
||||
*/
|
||||
getConnectedChannels(socket){
|
||||
//Create a list to hold connected channels
|
||||
var chanList = [];
|
||||
|
|
@ -268,52 +185,36 @@ class channelManager{
|
|||
return chanList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through connections by a given username, and runs them through a given callback function/method
|
||||
* @param {String} user - Username to crawl connections against
|
||||
* @param {Function} cb - Callback function to run active connections of a given user against
|
||||
*/
|
||||
crawlConnections(user, cb){
|
||||
//Pull connection list from status map
|
||||
const list = this.activeUsers.get(user);
|
||||
//For each channel
|
||||
this.activeChannels.forEach((channel) => {
|
||||
//Check and see if the user is connected
|
||||
const foundUser = channel.userList.get(user);
|
||||
|
||||
//If we have active connections
|
||||
if(list != null){
|
||||
//For each connection
|
||||
for(let user of list){
|
||||
//Run the callback against it
|
||||
cb(user);
|
||||
//If we found a user and this channel hasn't been added to the list
|
||||
if(foundUser){
|
||||
cb(foundUser);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through connections by a given username, and runs them through a given callback function/method
|
||||
* This function is deprecated. Instead use channelManager.activeUsers.get(user)
|
||||
* @param {String} user - Username to crawl connections against
|
||||
* @param {Function} cb - Callback function to run active connections of a given user against
|
||||
*/
|
||||
getConnections(user){
|
||||
const connections = this.activeUsers.get(user);
|
||||
//Create a list to store our connections
|
||||
var connections = [];
|
||||
|
||||
//crawl through connections
|
||||
//this.crawlConnections(user,(foundUser)=>{connections.push(foundUser)});
|
||||
this.crawlConnections(user,(foundUser)=>{connections.push(foundUser)});
|
||||
|
||||
//return connects
|
||||
return connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks a user from all channels by username
|
||||
* @param {String} user - Username to kick from the server
|
||||
* @param {String} reason - Reason for kick
|
||||
*/
|
||||
kickConnections(user, reason){
|
||||
//crawl through connections and kick user
|
||||
this.crawlConnections(user,(foundUser)=>{foundUser.disconnect(reason)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast global emote list
|
||||
*/
|
||||
async broadcastSiteEmotes(){
|
||||
//Get emote list from DB
|
||||
const emoteList = await emoteModel.getEmotes();
|
||||
|
|
@ -321,6 +222,4 @@ class channelManager{
|
|||
//Broadcast that sumbitch
|
||||
this.io.sockets.emit('siteEmotes', emoteList);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = channelManager;
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//local imports
|
||||
const chatMetadata = require("../chatMetadata");
|
||||
|
||||
/**
|
||||
* Class representing a single chat message
|
||||
*/
|
||||
class chat extends chatMetadata{
|
||||
/**
|
||||
* Instantiates a chat message object
|
||||
* @param {connectedUser} user - User who sent the message
|
||||
*/
|
||||
constructor(user, flair, highLevel, msg, type, links){
|
||||
//Call derived constructor
|
||||
super(user, flair, highLevel, msg, type, links);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = chat;
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
const config = require('../../../config.json');
|
||||
const channelModel = require('../../schemas/channel/channelSchema');
|
||||
|
||||
/**
|
||||
* Class representing a stored chat buffer
|
||||
*/
|
||||
class chatBuffer{
|
||||
/**
|
||||
* Instantiates a new chat buffer for a given channel
|
||||
* @param {channelManager} server - Parent Server Object
|
||||
* @param {Mongoose.Document} chanDB - chanDB to rehydrate buffer from
|
||||
* @param {activeChannel} channel - Parent Channel Object
|
||||
*/
|
||||
constructor(server, chanDB, channel){
|
||||
/**
|
||||
* Parent Server Object
|
||||
*/
|
||||
this.server = server;
|
||||
|
||||
/**
|
||||
* Parent CHannel Object
|
||||
*/
|
||||
this.channel = channel;
|
||||
|
||||
//If we have no chanDB.chatBuffer
|
||||
if(chanDB == null || chanDB.chatBuffer == null){
|
||||
/**
|
||||
* RAM-Based buffer containing array of previous chats
|
||||
*/
|
||||
this.buffer = [];
|
||||
//Otherwise
|
||||
}else{
|
||||
/**
|
||||
* RAM-Based buffer containing array of previous chats
|
||||
*/
|
||||
this.buffer = chanDB.chatBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivity Timer, goes off after x seconds of chat inactivity
|
||||
*/
|
||||
this.inactivityTimer = null;
|
||||
|
||||
/**
|
||||
* Inactivity Timer Delay
|
||||
*/
|
||||
this.inactivityDelay = 10;
|
||||
|
||||
/**
|
||||
* Goes off after x minutes of solid chatroom activity (no inactivityTimer call in x minutes)
|
||||
*/
|
||||
this.busyTimer = null;
|
||||
|
||||
/**
|
||||
* Busy Timer Delay
|
||||
*/
|
||||
this.busyDelay = 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given chat to the chat buffer in RAM and sets any appropriate timers for DB transactions
|
||||
* @param {chat} chat - Chat object to commit to buffer
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the oldest item from the chat buffer
|
||||
*
|
||||
* Was originally created in-case we needed to trigger timing functions
|
||||
*
|
||||
* Left here since it seems like good form anywho, since this would be a private, or at least protected member in another language
|
||||
*/
|
||||
shift(){
|
||||
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.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out buffer timers to prevent saving
|
||||
*/
|
||||
clearTimers(){
|
||||
//clear existing timers
|
||||
clearTimeout(this.inactivityTimer);
|
||||
clearTimeout(this.busyTimer);
|
||||
this.inactivityTimer = null;
|
||||
this.busyTimer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears RAM-Based chat buffer and saves the result to DB
|
||||
* @param {String} name - Name of user to clear chats from. Left as null or an empty string, it will clear the entire buffer.
|
||||
*/
|
||||
async clearBuffer(name){
|
||||
//Clear out DB Timers
|
||||
this.clearTimers();
|
||||
|
||||
let reason = "clearing chat";
|
||||
|
||||
//If we have a null or empty string passed as name
|
||||
if(name == null || name == ""){
|
||||
//Nuke that fcker
|
||||
this.buffer = [];
|
||||
//Otherwise
|
||||
}else{
|
||||
reason = `clearing ${name}'s chats`
|
||||
|
||||
//Iterate through chat buffer by index
|
||||
for(let chatIndex in this.buffer){
|
||||
//If the current chat we're looking at was submitted by the given user
|
||||
if(this.buffer[chatIndex].user.toLowerCase() == name.toLowerCase()){
|
||||
//Splice that fcker out
|
||||
this.buffer.splice(chatIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.saveDB(reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves RAM-Based buffer to Channel Document in DB
|
||||
* @param {String} reason - Reason for DB save, formatted as 'x minutes/seconds of in/activity', used for logging purposes
|
||||
* @param {Mongoose.Document} chanDB - Channel Doc to work with, can be left empty for method to auto-find through channel name.
|
||||
*/
|
||||
async saveDB(reason, chanDB){
|
||||
//Clear out DB Timers
|
||||
this.clearTimers();
|
||||
|
||||
//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;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -14,51 +14,19 @@ 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 validator = require('validator')
|
||||
|
||||
//local imports
|
||||
const chatPreprocessor = require('../chatPreprocessor');
|
||||
const commandProcessor = require('./commandProcessor');
|
||||
const tokebot = require('./tokebot');
|
||||
const commandPreprocessor = require('./commandPreprocessor');
|
||||
const loggerUtils = require('../../utils/loggerUtils');
|
||||
const linkUtils = require('../../utils/linkUtils');
|
||||
const emoteValidator = require('../../validators/emoteValidator');
|
||||
const chat = require('./chat');
|
||||
const {userModel} = require('../../schemas/user/userSchema');
|
||||
|
||||
/**
|
||||
* Class containing global server-side chat relay logic
|
||||
*/
|
||||
class chatHandler{
|
||||
/**
|
||||
* Instantiates a chatHandler object
|
||||
* @param {channelManager} server - Parent Server Object
|
||||
*/
|
||||
module.exports = class{
|
||||
constructor(server){
|
||||
/**
|
||||
* Parent Server Object
|
||||
*/
|
||||
this.server = server;
|
||||
|
||||
/**
|
||||
* Child Command Pre-Processor Object
|
||||
*/
|
||||
this.chatPreprocessor = new chatPreprocessor(
|
||||
new commandProcessor(server, this),
|
||||
new tokebot(server, this)
|
||||
);
|
||||
|
||||
/**
|
||||
* Max chat buffer message count
|
||||
*/
|
||||
this.chatBufferSize = 50;
|
||||
this.commandPreprocessor = new commandPreprocessor(server, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines global server-side chat relay event listeners
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
*/
|
||||
defineListeners(socket){
|
||||
socket.on("chatMessage", (data) => {this.handleChat(socket, data)});
|
||||
socket.on("setFlair", (data) => {this.setFlair(socket, data)});
|
||||
|
|
@ -67,33 +35,10 @@ class chatHandler{
|
|||
socket.on("deletePersonalEmote", (data) => {this.deletePersonalEmote(socket, data)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming chat messages from client connections
|
||||
* @param {Socket} socket - Socket we're receiving the request from
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async handleChat(socket, data){
|
||||
try{
|
||||
//Preprocess chat data
|
||||
const preprocessedChat = await this.chatPreprocessor.preprocess(socket, data);
|
||||
|
||||
//If send flag wasn't set to false
|
||||
if(preprocessedChat != false){
|
||||
//Send that shit!
|
||||
this.relayUserChat(socket, preprocessedChat.message, preprocessedChat.chatType, preprocessedChat.links);
|
||||
}
|
||||
//If something fucked up
|
||||
}catch(err){
|
||||
//Bitch and moan
|
||||
return loggerUtils.socketExceptionHandler(socket, err);
|
||||
}
|
||||
handleChat(socket, data){
|
||||
this.commandPreprocessor.preprocess(socket, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming client request to change flair
|
||||
* @param {Socket} socket - Socket we're receiving the request from
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async setFlair(socket, data){
|
||||
var userDB = await userModel.findOne({user: socket.user.user});
|
||||
|
||||
|
|
@ -113,18 +58,13 @@ class chatHandler{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming client request to change high level
|
||||
* @param {Socket} socket - Socket we're receiving the request from
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async setHighLevel(socket, data){
|
||||
var userDB = await userModel.findOne({user: socket.user.user});
|
||||
|
||||
if(userDB){
|
||||
try{
|
||||
//Floor input to an integer and set high level
|
||||
userDB.highLevel = Math.floor(data.highLevel);
|
||||
//Set high level
|
||||
userDB.highLevel = data.highLevel;
|
||||
//Save user DB Document
|
||||
await userDB.save();
|
||||
|
||||
|
|
@ -141,11 +81,6 @@ class chatHandler{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming client request to add a personal emote
|
||||
* @param {Socket} socket - Socket we're receiving the request from
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async addPersonalEmote(socket, data){
|
||||
//Sanatize and Validate input
|
||||
const name = emoteValidator.manualName(data.name);
|
||||
|
|
@ -176,11 +111,6 @@ class chatHandler{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming client request to delete a personal emote
|
||||
* @param {Socket} socket - Socket we're receiving the request from
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async deletePersonalEmote(socket, data){
|
||||
//Get user doc from DB based on socket
|
||||
const userDB = await userModel.findOne({user: socket.user.user});
|
||||
|
|
@ -191,149 +121,39 @@ class chatHandler{
|
|||
}
|
||||
}
|
||||
|
||||
//Base chat functions
|
||||
/**
|
||||
* Creates a new chatObject and relays the resulting message to the given channel
|
||||
* @param {String} user - Originating user
|
||||
* @param {String} flair - Flair ID to mark chat with
|
||||
* @param {Number} highLevel - High Level to mark chat with
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {String} type - Message Type, used for client-side chat post-processing.
|
||||
* @param {String} chan - Channel to broadcast message within
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
relayChat(user, flair, highLevel, msg, type = 'chat', chan, links){
|
||||
this.relayChatObject(chan, new chat(user, flair, highLevel, msg, type, links));
|
||||
}
|
||||
|
||||
/**
|
||||
* Relays an existing chat object to a channel
|
||||
* @param {String} chan - Channel to broadcast message within
|
||||
* @param {chat} chat - Chat Object representing the message to broadcast to the given channel
|
||||
*/
|
||||
relayChatObject(chan, chat){
|
||||
//If we have an empty chat
|
||||
if(chat.msg.length <= 0){
|
||||
//Drop it
|
||||
return;
|
||||
}
|
||||
|
||||
//Send out chat
|
||||
this.server.io.in(chan).emit("chatMessage", chat);
|
||||
|
||||
const channel = this.server.activeChannels.get(chan);
|
||||
|
||||
//If chat buffer length is over mandated size
|
||||
if(channel.chatBuffer.buffer.length >= this.chatBufferSize){
|
||||
//Take out oldest chat
|
||||
channel.chatBuffer.shift();
|
||||
}
|
||||
|
||||
//Add buffer to chat
|
||||
channel.chatBuffer.push(chat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new chatObject and relays the resulting message to the given socket
|
||||
* @param {Socket} socket - Socket we're sending a message to (sounds menacing, huh?)
|
||||
* @param {String} user - Originating user
|
||||
* @param {String} flair - Flair ID to mark chat with
|
||||
* @param {Number} highLevel - High Level to mark chat with
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {String} type - Message Type, used for client-side chat post-processing.
|
||||
* @param {String} chan - Channel to broadcast message within
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
relayPrivateChat(socket, user, flair, highLevel, msg, type, links){
|
||||
this.relayPrivateChatObject(socket , new chat(user, flair, highLevel, msg, type, links));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming client request to delete a personal emote
|
||||
* @param {Socket} socket - Socket we're receiving the request from
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
relayPrivateChatObject(socket, chat){
|
||||
socket.emit("chatMessage", chat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new chatObject and relays the resulting message to the entire server
|
||||
* @param {String} user - Originating user
|
||||
* @param {String} flair - Flair ID to mark chat with
|
||||
* @param {Number} highLevel - High Level to mark chat with
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {String} type - Message Type, used for client-side chat post-processing.
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
relayGlobalChat(user, flair, highLevel, msg, type = 'chat', links){
|
||||
this.relayGlobalChatObject(new chat(user, flair, highLevel, msg, type, links));
|
||||
}
|
||||
|
||||
/**
|
||||
* Relays an existing chat object to the entire server
|
||||
* @param {chat} chat - Chat Object representing the message to broadcast throughout the server
|
||||
*/
|
||||
relayGlobalChatObject(chat){
|
||||
this.server.io.emit("chatMessage", chat);
|
||||
}
|
||||
|
||||
//User Chat Functions
|
||||
/**
|
||||
* Relays a chat message from a user to the rest of the channel based on socket
|
||||
* @param {Socket} socket - Socket we're receiving the request from
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {String} type - Message Type, used for client-side chat post-processing.
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
relayUserChat(socket, msg, type, links){
|
||||
const user = this.server.getSocketInfo(socket);
|
||||
this.relayChat(user.user, user.flair, user.highLevel, msg, type, socket.chan, links);
|
||||
this.relayChat(user.user, user.flair, user.highLevel, msg, type, socket.chan, links)
|
||||
}
|
||||
|
||||
relayChat(user, flair, highLevel, msg, type = 'chat', chan, links){
|
||||
this.server.io.in(chan).emit("chatMessage", {user, flair, highLevel, msg, type, links});
|
||||
}
|
||||
|
||||
relayServerWisper(socket, user, flair, highLevel, msg, type, links){
|
||||
socket.emit("chatMessage", {user, flair, highLevel, msg, type, links});
|
||||
}
|
||||
|
||||
relayGlobalChat(user, flair, highLevel, msg, type = 'chat', links){
|
||||
this.server.io.emit("chatMessage", {user, flair, highLevel, msg, type, links});
|
||||
}
|
||||
|
||||
//Toke Chat Functions
|
||||
/**
|
||||
* Broadcasts toke callout to the server
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
relayTokeCallout(msg, links){
|
||||
this.relayGlobalChat("Tokebot", "", '∞', msg, "toke", links);
|
||||
}
|
||||
/**
|
||||
* Broadcasts toke callout to the server
|
||||
* @param {Socket} socket - Socket we're sending the whisper to
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
|
||||
relayTokeWhisper(socket, msg, links){
|
||||
this.relayPrivateChat(socket, "Tokebot", "", '∞', msg, "tokewhisper", links);
|
||||
this.relayServerWisper(socket, "Tokebot", "", '∞', msg, "tokewhisper", links);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts toke whisper to the server
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
relayGlobalTokeWhisper(msg, links){
|
||||
this.relayGlobalChat("Tokebot", "", '∞', msg, "tokewhisper", links);
|
||||
}
|
||||
|
||||
//Announcement Functions
|
||||
/**
|
||||
* Broadcasts announcement to the server
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
relayServerAnnouncement(msg, links){
|
||||
this.relayGlobalChat("Server", "", '∞', msg, "announcement", links);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts announcement to a given channel
|
||||
* @param {String} msg - Message Text Content
|
||||
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
|
||||
*/
|
||||
relayChannelAnnouncement(chan, msg, links){
|
||||
const activeChan = this.server.activeChannels.get(chan);
|
||||
|
||||
|
|
@ -343,12 +163,6 @@ class chatHandler{
|
|||
}
|
||||
}
|
||||
|
||||
//Misc Functions
|
||||
/**
|
||||
* Clears chat for a given channel, targets specified user or entire channel if none found/specified.
|
||||
* @param {String} user - User chats to clear
|
||||
* @param {String} chan - Channel to broadcast message within
|
||||
*/
|
||||
clearChat(chan, user){
|
||||
const activeChan = this.server.activeChannels.get(chan);
|
||||
|
||||
|
|
@ -358,14 +172,8 @@ class chatHandler{
|
|||
|
||||
//If no user was entered OR the user was found
|
||||
if(user == null || target != null){
|
||||
//Send command out to browsers to drop chats from buffer
|
||||
this.server.io.in(chan).emit("clearChat", {user});
|
||||
|
||||
//Clear serverside buffer, down to the DB
|
||||
activeChan.chatBuffer.clearBuffer(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = chatHandler;
|
||||
}
|
||||
315
src/app/channel/commandPreprocessor.js
Normal file
315
src/app/channel/commandPreprocessor.js
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
/*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 <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
//NPM Imports
|
||||
const validator = require('validator');//No express here, so regular validator it is!
|
||||
|
||||
//Local Imports
|
||||
const tokebot = require('./tokebot');
|
||||
const linkUtils = require('../../utils/linkUtils');
|
||||
const permissionModel = require('../../schemas/permissionSchema');
|
||||
const channelModel = require('../../schemas/channel/channelSchema');
|
||||
|
||||
module.exports = class commandPreprocessor{
|
||||
constructor(server, chatHandler){
|
||||
this.server = server;
|
||||
this.chatHandler = chatHandler;
|
||||
this.commandProcessor = new commandProcessor(server, chatHandler);
|
||||
this.tokebot = new tokebot(server, chatHandler);
|
||||
}
|
||||
|
||||
async preprocess(socket, data){
|
||||
//Set command object
|
||||
const commandObj = {
|
||||
socket,
|
||||
sendFlag: true,
|
||||
rawData: data,
|
||||
chatType: 'chat'
|
||||
}
|
||||
|
||||
//If we don't pass sanatization/validation turn this car around
|
||||
if(!this.sanatizeCommand(commandObj)){
|
||||
return;
|
||||
}
|
||||
|
||||
//split the command
|
||||
this.splitCommand(commandObj);
|
||||
|
||||
//Process the command
|
||||
await this.processServerCommand(commandObj);
|
||||
|
||||
//If we're going to relay this command as a message, continue on to chat processing
|
||||
if(commandObj.sendFlag){
|
||||
//Prep the message
|
||||
await this.prepMessage(commandObj);
|
||||
|
||||
//Send the chat
|
||||
this.sendChat(commandObj);
|
||||
}
|
||||
}
|
||||
|
||||
sanatizeCommand(commandObj){
|
||||
//Trim and Sanatize for XSS
|
||||
commandObj.command = validator.trim(validator.escape(commandObj.rawData.msg));
|
||||
|
||||
//Return whether or not the shit was long enough
|
||||
return (validator.isLength(commandObj.rawData.msg, {min: 1, max: 255}));
|
||||
}
|
||||
|
||||
splitCommand(commandObj){
|
||||
//Split string by words
|
||||
commandObj.commandArray = commandObj.command.split(/\b/g);//Split by word-borders
|
||||
commandObj.argumentArray = commandObj.command.match(/\b\w+\b/g);//Match by words surrounded by borders
|
||||
}
|
||||
|
||||
async processServerCommand(commandObj){
|
||||
//If the raw message starts with '!' (skip commands that start with whitespace so people can send example commands in chat)
|
||||
if(commandObj.rawData.msg[0] == '!'){
|
||||
//if it isn't just an exclimation point, and we have a real command
|
||||
if(commandObj.argumentArray != null){
|
||||
if(this.commandProcessor[commandObj.argumentArray[0].toLowerCase()] != null){
|
||||
//Process the command and use the return value to set the sendflag (true if command valid)
|
||||
commandObj.sendFlag = await this.commandProcessor[commandObj.argumentArray[0].toLowerCase()](commandObj);
|
||||
}else{
|
||||
//Process as toke command if we didnt get a match from the standard server-side command processor
|
||||
commandObj.sendFlag = await this.tokebot.tokeProcessor(commandObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async markLinks(commandObj){
|
||||
//Setup the links array
|
||||
commandObj.links = [];
|
||||
|
||||
//For each link sent from the client
|
||||
//this.rawData.links.forEach((link) => {
|
||||
for (const link of commandObj.rawData.links){
|
||||
//Add a marked link object to our links array
|
||||
commandObj.links.push(await linkUtils.markLink(link));
|
||||
}
|
||||
}
|
||||
|
||||
async prepMessage(commandObj){
|
||||
//Create message from commandArray
|
||||
commandObj.message = commandObj.commandArray.join('').trimStart();
|
||||
//Validate links and mark them by embed type
|
||||
await this.markLinks(commandObj);
|
||||
}
|
||||
|
||||
sendChat(commandObj){
|
||||
//FUCKIN' SEND IT!
|
||||
this.chatHandler.relayUserChat(commandObj.socket, commandObj.message, commandObj.chatType, commandObj.links);
|
||||
}
|
||||
}
|
||||
|
||||
class commandProcessor{
|
||||
constructor(server, chatHandler){
|
||||
this.server = server;
|
||||
this.chatHandler = chatHandler;
|
||||
}
|
||||
|
||||
//Command keywords get run through .toLowerCase(), so we should use lowercase method names for command methods
|
||||
whisper(preprocessor){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a whisper
|
||||
preprocessor.chatType = 'whisper';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
spoiler(preprocessor){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
preprocessor.chatType = 'spoiler';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
strikethrough(preprocessor){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
preprocessor.chatType = 'strikethrough';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
bold(preprocessor){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
preprocessor.chatType = 'bold';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
italics(preprocessor){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
preprocessor.chatType = 'italics';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
async announce(preprocessor){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: preprocessor.socket.chan});
|
||||
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(chanDB != null && await chanDB.permCheck(preprocessor.socket.user, 'announce')){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Prep the message using pre-processor functions chat-handling
|
||||
await preprocessor.prepMessage();
|
||||
|
||||
//send it
|
||||
this.chatHandler.relayChannelAnnouncement(preprocessor.socket.chan, preprocessor.message, preprocessor.links);
|
||||
|
||||
//throw send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
|
||||
async serverannounce(preprocessor){
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(await permissionModel.permCheck(preprocessor.socket.user, 'announce')){
|
||||
//splice out our command
|
||||
preprocessor.commandArray.splice(0,2);
|
||||
|
||||
//Prep the message using pre-processor functions for chat-handling
|
||||
await preprocessor.prepMessage();
|
||||
|
||||
//send it
|
||||
this.chatHandler.relayServerAnnouncement(preprocessor.message, preprocessor.links);
|
||||
|
||||
//throw send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
|
||||
async clear(preprocessor){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: preprocessor.socket.chan});
|
||||
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(await chanDB.permCheck(preprocessor.socket.user, 'clearChat')){
|
||||
//Send off the command
|
||||
this.chatHandler.clearChat(preprocessor.socket.chan, preprocessor.argumentArray[1]);
|
||||
//throw send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
|
||||
async kick(preprocessor){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: preprocessor.socket.chan});
|
||||
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(await chanDB.permCheck(preprocessor.socket.user, 'kickUser')){
|
||||
//Get username from argument array
|
||||
const username = preprocessor.argumentArray[1];
|
||||
|
||||
//Get channel
|
||||
const channel = this.server.activeChannels.get(preprocessor.socket.chan);
|
||||
|
||||
//get initiator and target user objects
|
||||
const initiator = channel.userList.get(preprocessor.socket.user.user);
|
||||
const target = channel.userList.get(username);
|
||||
|
||||
//get initiator and target override abilities
|
||||
const override = await permissionModel.overrideCheck(preprocessor.socket.user, 'kickUser');
|
||||
const targetOverride = await permissionModel.overrideCheck(target, 'kickUser');
|
||||
|
||||
//If there is no user
|
||||
if(target == null){
|
||||
//silently drop the command
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//If the user is capable of overriding this permission based on site permissions
|
||||
if(override || targetOverride){
|
||||
//If the site rank is equal
|
||||
if(permissionModel.rankToNum(initiator.rank) == permissionModel.rankToNum(target.rank)){
|
||||
//compare chan rank
|
||||
if(permissionModel.rankToNum(initiator.chanRank) <= permissionModel.rankToNum(target.chanRank)){
|
||||
//shame the person running it
|
||||
return true;
|
||||
}
|
||||
//otherwise
|
||||
}else{
|
||||
//compare site rank
|
||||
if(permissionModel.rankToNum(initiator.rank) <= permissionModel.rankToNum(target.rank)){
|
||||
//shame the person running it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//If the target has a higher chan rank than the initiator
|
||||
if(permissionModel.rankToNum(initiator.chanRank) <= permissionModel.rankToNum(target.chanRank)){
|
||||
//shame the person running it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Splice out kick
|
||||
preprocessor.commandArray.splice(0,4)
|
||||
|
||||
//Get collect reason
|
||||
var reason = preprocessor.commandArray.join('');
|
||||
|
||||
//If no reason was given
|
||||
if(reason == ''){
|
||||
//Fill in a generic reason
|
||||
reason = "You have been kicked from the channel!";
|
||||
}
|
||||
|
||||
//Kick the user
|
||||
target.disconnect(reason);
|
||||
|
||||
//throw send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,294 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//Local Imports
|
||||
const permissionModel = require('../../schemas/permissionSchema');
|
||||
const channelModel = require('../../schemas/channel/channelSchema');
|
||||
|
||||
/**
|
||||
* Class representing global server-side chat/command processing logic
|
||||
*/
|
||||
class commandProcessor{
|
||||
/**
|
||||
* Instantiates a commandProcessor object
|
||||
* @param {channelManager} server - Parent Server Object
|
||||
* @param {chatHandler} chatHandler - Parent Chat Handler Object
|
||||
*/
|
||||
constructor(server, chatHandler){
|
||||
this.server = server;
|
||||
this.chatHandler = chatHandler;
|
||||
}
|
||||
|
||||
//Command keywords get run through .toLowerCase(), so we should use lowercase method names for command methods
|
||||
/**
|
||||
* Command Processor method to handle the '!whisper' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag
|
||||
*/
|
||||
whisper(commandObj){
|
||||
//splice out our command
|
||||
commandObj.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a whisper
|
||||
commandObj.chatType = 'whisper';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!spoiler' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag
|
||||
*/
|
||||
spoiler(commandObj){
|
||||
//splice out our command
|
||||
commandObj.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
commandObj.chatType = 'spoiler';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!strikethrough' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag
|
||||
*/
|
||||
strikethrough(commandObj){
|
||||
//splice out our command
|
||||
commandObj.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
commandObj.chatType = 'strikethrough';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!bold' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag
|
||||
*/
|
||||
bold(commandObj){
|
||||
//splice out our command
|
||||
commandObj.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
commandObj.chatType = 'bold';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!italics' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag
|
||||
*/
|
||||
italics(commandObj){
|
||||
//splice out our command
|
||||
commandObj.commandArray.splice(0,2);
|
||||
|
||||
//Mark out the current message as a spoiler
|
||||
commandObj.chatType = 'italics';
|
||||
|
||||
//Make sure to throw the send flag
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!announce' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
|
||||
*/
|
||||
async announce(commandObj, preprocessor){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
|
||||
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(chanDB != null && await chanDB.permCheck(commandObj.socket.user, 'announce')){
|
||||
//splice out our command
|
||||
commandObj.commandArray.splice(0,2);
|
||||
|
||||
//Prep the message using pre-processor functions chat-handling
|
||||
await preprocessor.prepMessage(commandObj);
|
||||
|
||||
//send it
|
||||
this.chatHandler.relayChannelAnnouncement(commandObj.socket.chan, commandObj.message, commandObj.links);
|
||||
|
||||
//throw send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!serverannounce' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
|
||||
*/
|
||||
async serverannounce(commandObj, preprocessor){
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(await permissionModel.permCheck(commandObj.socket.user, 'announce')){
|
||||
//splice out our command
|
||||
commandObj.commandArray.splice(0,2);
|
||||
|
||||
//Prep the message using pre-processor functions for chat-handling
|
||||
await preprocessor.prepMessage(commandObj);
|
||||
|
||||
//send it
|
||||
this.chatHandler.relayServerAnnouncement(commandObj.message, commandObj.links);
|
||||
|
||||
//disble send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!resettoke' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
|
||||
*/
|
||||
async resettoke(commandObj, preprocessor){
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(await permissionModel.permCheck(commandObj.socket.user, 'resetToke')){
|
||||
//Acknowledge command
|
||||
this.chatHandler.relayTokeWhisper(commandObj.socket, 'Toke cooldown reset.');
|
||||
|
||||
//Tell tokebot to reset the toke
|
||||
preprocessor.tokebot.resetToke();
|
||||
|
||||
//disable send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!clear' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
|
||||
*/
|
||||
async clear(commandObj){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
|
||||
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(await chanDB.permCheck(commandObj.socket.user, 'clearChat')){
|
||||
//Send off the command
|
||||
this.chatHandler.clearChat(commandObj.socket.chan, commandObj.argumentArray[1]);
|
||||
//disable send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Processor method to handle the '!kick' command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
|
||||
*/
|
||||
async kick(commandObj){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
|
||||
|
||||
//Check if the user has permission, and publicly shame them if they don't (lmao)
|
||||
if(await chanDB.permCheck(commandObj.socket.user, 'kickUser')){
|
||||
//Get username from argument array
|
||||
const username = commandObj.argumentArray[1];
|
||||
|
||||
//Get channel
|
||||
const channel = this.server.activeChannels.get(commandObj.socket.chan);
|
||||
|
||||
//get initiator and target user objects
|
||||
const initiator = channel.userList.get(commandObj.socket.user.user);
|
||||
const target = channel.userList.get(username);
|
||||
|
||||
//get initiator and target override abilities
|
||||
const override = await permissionModel.overrideCheck(commandObj.socket.user, 'kickUser');
|
||||
const targetOverride = await permissionModel.overrideCheck(target, 'kickUser');
|
||||
|
||||
//If there is no user
|
||||
if(target == null){
|
||||
//silently drop the command
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//If the user is capable of overriding this permission based on site permissions
|
||||
if(override || targetOverride){
|
||||
//If the site rank is equal
|
||||
if(permissionModel.rankToNum(initiator.rank) == permissionModel.rankToNum(target.rank)){
|
||||
//compare chan rank
|
||||
if(permissionModel.rankToNum(initiator.chanRank) <= permissionModel.rankToNum(target.chanRank)){
|
||||
//shame the person running it
|
||||
return true;
|
||||
}
|
||||
//otherwise
|
||||
}else{
|
||||
//compare site rank
|
||||
if(permissionModel.rankToNum(initiator.rank) <= permissionModel.rankToNum(target.rank)){
|
||||
//shame the person running it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//If the target has a higher chan rank than the initiator
|
||||
if(permissionModel.rankToNum(initiator.chanRank) <= permissionModel.rankToNum(target.chanRank)){
|
||||
//shame the person running it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Splice out kick
|
||||
commandObj.commandArray.splice(0,4)
|
||||
|
||||
//Get collect reason
|
||||
var reason = commandObj.commandArray.join('');
|
||||
|
||||
//If no reason was given
|
||||
if(reason == ''){
|
||||
//Fill in a generic reason
|
||||
reason = "You have been kicked from the channel!";
|
||||
}
|
||||
|
||||
//Kick the user
|
||||
target.disconnect(reason);
|
||||
|
||||
//throw send flag
|
||||
return false;
|
||||
}
|
||||
|
||||
//throw send flag
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = commandProcessor;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -15,89 +15,34 @@ 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 config = require('../../../config.json');
|
||||
const channelModel = require('../../schemas/channel/channelSchema');
|
||||
const permissionModel = require('../../schemas/permissionSchema');
|
||||
const flairModel = require('../../schemas/flairSchema');
|
||||
const emoteModel = require('../../schemas/emoteSchema');
|
||||
const { userModel } = require('../../schemas/user/userSchema');
|
||||
|
||||
/**
|
||||
* Class representing a single user connected to a channel
|
||||
*/
|
||||
class connectedUser{
|
||||
/**
|
||||
* Instantiates a connectedUser object
|
||||
* @param {Mongoose.Document} userDB - User document to re-hydrate user from
|
||||
* @param {PemissionModel.chanRank} chanRank - Enum representing user channel rank
|
||||
* @param {String} - Channel the user is connecting to
|
||||
* @param {Socket} socket - Socket associated with the users connection
|
||||
*/
|
||||
module.exports = class{
|
||||
constructor(userDB, chanRank, channel, socket){
|
||||
/**
|
||||
* User ID Number
|
||||
*/
|
||||
this.id = userDB.id;
|
||||
|
||||
/**
|
||||
* User Name
|
||||
*/
|
||||
this.user = userDB.user;
|
||||
|
||||
/**
|
||||
* User Rank
|
||||
*/
|
||||
this.rank = userDB.rank;
|
||||
|
||||
/**
|
||||
* User High-Level
|
||||
*/
|
||||
this.highLevel = userDB.highLevel;
|
||||
|
||||
/**
|
||||
* Internal Database Identifier for the user
|
||||
*/
|
||||
this._id = userDB._id;
|
||||
|
||||
//Check to make sure users flair entry from DB is good
|
||||
if(userDB.flair != null){
|
||||
//Set flair from DB
|
||||
/**
|
||||
* User Flair
|
||||
*/
|
||||
//Use flair from DB
|
||||
this.flair = userDB.flair.name;
|
||||
//Otherwise
|
||||
}else{
|
||||
//Gracefully default to classic
|
||||
/**
|
||||
* User Flair
|
||||
*/
|
||||
this.flair = 'classic';
|
||||
}
|
||||
|
||||
/**
|
||||
* User Channel-Rank
|
||||
*/
|
||||
this.chanRank = chanRank;
|
||||
|
||||
/**
|
||||
* Connected Channel
|
||||
*/
|
||||
this.channel = channel;
|
||||
|
||||
/**
|
||||
* List of active sockets to current channel
|
||||
*/
|
||||
this.sockets = [socket.id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles server-side initialization for new connections from a specific user
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
* @returns {activeUser} active user object generated by the new connection
|
||||
*/
|
||||
async handleConnection(userDB, chanDB, socket){
|
||||
//send metadata to client
|
||||
this.sendClientMetadata();
|
||||
|
|
@ -113,24 +58,10 @@ class connectedUser{
|
|||
//Send out the currently playing item
|
||||
this.channel.queue.sendMedia(socket);
|
||||
|
||||
//If we're proxied
|
||||
if(config.proxied){
|
||||
//Tattoo hashed IP address from reverse proxy to user account for seven days
|
||||
await userDB.tattooIPRecord(socket.handshake.headers['x-forwarded-for']);
|
||||
}else{
|
||||
//Tattoo hashed IP address to user account for seven days
|
||||
await userDB.tattooIPRecord(socket.handshake.address);
|
||||
}
|
||||
|
||||
|
||||
//Return active user object for use by activeChannel and channelManager objects
|
||||
return this;
|
||||
//Tattoo hashed IP address to user account for seven days
|
||||
await userDB.tattooIPRecord(socket.handshake.address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through all known connections for a given user, running them through a supplied callback function
|
||||
* @param {Function} cb - Callback to call against found sockets for a given user
|
||||
*/
|
||||
socketCrawl(cb){
|
||||
//Crawl through user's sockets (lol)
|
||||
this.sockets.forEach((sockid) => {
|
||||
|
|
@ -141,33 +72,22 @@ class connectedUser{
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Emits an event to all known sockets for a given user
|
||||
*
|
||||
* My brain keeps going back to using dynamic per-user namespaces for this
|
||||
* but everytime i look into it I come to the conclusion that it's a bad idea, then I toy with making chans namespaces
|
||||
* and using per-user channels for this, but what of gold or mod-only features? or games?
|
||||
* No matter what it'd probably end up hacky, as namespaces where meant for splitting app logic not user comms (like rooms).
|
||||
* at the end of the day there has to be some penance for decent multi-session handling on-top of a library that doesn't do it.
|
||||
* Having to crawl through these sockets is that. Because the other ways seem more gross somehow.
|
||||
* @param {String} eventName - Event name to emit to client sockets
|
||||
* @param {Object} data - Data to emit to client sockets
|
||||
*/
|
||||
emit(eventName, data){
|
||||
//My brain keeps going back to using dynamic per-user namespaces for this
|
||||
//but everytime i look into it I come to the conclusion that it's a bad idea, then I toy with making chans namespaces
|
||||
//and using per-user channels for this, but what of gold or mod-only features? or games?
|
||||
//No matter what it'd probably end up hacky, as namespaces where meant for splitting app logic not user comms (like rooms).
|
||||
//at the end of the day there has to be some penance for decent multi-session handling on-top of a library that doesn't do it.
|
||||
//Having to crawl through these sockets is that. Because the other ways seem more gross somehow.
|
||||
emit(eventName, args){
|
||||
this.socketCrawl((socket)=>{
|
||||
//Ensure our socket is initialized
|
||||
if(socket != null){
|
||||
socket.emit(eventName, data);
|
||||
socket.emit(eventName, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects all sockets for a given user
|
||||
* @param {String} reason - Reason for being disconnected
|
||||
* @param {String} type - Disconnection Type
|
||||
*/
|
||||
//generic disconnect function, defaults to kick
|
||||
disconnect(reason, type = "Disconnected"){
|
||||
this.emit("kick",{type, reason});
|
||||
this.socketCrawl((socket)=>{socket.disconnect()});
|
||||
|
|
@ -175,11 +95,6 @@ class connectedUser{
|
|||
|
||||
//This is the big first push upon connection
|
||||
//It should only fire once, so things that only need to be sent once can be slapped into here
|
||||
/**
|
||||
* Sends glut of required initial metadata to the client upon a new connection
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async sendClientMetadata(userDB, chanDB){
|
||||
//Get flairList from DB and setup flairList array
|
||||
const flairListDB = await flairModel.find({});
|
||||
|
|
@ -231,19 +146,16 @@ class connectedUser{
|
|||
}
|
||||
});
|
||||
|
||||
//Get schedule as a temporary array
|
||||
const queue = await this.channel.queue.prepQueue(chanDB);
|
||||
|
||||
//Get schedule lock status
|
||||
const queueLock = this.channel.queue.locked;
|
||||
|
||||
//Get chat buffer
|
||||
const chatBuffer = this.channel.chatBuffer.buffer;
|
||||
|
||||
//Send off the metadata to our user's clients
|
||||
this.emit("clientMetadata", {user: userObj, flairList, queueLock, chatBuffer});
|
||||
this.emit("clientMetadata", {user: userObj, flairList, queue, queueLock});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send copy of site emotes to the user
|
||||
*/
|
||||
async sendSiteEmotes(){
|
||||
//Get emote list from DB
|
||||
const emoteList = await emoteModel.getEmotes();
|
||||
|
|
@ -252,10 +164,6 @@ class connectedUser{
|
|||
this.emit('siteEmotes', emoteList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send copy of channel emotes to the user
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async sendChanEmotes(chanDB){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
|
|
@ -270,10 +178,6 @@ class connectedUser{
|
|||
this.emit('chanEmotes', emoteList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send copy of channel emotes to the user
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
*/
|
||||
async sendPersonalEmotes(userDB){
|
||||
//if we wherent handed a user document
|
||||
if(userDB == null){
|
||||
|
|
@ -288,10 +192,6 @@ class connectedUser{
|
|||
this.emit('personalEmotes', emoteList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send copy of channel emotes to the user
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
*/
|
||||
async sendUsedTokes(userDB){
|
||||
//if we wherent handed a user document
|
||||
if(userDB == null){
|
||||
|
|
@ -305,10 +205,6 @@ class connectedUser{
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flair for a given user and broadcast update to clients
|
||||
* @param {String} flair - Flair string to update user's flair to
|
||||
*/
|
||||
updateFlair(flair){
|
||||
this.flair = flair;
|
||||
|
||||
|
|
@ -316,10 +212,6 @@ class connectedUser{
|
|||
this.sendClientMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set high level for a given user and broadcast update to clients
|
||||
* @param {Number} highLevel - Number to update user's high-level to
|
||||
*/
|
||||
updateHighLevel(highLevel){
|
||||
this.highLevel = highLevel;
|
||||
|
||||
|
|
@ -327,6 +219,4 @@ class connectedUser{
|
|||
this.channel.broadcastUserList();
|
||||
this.sendClientMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connectedUser;
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//Local Imports
|
||||
const media = require('./media');
|
||||
const queuedMedia = require('./queuedMedia');
|
||||
|
||||
/**
|
||||
* Class extending media which represents a queued piece of media
|
||||
* @extends media
|
||||
*/
|
||||
class archivedMedia extends queuedMedia{
|
||||
/**
|
||||
* Creates a new queued media object
|
||||
* @param {String} channel - Channel where object was queued
|
||||
*/
|
||||
constructor(title, fileName, url, id, type, duration, rawLink, startTime, startTimeStamp = 0, earlyEnd, uuid, channel){
|
||||
//Call derived constructor
|
||||
super(title, fileName, url, id, type, duration, rawLink, startTime, startTimeStamp, earlyEnd, uuid);
|
||||
|
||||
/**
|
||||
* Channel media was queued
|
||||
*/
|
||||
this.channel = channel;
|
||||
|
||||
/**
|
||||
* Media status type
|
||||
*/
|
||||
this.status = 'archived';
|
||||
}
|
||||
|
||||
//statics
|
||||
/**
|
||||
* Creates a archivedMedia object from a media object
|
||||
* @param {String} channel - Channel where object was queued
|
||||
* @returns {archivedMedia} queuedMedia object created from given media object
|
||||
*/
|
||||
static fromMedia(media, startTime, startTimeStamp, channel){
|
||||
//Create and return queuedMedia object from given media object and arguments
|
||||
return new this(
|
||||
media.title,
|
||||
media.fileName,
|
||||
media.url,
|
||||
media.id,
|
||||
media.type,
|
||||
media.duration,
|
||||
media.rawLink,
|
||||
startTime,
|
||||
startTimeStamp,
|
||||
null,
|
||||
null,
|
||||
channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a archivedMedia object from a queuedMedia object
|
||||
* @param {String} channel - Channel where object was queued
|
||||
* @returns {archivedMedia} queuedMedia object created from given media object
|
||||
*/
|
||||
static fromQueuedMedia(media, channel){
|
||||
//Create and return queuedMedia object from given media object and arguments
|
||||
return new this(
|
||||
media.title,
|
||||
media.fileName,
|
||||
media.url,
|
||||
media.id,
|
||||
media.type,
|
||||
media.duration,
|
||||
media.rawLink,
|
||||
media.startTime,
|
||||
media.startTimeStamp,
|
||||
media.earlyEnd,
|
||||
media.uuid,
|
||||
channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts array of media objects into array of archivedMedia objects
|
||||
* @param {String} channel - Channel where object was queued
|
||||
* @returns Array of converted queued media objects
|
||||
*/
|
||||
static fromMediaArray(mediaList, start, channel){
|
||||
//Queued Media List
|
||||
const archivedMediaList = [];
|
||||
//Start Time Offset
|
||||
let startOffset = 0;
|
||||
|
||||
for(let media of mediaList){
|
||||
//Convert mediaObj to queuedMedia and push to the back of the list
|
||||
archivedMediaList.push(this.fromMedia(media, start + startOffset, 0, channel));
|
||||
|
||||
//Set start offset to end of the current item
|
||||
startOffset += (media.duration * 1000) + 5;
|
||||
}
|
||||
|
||||
return archivedMediaList;
|
||||
}
|
||||
|
||||
//methods
|
||||
/**
|
||||
* Generates a unique clone of a given media object
|
||||
* @returns unique clone of media object
|
||||
*/
|
||||
clone(){
|
||||
return new archivedMedia(
|
||||
this.title,
|
||||
this.fileName,
|
||||
this.url,
|
||||
this.id,
|
||||
this.type,
|
||||
this.duration,
|
||||
this.rawLink,
|
||||
this.startTime,
|
||||
this.startTimeStamp,
|
||||
this.earlyEnd,
|
||||
null,
|
||||
this.channel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = archivedMedia;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -14,64 +14,16 @@ 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/>.*/
|
||||
|
||||
/**
|
||||
* Object representing a piece of media
|
||||
*/
|
||||
class media{
|
||||
/**
|
||||
* Creates a new media object from scraped information
|
||||
* @param {String} title - Chosen title of media
|
||||
* @param {String} fileName - Original filename/title of media provided by source
|
||||
* @param {String} url - Original URL to file
|
||||
* @param {String} id - Video ID from source (IE: youtube watch code/archive.org file path)
|
||||
* @param {String} type - Original video source
|
||||
* @param {Number} duration - Length of media in seconds
|
||||
* @param {String} rawLink - URLs to raw file copies of media, not applicable to all sources, not saved to the DB
|
||||
*/
|
||||
constructor(title, fileName, url, id, type, duration, rawLink){
|
||||
/**
|
||||
* Chosen title of media
|
||||
*/
|
||||
//Node imports
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
module.exports = class{
|
||||
constructor(title, fileName, url, id, type, duration){
|
||||
this.title = title;
|
||||
|
||||
/**
|
||||
* Original filename/title of media provided by source
|
||||
*/
|
||||
this.fileName = fileName
|
||||
|
||||
/**
|
||||
* Original URL to file
|
||||
*/
|
||||
this.url = url;
|
||||
|
||||
/**
|
||||
* Video ID from source (IE: youtube watch code/archive.org file path)
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* Original video source
|
||||
*/
|
||||
this.type = type;
|
||||
|
||||
/**
|
||||
* Length of media in seconds
|
||||
*/
|
||||
this.duration = duration;
|
||||
|
||||
if(rawLink == null){
|
||||
/**
|
||||
* URL to raw file copy of media, not applicable to all sources
|
||||
*/
|
||||
this.rawLink = {
|
||||
audio: [],
|
||||
video: [],
|
||||
combo: [['default',url]]
|
||||
};
|
||||
}else{
|
||||
this.rawLink = rawLink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = media;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,94 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//local includes
|
||||
const auxServer = require("../../auxServer");
|
||||
const socketUtils = require("../../../utils/socketUtils")
|
||||
const loggerUtils = require("../../../utils/loggerUtils");
|
||||
const channelModel = require("../../../schemas/channel/channelSchema");
|
||||
|
||||
/**
|
||||
* Class containg per-channel server-side queue broadcasting logic
|
||||
*
|
||||
* Exists to make broadcasting channel queues to groups of authenticated users with the 'read-queue' perm as painless as possible,
|
||||
* reducing DB call/perm checks to just connection time, and not requireing any out-of-library user iteration at broadcast time.
|
||||
*
|
||||
* Calls to modify and write to the schedule are still handled by the main namespace
|
||||
* This is both for it's ease of access to the rest of the channel logic, but also to keep this class as small as possible.
|
||||
*/
|
||||
class queueBroadcastManager extends auxServer{
|
||||
/**
|
||||
* Instantiates object containing global server-side channel schedule broadcasting subsystem
|
||||
* @param {Socket.io} io - Socket.io server instanced passed down from server.js
|
||||
* @param {channelManager} chanServer - Sister channel management server object
|
||||
*/
|
||||
constructor(io, chanServer){
|
||||
super(io, chanServer, "/queue-broadcast");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles global server-side initialization for new connections to the queue broadcast subsystem
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
*/
|
||||
async handleConnection(socket){
|
||||
//Check if we're properly authorized
|
||||
const userObj = await super.handleConnection(socket);
|
||||
|
||||
//If we're un-authorized
|
||||
if(userObj == null){
|
||||
//Drop the connection
|
||||
return;
|
||||
}
|
||||
|
||||
//Set socket channel value
|
||||
socket.chan = socketUtils.getChannelName(socket);
|
||||
//Pull active channel
|
||||
const activeChannel = this.chanServer.activeChannels.get(socket.chan);
|
||||
|
||||
//If there isn't an active channel
|
||||
if(activeChannel == null){
|
||||
//Drop the connection
|
||||
return;
|
||||
}
|
||||
|
||||
//Pull channel DB
|
||||
const chanDB = (await channelModel.findOne({name: socket.chan}));
|
||||
|
||||
//If the user is connecting from an invalid channel
|
||||
if(chanDB == null){
|
||||
//Drop the connection
|
||||
return;
|
||||
}
|
||||
|
||||
//If the user is allowed to read the schedule
|
||||
if(await chanDB.permCheck(socket.user, 'readSchedule')){
|
||||
//Throw the user into the channels room within the queue-broadcast instance
|
||||
socket.join(socket.chan);
|
||||
|
||||
//Send the queue down to our newly connected user
|
||||
activeChannel.queue.emitQueue(chanDB, socket);
|
||||
|
||||
//Define listeners
|
||||
this.defineListeners(socket);
|
||||
}
|
||||
}
|
||||
|
||||
defineListeners(socket){
|
||||
super.defineListeners(socket);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = queueBroadcastManager;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,40 +17,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//Local Imports
|
||||
const media = require('./media');
|
||||
|
||||
/**
|
||||
* Class extending media which represents a queued piece of media
|
||||
* @extends media
|
||||
*/
|
||||
class queuedMedia extends media{
|
||||
/**
|
||||
* Creates a new queued media object
|
||||
* @param {Number} startTime - JS Epoch representing start time
|
||||
* @param {Number} startTimeStamp - Media start time stamp in seconds (relative to duration)
|
||||
* @param {Number} earlyEnd - Media end timestamp in seconds (relative to duration)
|
||||
* @param {String} uuid - Media object's unique identifier
|
||||
*/
|
||||
constructor(title, fileName, url, id, type, duration, rawLink, startTime, startTimeStamp = 0, earlyEnd, uuid){
|
||||
module.exports = class extends media{
|
||||
constructor(title, fileName, url, id, type, duration, startTime, startTimeStamp, earlyEnd, uuid){
|
||||
//Call derived constructor
|
||||
super(title, fileName, url, id, type, duration, rawLink);
|
||||
|
||||
/**
|
||||
* JS Epoch (millis) representing start time
|
||||
*/
|
||||
super(title, fileName, url, id, type, duration);
|
||||
//Set media start time
|
||||
this.startTime = startTime;
|
||||
|
||||
/**
|
||||
* Media start time stamp in seconds (relative to duration)
|
||||
*/
|
||||
//Set the media start time stamp
|
||||
this.startTimeStamp = startTimeStamp;
|
||||
|
||||
/**
|
||||
* Media ent timestamp in seconds (relative to duration)
|
||||
*/
|
||||
//Create empty variable to hold early end if media is stopped early
|
||||
this.earlyEnd = earlyEnd;
|
||||
|
||||
/**
|
||||
* Media status type
|
||||
*/
|
||||
//Set status for discriminator key
|
||||
this.status = 'queued';
|
||||
|
||||
//If we have a null uuid (can't use default argument because of 'this')
|
||||
|
|
@ -59,21 +36,11 @@ class queuedMedia extends media{
|
|||
//That way even if we have six copies of the same video queued, we can still uniquely idenitify each instance
|
||||
this.genUUID();
|
||||
}else{
|
||||
/**
|
||||
* Media object's unique identifier
|
||||
*/
|
||||
this.uuid = uuid;
|
||||
}
|
||||
}
|
||||
|
||||
//statics
|
||||
/**
|
||||
* Creates a queuedMedia object from a media object
|
||||
* @param {media} media - Media object to queue
|
||||
* @param {Number} startTime - Start time formatted as a JS Epoch
|
||||
* @param {Number} startTimeStamp - Start time stamp in seconds
|
||||
* @returns {queuedMedia} queuedMedia object created from given media object
|
||||
*/
|
||||
static fromMedia(media, startTime, startTimeStamp){
|
||||
//Create and return queuedMedia object from given media object and arguments
|
||||
return new this(
|
||||
|
|
@ -83,38 +50,10 @@ class queuedMedia extends media{
|
|||
media.id,
|
||||
media.type,
|
||||
media.duration,
|
||||
media.rawLink,
|
||||
startTime,
|
||||
startTimeStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a queuedMedia object from an archivedMedia object
|
||||
* @param {String} channel - Channel where object was queued
|
||||
* @returns {archivedMedia} queuedMedia object created from given media object
|
||||
*/
|
||||
static fromArchivedMedia(media){
|
||||
//Create and return queuedMedia object from given media object and arguments
|
||||
return new this(
|
||||
media.title,
|
||||
media.fileName,
|
||||
media.url,
|
||||
media.id,
|
||||
media.type,
|
||||
media.duration,
|
||||
media.rawLink,
|
||||
media.startTime,
|
||||
media.startTimeStamp,
|
||||
media.earlyEnd,
|
||||
media.uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts array of media objects into array of queuedMedia objects
|
||||
* @param {Array} mediaList - Array of media objects to queue
|
||||
* @param {Number} start - Start time formatted as JS Epoch
|
||||
* @returns Array of converted queued media objects
|
||||
*/
|
||||
static fromMediaArray(mediaList, start){
|
||||
//Queued Media List
|
||||
const queuedMediaList = [];
|
||||
|
|
@ -133,40 +72,13 @@ class queuedMedia extends media{
|
|||
}
|
||||
|
||||
//methods
|
||||
/**
|
||||
* Generates new unique identifier for queued media
|
||||
*/
|
||||
genUUID(){
|
||||
this.uuid = crypto.randomUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique clone of a given media object
|
||||
* @returns unique clone of media object
|
||||
*/
|
||||
clone(){
|
||||
return new queuedMedia(
|
||||
this.title,
|
||||
this.fileName,
|
||||
this.url,
|
||||
this.id,
|
||||
this.type,
|
||||
this.duration,
|
||||
this.rawLink,
|
||||
this.startTime,
|
||||
this.startTimeStamp,
|
||||
this.earlyEnd
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* return the end time of a given queuedMedia object
|
||||
* @param {boolean} fullTime - Overrides early ends
|
||||
* @returns end time of given queuedMedia object
|
||||
*/
|
||||
getEndTime(fullTime = false){
|
||||
getEndTime(){
|
||||
//If we have an early ending
|
||||
if(this.earlyEnd == null || fullTime){
|
||||
if(this.earlyEnd == null){
|
||||
//Calculate our ending
|
||||
return this.startTime + ((this.duration - this.startTimeStamp) * 1000);
|
||||
}else{
|
||||
|
|
@ -174,6 +86,4 @@ class queuedMedia extends media{
|
|||
return this.startTime + (this.earlyEnd * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = queuedMedia;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -16,102 +16,46 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
|
||||
//Local Imports
|
||||
const tokeCommandModel = require('../../schemas/tokebot/tokeCommandSchema');
|
||||
const tokeModel = require('../../schemas/tokebot/tokeSchema');
|
||||
const {userModel} = require('../../schemas/user/userSchema');
|
||||
const statModel = require('../../schemas/statSchema');
|
||||
const statSchema = require('../../schemas/statSchema');
|
||||
|
||||
|
||||
/**
|
||||
* Class containing global server-side tokebot logic
|
||||
*/
|
||||
class tokebot{
|
||||
/**
|
||||
* Instantiates a tokebot object
|
||||
* @param {channelManager} server - Parent Server Object
|
||||
* @param {chatHandler} chatHandler - Parent Chat Handler Object
|
||||
*/
|
||||
module.exports = class tokebot{
|
||||
constructor(server, chatHandler){
|
||||
/**
|
||||
* Parent Server Object
|
||||
*/
|
||||
//Set parents
|
||||
this.server = server;
|
||||
|
||||
/**
|
||||
* Parent Chat Handler
|
||||
*/
|
||||
this.chatHandler = chatHandler;
|
||||
|
||||
/**
|
||||
* Toke Timer
|
||||
*/
|
||||
//Set timeouts to null
|
||||
this.tokeTimer = null;
|
||||
|
||||
/**
|
||||
* Cooldown Timer
|
||||
*/
|
||||
this.cooldownTimer = null;
|
||||
|
||||
/**
|
||||
* Toke time
|
||||
*/
|
||||
//Set start times
|
||||
this.tokeTime = 60;
|
||||
|
||||
/**
|
||||
* Cooldown Time
|
||||
*/
|
||||
this.cooldownTime = 120;
|
||||
|
||||
/**
|
||||
* Toke Counter
|
||||
*/
|
||||
//Create counter variable
|
||||
this.tokeCounter = 0;
|
||||
|
||||
/**
|
||||
* Cooldown Counter
|
||||
*/
|
||||
this.cooldownCounter = 0;
|
||||
|
||||
/**
|
||||
* List of current tokers
|
||||
*/
|
||||
//Create tokers list
|
||||
this.tokers = new Map();
|
||||
|
||||
//Load in toke commands from the DB
|
||||
this.refreshCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads toke commands from DB into RAM-based toke command store
|
||||
*/
|
||||
async refreshCommands(){
|
||||
//Pull Command Strings from DB
|
||||
this.tokeCommands = await tokeCommandModel.getCommandStrings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes toke commands from Command Pre-Processor
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request, passed down from the Command Pre-Processor
|
||||
* @returns {Boolean} True if the toke is an invalid toke command (tells Command Pre-Processor to send command as chat)
|
||||
*/
|
||||
tokeProcessor(commandObj){
|
||||
//Check for site-wide toke commands
|
||||
if(this.tokeCommands.indexOf(commandObj.argumentArray[0].toLowerCase()) != -1){
|
||||
//Seems lame to set a bool in an if statement but this would've made a really ugly turinary
|
||||
var foundToke = true;
|
||||
}else if(commandObj.argumentArray[0].toLowerCase() == 'r'){
|
||||
//Find the users active channel
|
||||
const activeChan = this.server.activeChannels.get(commandObj.socket.chan);
|
||||
|
||||
//Combile site-wide and channel tokes into one list
|
||||
const tokeList = this.tokeCommands.concat(activeChan.tokeCommands);
|
||||
|
||||
//Pick a random number between 0 and one less than the number of tokes
|
||||
const foundIndex = Math.round(Math.random() * (tokeList.length - 1));
|
||||
|
||||
//Set override command argument 0 w/ the found toke
|
||||
commandObj.argumentArray[0] = tokeList[foundIndex];
|
||||
|
||||
//throw toke flag
|
||||
var foundToke = true;
|
||||
}else{
|
||||
//Find the users active channel
|
||||
const activeChan = this.server.activeChannels.get(commandObj.socket.chan);
|
||||
|
|
@ -148,15 +92,6 @@ class tokebot{
|
|||
|
||||
//Add the toking user to the tokers map
|
||||
this.tokers.set(commandObj.socket.user.user, commandObj.argumentArray[0].toLowerCase());
|
||||
|
||||
if(this.tokeCounter <= 3){
|
||||
//Drop the toke timer
|
||||
clearTimeout(this.tokeTimer);
|
||||
//Roll the toke counter back to 3
|
||||
this.tokeCounter = 3;
|
||||
//Re-start the toke timer
|
||||
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000);
|
||||
}
|
||||
//If the user is already in the toke
|
||||
}else{
|
||||
//Tell them to fuck off
|
||||
|
|
@ -170,16 +105,13 @@ class tokebot{
|
|||
}
|
||||
|
||||
//Toke command found, and there isn't any extra text, don't send as chat (re-create fore.st tokebot behaviour)
|
||||
return (commandObj.command != `!${commandObj.argumentArray[0]}` && commandObj.command != '!r');
|
||||
return (commandObj.command != `!${commandObj.argumentArray[0]}`)
|
||||
}else{
|
||||
//No toke found, send it down the line, because shaming the user is funny
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called each second during the toke. Handles decrementing the timer variable, and countdown end logic.
|
||||
*/
|
||||
countdown(){
|
||||
//If we're in the last three seconds
|
||||
if(this.tokeCounter <= 3 && this.tokeCounter > 0){
|
||||
|
|
@ -199,8 +131,8 @@ class tokebot{
|
|||
|
||||
//Asynchronously tattoo the toke into the users documents within the database so that tokebot doesn't have to wait or worry about DB transactions
|
||||
userModel.tattooToke(this.tokers);
|
||||
//Do the same for the global toke statistics collection
|
||||
tokeModel.tattooToke(this.tokers);
|
||||
//Do the same for the global stat schema
|
||||
statSchema.tattooToke(this.tokers);
|
||||
|
||||
//Set the toke cooldown
|
||||
this.cooldownCounter = this.cooldownTime;
|
||||
|
|
@ -219,13 +151,9 @@ class tokebot{
|
|||
//Decrement toke time
|
||||
this.tokeCounter--;
|
||||
//try again in another second
|
||||
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000);
|
||||
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method seems to be a vestage from a bygone era. We should remove it after documenting shit.
|
||||
* I would now, but I don't want to break shit in a comment-only commit.
|
||||
*/
|
||||
async asyncFinisher(){
|
||||
//Grab a copy of the tokers map before it gets cleared out
|
||||
const tokers = this.tokers;
|
||||
|
|
@ -234,9 +162,6 @@ class tokebot{
|
|||
await userModel.tattooToke(tokers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs every second for 60 seconds after a toke
|
||||
*/
|
||||
cooldown(){
|
||||
//If the cooldown timer isn't over
|
||||
if(this.cooldownCounter > 0){
|
||||
|
|
@ -251,17 +176,4 @@ class tokebot{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets toke cooldowns early upon authorized request
|
||||
*/
|
||||
resetToke(){
|
||||
//Set cooldown to 0
|
||||
this.cooldownCounter = 0;
|
||||
|
||||
//Null out the timer
|
||||
this.cooldownTimer = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = tokebot;
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
/**
|
||||
* Class representing a the metadata of a single message
|
||||
*/
|
||||
class chatMetadata{
|
||||
/**
|
||||
* Instantiates a chat metadata object
|
||||
* @param {String} user - Name of user who sent the message
|
||||
* @param {String} flair - Flair ID String for the flair used to send the message
|
||||
* @param {Number} highLevel - Number representing current high level
|
||||
* @param {String} msg - Contents of the message, with links replaced with numbered file-seperator markers
|
||||
* @param {String} type - Message Type Identifier, used for client-side processing.
|
||||
* @param {Array} links - Array of URLs/Links included in the message.
|
||||
*/
|
||||
constructor(user, flair, highLevel, msg, type, links){
|
||||
/**
|
||||
* Name of user who sent the message
|
||||
*/
|
||||
this.user = user;
|
||||
|
||||
/**
|
||||
* Flair ID String for the flair used to send the message
|
||||
*/
|
||||
this.flair = flair;
|
||||
|
||||
/**
|
||||
* Number representing current high level
|
||||
*/
|
||||
this.highLevel = highLevel;
|
||||
|
||||
/**
|
||||
* COntents of the message, with links replaced with numbered file-seperator marks
|
||||
*/
|
||||
this.msg = msg;
|
||||
|
||||
/**
|
||||
* Message Type Identifier, used for client-side processing.
|
||||
*/
|
||||
this.type = type;
|
||||
|
||||
/**
|
||||
* Array of URLs/Links included in the message.
|
||||
*/
|
||||
this.links = links;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = chatMetadata;
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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 validator = require('validator');//No express here, so regular validator it is!
|
||||
|
||||
//Local Imports
|
||||
const linkUtils = require('../utils/linkUtils');
|
||||
const commandProcessor = require('./channel/commandProcessor');
|
||||
|
||||
/**
|
||||
* Class containing global server-side chat/command pre-processing logic
|
||||
*/
|
||||
class chatPreprocessor{
|
||||
/**
|
||||
* Instantiates a commandPreprocessor object
|
||||
* @param {commandProcessor} - Child Command Processor Object. Contains functions named after commands.
|
||||
*/
|
||||
constructor(commandProcessor, tokebot){
|
||||
/**
|
||||
* Child Command Processor Object. Contains functions named after commands.
|
||||
*/
|
||||
this.commandProcessor = commandProcessor;
|
||||
|
||||
/**
|
||||
* Child Tokebot Object
|
||||
*/
|
||||
this.tokebot = tokebot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ingests a command/chat request from Chat Handler and pre-processes and processes it accordingly
|
||||
* @param {Socket} socket - Socket we're receiving the request from
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async preprocess(socket, data){
|
||||
//Set command object
|
||||
const commandObj = {
|
||||
socket,
|
||||
sendFlag: true,
|
||||
rawData: data,
|
||||
chatType: 'chat'
|
||||
}
|
||||
|
||||
//If we don't pass sanatization/validation turn this car around
|
||||
if(!this.sanatizeCommand(commandObj)){
|
||||
return false;
|
||||
}
|
||||
|
||||
//split the command
|
||||
this.splitCommand(commandObj);
|
||||
|
||||
//Process the command
|
||||
await this.processServerCommand(commandObj);
|
||||
|
||||
//If we're going to relay this command as a message, continue on to chat processing
|
||||
if(commandObj.sendFlag){
|
||||
//Prep the message
|
||||
await this.prepMessage(commandObj);
|
||||
|
||||
//Send the chat
|
||||
return commandObj;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanatizes and Validates a single user chat message/command
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
* @returns {Boolean} false if Command/Message is too long to send
|
||||
*/
|
||||
sanatizeCommand(commandObj){
|
||||
//Trim and Sanatize for XSS
|
||||
commandObj.command = validator.trim(validator.escape(commandObj.rawData.msg));
|
||||
|
||||
//Return whether or not the shit was long enough
|
||||
return (validator.isLength(commandObj.rawData.msg, {min: 1, max: 255}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits raw chat/command data into seperate arrays, one by word-borders and words surrounded by word-borders
|
||||
* These arrays are used to handle further command/chat processing
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
*/
|
||||
splitCommand(commandObj){
|
||||
//Split string by words
|
||||
commandObj.commandArray = commandObj.command.split(/\b/g);//Split by word-borders
|
||||
commandObj.argumentArray = commandObj.command.match(/\b\w+\b/g);//Match by words surrounded by borders
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the server's Command Processor object to process the chat/command request.
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
*/
|
||||
async processServerCommand(commandObj){
|
||||
//If the raw message starts with '!' (skip commands that start with whitespace so people can send example commands in chat)
|
||||
if(commandObj.rawData.msg[0] == '!'){
|
||||
//if it isn't just an exclimation point, and we have a real command
|
||||
if(commandObj.argumentArray != null){
|
||||
//If the command processor knows what to do with whatever the fuck the user sent us
|
||||
if(this.commandProcessor != null && this.commandProcessor[commandObj.argumentArray[0].toLowerCase()] != null){
|
||||
//Process the command and use the return value to set the sendflag (true if command valid)
|
||||
commandObj.sendFlag = await this.commandProcessor[commandObj.argumentArray[0].toLowerCase()](commandObj, this);
|
||||
}else if(this.tokebot != null){
|
||||
//Process as toke command if we didnt get a match from the standard server-side command processor
|
||||
commandObj.sendFlag = await this.tokebot.tokeProcessor(commandObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through links in message and marks them by link type for later use by client-side post-processing
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
*/
|
||||
async markLinks(commandObj){
|
||||
//Setup the links array
|
||||
commandObj.links = [];
|
||||
|
||||
//For each link sent from the client
|
||||
for (const link of commandObj.rawData.links){
|
||||
//Add a marked link object to our links array
|
||||
commandObj.links.push(await linkUtils.markLink(link));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-creates message string from processed Command Array
|
||||
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||
*/
|
||||
async prepMessage(commandObj){
|
||||
//Create message from commandArray
|
||||
commandObj.message = commandObj.commandArray.join('').trimStart();
|
||||
//Validate links and mark them by embed type
|
||||
await this.markLinks(commandObj);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = chatPreprocessor;
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//localImports
|
||||
const chatMetadata = require("../chatMetadata");
|
||||
|
||||
/**
|
||||
* Class representing a single chat message
|
||||
*/
|
||||
class message extends chatMetadata{
|
||||
/**
|
||||
* @param {String} user - Name of user who sent the message
|
||||
* @param {Array} recipients - Array of usernames who are supposed to receive the message
|
||||
*/
|
||||
constructor(user, recipients, flair, highLevel, msg, type, links){
|
||||
//Call derived constructor
|
||||
super(user, flair, highLevel, msg, type, links);
|
||||
|
||||
/**
|
||||
* Array of usernames who are supposed to receive the message
|
||||
*/
|
||||
this.recipients = recipients;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = message;
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//local includes
|
||||
const auxServer = require('../auxServer');
|
||||
const chatPreprocessor = require('../chatPreprocessor');
|
||||
const loggerUtils = require("../../utils/loggerUtils");
|
||||
const message = require("./message");
|
||||
const socketUtils = require("../../utils/socketUtils");
|
||||
|
||||
/**
|
||||
* Class containg global server-side private message relay logic
|
||||
*/
|
||||
class pmHandler extends auxServer{
|
||||
/**
|
||||
* Instantiates object containing global server-side private message relay logic
|
||||
* @param {Socket.io} io - Socket.io server instanced passed down from server.js
|
||||
* @param {channelManager} chanServer - Sister channel management server object
|
||||
*/
|
||||
constructor(io, chanServer){
|
||||
super(io, chanServer, "/pm");
|
||||
|
||||
this.chatPreprocessor = new chatPreprocessor(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles global server-side initialization for new connections to the private messaging system
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
*/
|
||||
async handleConnection(socket){
|
||||
//Check if we're properly authorized
|
||||
const authorized = await super.handleConnection(socket);
|
||||
|
||||
//If we're authorized
|
||||
if(authorized != null){
|
||||
//Throw the user into their own unique channel
|
||||
socket.join(socket.user.user);
|
||||
|
||||
//Define listeners
|
||||
this.defineListeners(socket);
|
||||
}
|
||||
}
|
||||
|
||||
defineListeners(socket){
|
||||
super.defineListeners(socket);
|
||||
socket.on("pm", (data)=>{this.handlePM(data, socket)});
|
||||
}
|
||||
|
||||
async handlePM(data, socket){
|
||||
try{
|
||||
//Check recipients
|
||||
const recipients = this.checkRecipients(data.recipients, socket);
|
||||
|
||||
//If we don't have any valid recipients
|
||||
if(recipients.length <= 0){
|
||||
//Drop that shit
|
||||
return false;
|
||||
}
|
||||
|
||||
//If this is a sesh starter
|
||||
if(data.msg == '' || data.msg == null){
|
||||
//Skip pre-processing and send a cooked message
|
||||
return this.relayPMObj(new message(
|
||||
socket.user.user,
|
||||
recipients,
|
||||
'',
|
||||
'chat',
|
||||
[]
|
||||
));
|
||||
}
|
||||
|
||||
//preprocess message
|
||||
const preprocessedMessage = await this.chatPreprocessor.preprocess(socket, data);
|
||||
|
||||
//If the send flag wasnt thrown false
|
||||
if(preprocessedMessage != false){
|
||||
//Pull an active user profile from the first channel that gives it in the chan server
|
||||
const senderProfile = this.chanServer.activeUsers.get(socket.user.user);
|
||||
|
||||
//If user isn't actively connected to a channel
|
||||
if(senderProfile == null || senderProfile.length == 0){
|
||||
//They don't get to send shit lol
|
||||
return;
|
||||
}
|
||||
|
||||
//Create message object and relay it off to the recipients
|
||||
this.relayPMObj(new message(
|
||||
socket.user.user,
|
||||
recipients,
|
||||
senderProfile[0].flair,
|
||||
senderProfile[0].highLevel,
|
||||
preprocessedMessage.message,
|
||||
preprocessedMessage.chatType,
|
||||
preprocessedMessage.links
|
||||
));
|
||||
}
|
||||
|
||||
//If something fucked up
|
||||
}catch(err){
|
||||
//Bitch and moan
|
||||
return loggerUtils.socketExceptionHandler(socket, err);
|
||||
}
|
||||
}
|
||||
|
||||
relayPMObj(msg){
|
||||
//For each recipient
|
||||
for(let user of msg.recipients){
|
||||
//Send the message
|
||||
this.namespace.to(user).emit("message", msg);
|
||||
}
|
||||
|
||||
//Acknowledge the sent message
|
||||
this.namespace.to(msg.user).emit("sent", msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic function for checking presence
|
||||
* This could be done using Channel Presence, but running off of bare Socket.io functionality makes this easier to implement outside the channel if need be
|
||||
* @param {String} user - Username to check presence of
|
||||
* @returns {Boolean} Whether or not the user is currently able to accept messages
|
||||
*/
|
||||
checkPresence(user){
|
||||
//Pull room map from the guts of socket.io and run a null check against the given username
|
||||
return this.namespace.adapter.rooms.get(user) != null;
|
||||
}
|
||||
|
||||
checkRecipients(input, socket){
|
||||
//Create empty recipients array
|
||||
let recipients = [];
|
||||
|
||||
//For each requested recipient
|
||||
for(let user of input){
|
||||
//If the given user is online and didn't send the message
|
||||
if(this.checkPresence(user) && user != socket.user.user){
|
||||
//Add the recipient to the list
|
||||
recipients.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
//return recipients
|
||||
return recipients;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = pmHandler;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -26,5 +26,5 @@ module.exports = async function(req, res){
|
|||
res.status(404);
|
||||
|
||||
//Render page
|
||||
return res.render('404', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('404', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//Config
|
||||
const config = require('../../config.json');
|
||||
const package = require('../../package.json');
|
||||
|
||||
//Local Imports
|
||||
const csrfUtils = require('../utils/csrfUtils');
|
||||
|
||||
//register page functions
|
||||
module.exports.get = async function(req, res){
|
||||
//Render page
|
||||
return res.render('about', {aboutText: config.aboutText, instance: config.instanceName, links: config.links, user: req.session.user, version: package.canopyDisplayVersion, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,9 +17,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//Config
|
||||
const config = require('../../config.json');
|
||||
|
||||
//NPM Imports
|
||||
const validator = require('validator');//No express here, so regular validator it is!
|
||||
|
||||
//Local Imports
|
||||
const {userModel} = require('../schemas/user/userSchema');
|
||||
const permissionModel = require('../schemas/permissionSchema');
|
||||
|
|
@ -42,14 +39,13 @@ module.exports.get = async function(req, res){
|
|||
|
||||
//Render out the page
|
||||
return res.render('adminPanel', {
|
||||
instance: config.instanceName, links: config.links,
|
||||
instance: config.instanceName,
|
||||
user: req.session.user,
|
||||
rankEnum: permissionModel.rankEnum,
|
||||
chanGuide: chanGuide,
|
||||
userList: userList,
|
||||
permList: permList,
|
||||
csrfToken: csrfUtils.generateToken(req),
|
||||
unescape: validator.unescape
|
||||
csrfToken: csrfUtils.generateToken(req)
|
||||
});
|
||||
|
||||
}catch(err){
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -33,14 +33,17 @@ module.exports.post = async function(req, res){
|
|||
const data = matchedData(req);
|
||||
|
||||
//make sure we're not bullshitting ourselves here.
|
||||
if(user == null || user.user == null){
|
||||
return errorHandler(res, 'You must be logged in to delete your account!', 'unauthorized');
|
||||
if(user == null){
|
||||
res.status(400);
|
||||
return res.send('Invalid Session! Cannot delete account while logged out!');
|
||||
}
|
||||
|
||||
const userDB = await userModel.findOne({user: user.user});
|
||||
const userDB = await userModel.findOne(user);
|
||||
|
||||
|
||||
if(!userDB){
|
||||
return errorHandler(res, 'User not found!', 'unauthorized');
|
||||
res.status(400);
|
||||
return res.send('Invalid User! Account must exist in order to delete!');
|
||||
}
|
||||
|
||||
await userDB.nuke(data.pass);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -38,12 +38,9 @@ module.exports.post = async function(req, res){
|
|||
//Get sanatized/validated data
|
||||
const {email, pass} = matchedData(req);
|
||||
|
||||
//If we're proxied use passthrough IP
|
||||
const ip = config.proxied ? req.headers['x-forwarded-for'] : req.ip;
|
||||
|
||||
//Check to make sure the user is logged in
|
||||
if(req.session.user == null){
|
||||
return errorHandler(res, "Invalid user!");
|
||||
errorHandler(res, "Invalid user!");
|
||||
}
|
||||
|
||||
//Authenticate and find user model from DB
|
||||
|
|
@ -51,26 +48,15 @@ module.exports.post = async function(req, res){
|
|||
|
||||
//If we have an invalid user
|
||||
if(userDB == null){
|
||||
return errorHandler(res, "Invalid user!");
|
||||
errorHandler(res, "Invalid user!");
|
||||
}
|
||||
|
||||
if(userDB.email == email){
|
||||
return errorHandler(res, "Cannot set current email!");
|
||||
}
|
||||
|
||||
|
||||
//Look through DB and migration cache for existing email
|
||||
const existingDB = await userModel.findOne({email: new RegExp(`^${email}$`, 'i')});
|
||||
const needsMigration = userModel.migrationCache.emails.includes(email.toLowerCase());
|
||||
|
||||
//If the email is in use
|
||||
if(existingDB != null || needsMigration){
|
||||
//Complain
|
||||
return errorHandler(res, "Email already in use!");
|
||||
errorHandler(res, "Cannot set current email!");
|
||||
}
|
||||
|
||||
//Generate the password reset link
|
||||
const requestDB = await emailChangeModel.create({user: userDB._id, newEmail: email, ipHash: ip});
|
||||
const requestDB = await emailChangeModel.create({user: userDB._id, newEmail: email, ipHash: req.ip});
|
||||
|
||||
//Don't wait on mailer to get back to the browser
|
||||
res.sendStatus(200);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -21,11 +21,10 @@ const config = require('../../../../config.json');
|
|||
const {validationResult, matchedData} = require('express-validator');
|
||||
|
||||
//local imports
|
||||
const migrationModel = require('../../../schemas/user/migrationSchema.js');
|
||||
const rememberMeModel = require('../../../schemas/user/rememberMeSchema.js');
|
||||
const sessionUtils = require('../../../utils/sessionUtils');
|
||||
const hashUtils = require('../../../utils/hashUtils.js');
|
||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||
const altchaUtils = require('../../../utils/altchaUtils');
|
||||
const session = require('express-session');
|
||||
|
||||
//api account functions
|
||||
module.exports.post = async function(req, res){
|
||||
|
|
@ -36,45 +35,10 @@ module.exports.post = async function(req, res){
|
|||
//if we don't have errors
|
||||
if(validResult.isEmpty()){
|
||||
//Pull sanatzied/validated data
|
||||
const data = matchedData(req);
|
||||
|
||||
//try to authenticate the session, throwing an error and breaking the current code block if user is un-authorized
|
||||
const userDB = await sessionUtils.authenticateSession(data.user, data.pass, req);
|
||||
|
||||
//If the user already has a remember me token
|
||||
if(data.rememberme != null && data.rememberme.id != null){
|
||||
//Fucking nuke the bitch
|
||||
await rememberMeModel.deleteOne({id: data.rememberme.id})
|
||||
|
||||
//Tell the client to drop the token
|
||||
res.clearCookie("rememberme.id");
|
||||
res.clearCookie("rememberme.token");
|
||||
}
|
||||
|
||||
//If the user requested a rememberMe token (I'm not validation checking a fucking boolean)
|
||||
if(req.body.rememberMe){
|
||||
//Gen user token
|
||||
//requires second DB call, but this enforces password requirement for toke generation while ensuring we only
|
||||
//need one function in the userModel for authentication, even if the second woulda just been a wrapper.
|
||||
//Less attack surface is less attack surface, and this isn't something thats going to be getting constantly called
|
||||
const authToken = await rememberMeModel.genToken(userDB, data.pass);
|
||||
|
||||
//If we properly authed
|
||||
if(authToken != null){
|
||||
//Check config for protocol
|
||||
const secure = config.protocol.toLowerCase() == "https";
|
||||
|
||||
//Create expiration date for cookies (180 days)
|
||||
const expires = new Date(Date.now() + (1000 * 60 * 60 * 24 * 180));
|
||||
|
||||
//Set remember me ID and token as browser-side cookies for safe-keeping
|
||||
res.cookie("rememberme.id", authToken.id, {sameSite: 'strict', httpOnly: true, secure, expires});
|
||||
//This should be the servers last interaction with the plaintext token before saving the hashed copy, and dropping it out of RAM
|
||||
res.cookie("rememberme.token", authToken.token, {sameSite: 'strict', httpOnly: true, secure, expires});
|
||||
}
|
||||
}
|
||||
|
||||
//Tell the browser everything is dandy
|
||||
const {user, pass} = matchedData(req);
|
||||
|
||||
//try to authenticate the session, and return a successful code if it works
|
||||
await sessionUtils.authenticateSession(user, pass, req);
|
||||
return res.sendStatus(200);
|
||||
}else{
|
||||
res.status(400);
|
||||
|
|
@ -87,35 +51,21 @@ module.exports.post = async function(req, res){
|
|||
//if we don't have errors
|
||||
if(validResult.isEmpty()){
|
||||
//Get login attempts for current user
|
||||
const {user, pass} = matchedData(req);
|
||||
|
||||
//Look for the username in the migration DB
|
||||
const migrationDB = await migrationModel.findOne({user: new RegExp(`^${user}$`, 'i')});
|
||||
|
||||
//If we found a migration profile
|
||||
if(migrationDB != null){
|
||||
//If the user has a good password
|
||||
if(hashUtils.compareLegacyPassword(pass, migrationDB.pass)){
|
||||
//Redirect to migrate
|
||||
return res.sendStatus(301);
|
||||
}
|
||||
}
|
||||
|
||||
//Get login attempts
|
||||
const {user} = matchedData(req);
|
||||
const attempts = sessionUtils.getLoginAttempts(user)
|
||||
|
||||
//if we've gone over max attempts
|
||||
if(attempts != null && attempts.count > sessionUtils.throttleAttempts){
|
||||
//if we've gone over max attempts and
|
||||
if(attempts.count > sessionUtils.throttleAttempts){
|
||||
//tell client it needs a captcha
|
||||
return res.sendStatus(429);
|
||||
}else{
|
||||
//Scream about any un-caught errors
|
||||
return exceptionHandler(res, err);
|
||||
}
|
||||
}else{
|
||||
res.status(400);
|
||||
return res.send({errors: validResult.array()})
|
||||
}
|
||||
|
||||
//
|
||||
return exceptionHandler(res, err);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -15,36 +15,13 @@ 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 rememberMeModel = require('../../../schemas/user/rememberMeSchema');
|
||||
const sessionUtils = require('../../../utils/sessionUtils');
|
||||
const {exceptionHandler} = require('../../../utils/loggerUtils');
|
||||
const {validationResult, matchedData} = require('express-validator');
|
||||
const accountUtils = require('../../../utils/sessionUtils');
|
||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||
|
||||
module.exports.post = async function(req, res){
|
||||
if(req.session.user){
|
||||
try{
|
||||
sessionUtils.killSession(req.session);
|
||||
|
||||
//Check validation results
|
||||
const validResult = validationResult(req);
|
||||
|
||||
//if we don't have errors
|
||||
if(validResult.isEmpty()){
|
||||
//Pull sanatzied/validated data
|
||||
const data = matchedData(req);
|
||||
|
||||
//If the user has a remember me token id they've submitted with the request
|
||||
if(data.rememberme != null && data.rememberme.id != null){
|
||||
//Find the associated token and nuke it
|
||||
await rememberMeModel.deleteOne({id: data.rememberme.id})
|
||||
}
|
||||
}
|
||||
|
||||
//Clear out remember me tokens
|
||||
res.clearCookie("rememberme.id");
|
||||
res.clearCookie("rememberme.token");
|
||||
|
||||
//Return status
|
||||
accountUtils.killSession(req.session);
|
||||
return res.sendStatus(200);
|
||||
}catch(err){
|
||||
return exceptionHandler(res, err);
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//Config
|
||||
const config = require('../../../../config.json');
|
||||
|
||||
//NPM Imports
|
||||
const {validationResult, matchedData} = require('express-validator');
|
||||
|
||||
//local imports
|
||||
const userBanModel = require('../../../schemas/user/userBanSchema');
|
||||
const altchaUtils = require('../../../utils/altchaUtils');
|
||||
const migrationModel = require('../../../schemas/user/migrationSchema');
|
||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||
|
||||
module.exports.post = async function(req, res){
|
||||
try{
|
||||
//Check for validation errors
|
||||
const validResult = validationResult(req);
|
||||
|
||||
//If there are none
|
||||
if(validResult.isEmpty()){
|
||||
//Get sanatized/validated data
|
||||
const migration = matchedData(req);
|
||||
//Verify Altcha Payload
|
||||
const verified = await altchaUtils.verify(req.body.verification);
|
||||
|
||||
//If altcha verification failed
|
||||
if(!verified){
|
||||
return errorHandler(res, 'Altcha verification failed, Please refresh the page!', 'unauthorized');
|
||||
}
|
||||
|
||||
//If we're proxied use passthrough IP
|
||||
const ip = config.proxied ? req.headers['x-forwarded-for'] : req.ip;
|
||||
|
||||
//Look for ban by IP
|
||||
const ipBanDB = await userBanModel.checkBanByIP(ip);
|
||||
|
||||
//If this ip is randy bobandy
|
||||
if(ipBanDB != null){
|
||||
//Make the number a little prettier despite the lack of precision since we're not doing calculations here :P
|
||||
const expiration = ipBanDB.getDaysUntilExpiration() < 1 ? 0 : ipBanDB.getDaysUntilExpiration();
|
||||
let banMsg = [];
|
||||
|
||||
//If the ban is permanent
|
||||
if(ipBanDB.permanent){
|
||||
//tell it to fuck off
|
||||
//Make the code and message look pretty (kinda) at the same time
|
||||
banMsg = [
|
||||
'The IP address you are trying to migrate an account from has been permanently banned.',
|
||||
'Your cleartext IP has been saved to the database.',
|
||||
`Any accounts associated will be nuked in ${expiration} day(s).`,
|
||||
'If you beleive this to be an error feel free to reach out to your server administrator.',
|
||||
'Otherwise, fuck off :)'
|
||||
];
|
||||
}else{
|
||||
//tell it to fuck off
|
||||
//Make the code and message look pretty (kinda) at the same time
|
||||
banMsg = [
|
||||
'The IP address you are trying to migrate an account from has been temporarily banned.',
|
||||
`Your cleartext IP has been saved to the database until the ban expires in ${expiration} day(s).`,
|
||||
'If you beleive this to be an error feel free to reach out to your server administrator.',
|
||||
'Otherwise, fuck off :)'
|
||||
];
|
||||
}
|
||||
|
||||
//tell it to fuck off
|
||||
return errorHandler(res, banMsg.join('<br>'), 'unauthorized');
|
||||
}
|
||||
|
||||
//Find and consume migration document
|
||||
await migrationModel.consumeByUsername(ip, migration);
|
||||
|
||||
//tell of our success
|
||||
return res.sendStatus(200);
|
||||
}else{
|
||||
res.status(400);
|
||||
return res.send({errors: validResult.array()});
|
||||
}
|
||||
}catch(err){
|
||||
return exceptionHandler(res, err);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -40,9 +40,6 @@ module.exports.post = async function(req, res){
|
|||
//Verify Altcha Payload
|
||||
const verified = await altchaUtils.verify(req.body.verification);
|
||||
|
||||
//If we're proxied use passthrough IP
|
||||
const ip = config.proxied ? req.headers['x-forwarded-for'] : req.ip;
|
||||
|
||||
//If altcha verification failed
|
||||
if(!verified){
|
||||
return errorHandler(res, 'Altcha verification failed, Please refresh the page!', 'unauthorized');
|
||||
|
|
@ -66,7 +63,7 @@ module.exports.post = async function(req, res){
|
|||
}
|
||||
|
||||
//Generate the password reset link
|
||||
const requestDB = await passwordResetModel.create({user: userDB._id, ipHash: ip});
|
||||
const requestDB = await passwordResetModel.create({user: userDB._id, ipHash: req.ip});
|
||||
|
||||
//Send the reset url via email
|
||||
const mailInfo = await mailUtils.mailem(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -43,10 +43,6 @@ module.exports.post = async function(req, res){
|
|||
return errorHandler(res, 'Altcha verification failed, Please refresh the page!', 'unauthorized');
|
||||
}
|
||||
|
||||
|
||||
//If we're proxied use passthrough IP
|
||||
const ip = config.proxied ? req.headers['x-forwarded-for'] : req.ip;
|
||||
|
||||
//Would prefer to stick this in userModel.statics.register() but we end up with circular dependencies >:(
|
||||
const nukedBans = await userBanModel.checkProcessedBans(user.user);
|
||||
|
||||
|
|
@ -57,44 +53,22 @@ module.exports.post = async function(req, res){
|
|||
}
|
||||
|
||||
//Look for ban by IP
|
||||
const ipBanDB = await userBanModel.checkBanByIP(ip);
|
||||
const ipBanDB = await userBanModel.checkBanByIP(req.ip);
|
||||
|
||||
//If this ip is randy bobandy
|
||||
if(ipBanDB != null){
|
||||
|
||||
//Make the number a little prettier despite the lack of precision since we're not doing calculations here :P
|
||||
const expiration = ipBanDB.getDaysUntilExpiration() < 1 ? 0 : ipBanDB.getDaysUntilExpiration();
|
||||
let banMsg = [];
|
||||
|
||||
//If the ban is permanent
|
||||
if(ipBanDB.permanent){
|
||||
//tell it to fuck off
|
||||
//Make the code and message look pretty (kinda) at the same time
|
||||
banMsg = [
|
||||
'The IP address you are trying to register an account from has been permanently banned.',
|
||||
'Your cleartext IP has been saved to the database.',
|
||||
`Any associated accounts will be nuked in ${expiration} day(s).`,
|
||||
'If you beleive this to be an error feel free to reach out to your server administrator.',
|
||||
'Otherwise, fuck off :)'
|
||||
];
|
||||
}else{
|
||||
//tell it to fuck off
|
||||
//Make the code and message look pretty (kinda) at the same time
|
||||
banMsg = [
|
||||
'The IP address you are trying to register an account from has been temporarily banned.',
|
||||
`Your cleartext IP has been saved to the database until the ban expires in ${expiration} day(s).`,
|
||||
'If you beleive this to be an error feel free to reach out to your server administrator.',
|
||||
'Otherwise, fuck off :)'
|
||||
];
|
||||
}
|
||||
//Make the code and message look pretty (kinda) at the same time
|
||||
const banMsg = [
|
||||
'The IP address you are trying to register an account from has been banned.',
|
||||
'If you beleive this to be an error feel free to reach out to your server administrator.',
|
||||
'Otherwise, fuck off :)'
|
||||
];
|
||||
|
||||
//tell it to fuck off
|
||||
return errorHandler(res, banMsg.join('<br>'), 'unauthorized');
|
||||
}
|
||||
|
||||
//Register off of given IP
|
||||
await userModel.register(user, ip);
|
||||
|
||||
await userModel.register(user, req.ip);
|
||||
return res.sendStatus(200);
|
||||
}else{
|
||||
res.status(400);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -46,13 +46,7 @@ module.exports.post = async function(req, res){
|
|||
const {field, change} = data;
|
||||
const {user} = req.session;
|
||||
|
||||
//If the user is null
|
||||
if(user == null || user.user == null){
|
||||
//BEFORE YOU BREAK MY HEART!!!
|
||||
return errorHandler(res, 'You must be logged in to preform this action!', 'unauthorized');
|
||||
}
|
||||
|
||||
const userDB = await userModel.findOne({user: user.user});
|
||||
const userDB = await userModel.findOne(user);
|
||||
const update = {};
|
||||
|
||||
|
||||
|
|
@ -92,7 +86,8 @@ module.exports.post = async function(req, res){
|
|||
res.status(200);
|
||||
return res.send(update);
|
||||
}else{
|
||||
return errorHandler(res, 'User not found!', 'unauthorized');
|
||||
res.status(400);
|
||||
return res.send({errors: [{msg:"User not found!"}]});
|
||||
}
|
||||
}else{
|
||||
res.status(400);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -14,9 +14,6 @@ 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/>.*/
|
||||
|
||||
//config
|
||||
const config = require('../../../../config.json');
|
||||
|
||||
//npm imports
|
||||
const {validationResult, matchedData} = require('express-validator');
|
||||
|
||||
|
|
@ -37,9 +34,6 @@ module.exports.post = async function(req, res){
|
|||
//Find user from input
|
||||
const userDB = await userModel.findOne({user});
|
||||
|
||||
//If we're proxied use passthrough IP
|
||||
const ip = config.proxied ? req.headers['x-forwarded-for'] : req.ip;
|
||||
|
||||
//If there is no user
|
||||
if(userDB == null){
|
||||
//Scream
|
||||
|
|
@ -47,7 +41,7 @@ module.exports.post = async function(req, res){
|
|||
}
|
||||
|
||||
//Generate the password reset link
|
||||
const requestDB = await passwordResetModel.create({user: userDB._id, ipHash: ip});
|
||||
const requestDB = await passwordResetModel.create({user: userDB._id, ipHash: req.ip});
|
||||
|
||||
//send URL
|
||||
res.status(200);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -31,7 +31,7 @@ module.exports.post = async function(req, res){
|
|||
const channel = await channelModel.findOne({name: data.chanName});
|
||||
|
||||
if(channel == null){
|
||||
throw loggerUtils.exceptionSmith("Chanenl does not exist!", "validation");
|
||||
throw new Error("Chanenl does not exist!");
|
||||
}
|
||||
|
||||
await channel.nuke(data.confirm);
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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 channelModel = require('../../../schemas/channel/channelSchema');
|
||||
const {exceptionHandler} = require('../../../utils/loggerUtils');
|
||||
|
||||
//get thumby
|
||||
module.exports.get = async function(req, res){
|
||||
try{
|
||||
//Pull validated result
|
||||
const validResult = validationResult(req);
|
||||
|
||||
//if everything validated proper
|
||||
if(validResult.isEmpty()){
|
||||
//Get matched data
|
||||
const data = matchedData(req);
|
||||
//pull channel
|
||||
const chanDB = await channelModel.findOne({name: data.chanName});
|
||||
|
||||
//Null check channel
|
||||
if(chanDB == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "validation");
|
||||
}
|
||||
|
||||
//return thumby
|
||||
res.status(200);
|
||||
return res.send({description: chanDB.description});
|
||||
}else{
|
||||
res.status(400);
|
||||
res.send({errors: validResult.array()})
|
||||
}
|
||||
}catch(err){
|
||||
exceptionHandler(res, err);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Post function
|
||||
module.exports.post = async function(req, res){
|
||||
try{
|
||||
//Pull validated result
|
||||
const validResult = validationResult(req);
|
||||
|
||||
//if everything validated proper
|
||||
if(validResult.isEmpty()){
|
||||
//Get matched data
|
||||
const data = matchedData(req);
|
||||
//pull channel
|
||||
const chanDB = await channelModel.findOne({name: data.chanName});
|
||||
|
||||
//Null check channel
|
||||
if(chanDB == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "validation");
|
||||
}
|
||||
|
||||
//Set thumbnail
|
||||
chanDB.description = data.description;
|
||||
|
||||
//Save channel doc
|
||||
await chanDB.save();
|
||||
|
||||
//return thumby
|
||||
res.status(200);
|
||||
return res.send({description: chanDB.description});
|
||||
}else{
|
||||
res.status(400);
|
||||
res.send({errors: validResult.array()})
|
||||
}
|
||||
}catch(err){
|
||||
exceptionHandler(res, err);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -33,7 +33,7 @@ module.exports.get = async function(req, res){
|
|||
|
||||
|
||||
if(channel == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "validation");
|
||||
throw new Error("Channel not found.");
|
||||
}
|
||||
|
||||
res.status(200);
|
||||
|
|
@ -64,7 +64,7 @@ module.exports.post = async function(req, res){
|
|||
var permError = null;
|
||||
|
||||
if(chanDB == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "validation");
|
||||
throw new Error("Channel not found.");
|
||||
}
|
||||
|
||||
//For each permission submitted
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -31,7 +31,7 @@ module.exports.get = async function(req, res){
|
|||
const channel = await channelModel.findOne({name: data.chanName});
|
||||
|
||||
if(channel == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "validation");
|
||||
throw new Error("Channel not found.");
|
||||
}
|
||||
|
||||
res.status(200);
|
||||
|
|
@ -56,7 +56,7 @@ module.exports.post = async function(req, res){
|
|||
const settingsMap = new Map(Object.entries(data.settingsMap));
|
||||
|
||||
if(channel == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "validation");
|
||||
throw new Error("Channel not found.");
|
||||
}
|
||||
|
||||
res.status(200);
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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 channelModel = require('../../../schemas/channel/channelSchema');
|
||||
const {exceptionHandler} = require('../../../utils/loggerUtils');
|
||||
|
||||
//get thumby
|
||||
module.exports.get = async function(req, res){
|
||||
try{
|
||||
//Pull validated result
|
||||
const validResult = validationResult(req);
|
||||
|
||||
//if everything validated proper
|
||||
if(validResult.isEmpty()){
|
||||
//Get matched data
|
||||
const data = matchedData(req);
|
||||
//pull channel
|
||||
const chanDB = await channelModel.findOne({name: data.chanName});
|
||||
|
||||
//Null check channel
|
||||
if(chanDB == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "validation");
|
||||
}
|
||||
|
||||
//return thumby
|
||||
res.status(200);
|
||||
return res.send({thumbnail: chanDB.thumbnail});
|
||||
}else{
|
||||
res.status(400);
|
||||
res.send({errors: validResult.array()})
|
||||
}
|
||||
}catch(err){
|
||||
exceptionHandler(res, err);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Post function
|
||||
module.exports.post = async function(req, res){
|
||||
try{
|
||||
//Pull validated result
|
||||
const validResult = validationResult(req);
|
||||
|
||||
//if everything validated proper
|
||||
if(validResult.isEmpty()){
|
||||
//Get matched data
|
||||
const data = matchedData(req);
|
||||
//pull channel
|
||||
const chanDB = await channelModel.findOne({name: data.chanName});
|
||||
|
||||
//Null check channel
|
||||
if(chanDB == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "validation");
|
||||
}
|
||||
|
||||
//Set thumbnail
|
||||
chanDB.thumbnail = data.thumbnail;
|
||||
|
||||
//Save channel doc
|
||||
await chanDB.save();
|
||||
|
||||
//return thumby
|
||||
res.status(200);
|
||||
return res.send({thumbnail: chanDB.thumbnail});
|
||||
}else{
|
||||
res.status(400);
|
||||
res.send({errors: validResult.array()})
|
||||
}
|
||||
}catch(err){
|
||||
exceptionHandler(res, err);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//local imports
|
||||
const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils');
|
||||
const csrfUtils = require('../../utils/csrfUtils');
|
||||
|
||||
//api account functions
|
||||
module.exports.get = async function(req, res){
|
||||
try{
|
||||
//Set status to 200
|
||||
res.status(200);
|
||||
//Generate and send token based on the request
|
||||
res.send({token: csrfUtils.generateToken(req)});
|
||||
}catch(err){
|
||||
return exceptionHandler(res, err);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -22,5 +22,5 @@ const csrfUtils = require('../utils/csrfUtils');
|
|||
|
||||
//channel functions
|
||||
module.exports.get = function(req, res){
|
||||
res.render('channel', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||
res.render('channel', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,9 +17,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//Config
|
||||
const config = require('../../config.json');
|
||||
|
||||
//NPM Imports
|
||||
const validator = require('validator');//No express here, so regular validator it is!
|
||||
|
||||
//local imports
|
||||
const channelModel = require('../schemas/channel/channelSchema');
|
||||
const permissionModel = require('../schemas/permissionSchema');
|
||||
|
|
@ -39,10 +36,10 @@ module.exports.get = async function(req, res){
|
|||
delete chanDB.permissions._doc._id;
|
||||
|
||||
if(chanDB == null){
|
||||
throw loggerUtils.exceptionSmith("Channel not found.", "queue");
|
||||
throw new Error("Channel not found.");
|
||||
}
|
||||
|
||||
return res.render('channelSettings', {instance: config.instanceName, links: config.links, user: req.session.user, channel: chanDB, reqRank, rankEnum: permissionModel.rankEnum, csrfToken: csrfUtils.generateToken(req), unescape: validator.unescape});
|
||||
return res.render('channelSettings', {instance: config.instanceName, user: req.session.user, channel: chanDB, reqRank, rankEnum: permissionModel.rankEnum, csrfToken: csrfUtils.generateToken(req)});
|
||||
}catch(err){
|
||||
return exceptionHandler(res, err);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -40,18 +40,18 @@ module.exports.get = async function(req, res){
|
|||
|
||||
//If we have an invalid request
|
||||
if(requestDB == null){
|
||||
return res.render('emailChange', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
}
|
||||
|
||||
//Speak of our success (don't wait for the emails to be sent)
|
||||
res.render('emailChange', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: true});
|
||||
res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: true});
|
||||
|
||||
//Consume the request
|
||||
await requestDB.consume();
|
||||
}else{
|
||||
return res.render('emailChange', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
}
|
||||
}catch(err){
|
||||
return res.render('emailChange', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//Config
|
||||
const config = require('../../config.json');
|
||||
const package = require('../../package.json');
|
||||
|
||||
//Local Imports
|
||||
const csrfUtils = require('../utils/csrfUtils');
|
||||
|
||||
//register page functions
|
||||
module.exports.get = async function(req, res){
|
||||
//Render page
|
||||
return res.render('hrt', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,9 +17,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//Config
|
||||
const config = require('../../config.json');
|
||||
|
||||
//NPM Imports
|
||||
const validator = require('validator');//No express here, so regular validator it is!
|
||||
|
||||
//local imports
|
||||
const channelModel = require('../schemas/channel/channelSchema');
|
||||
const csrfUtils = require('../utils/csrfUtils');
|
||||
|
|
@ -29,7 +26,7 @@ const {exceptionHandler, errorHandler} = require('../utils/loggerUtils');
|
|||
module.exports.get = async function(req, res){
|
||||
try{
|
||||
const chanGuide = await channelModel.getChannelList();
|
||||
return res.render('index', {instance: config.instanceName, links: config.links, user: req.session.user, chanGuide: chanGuide, csrfToken: csrfUtils.generateToken(req), unescape: validator.unescape});
|
||||
return res.render('index', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide, csrfToken: csrfUtils.generateToken(req)});
|
||||
}catch(err){
|
||||
return exceptionHandler(res, err);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -45,7 +45,7 @@ module.exports.get = async function(req, res){
|
|||
//if we have previous attempts for this user
|
||||
if(attempts != null){
|
||||
if(attempts.count > sessionUtils.maxAttempts){
|
||||
return res.render('lockedAccount', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('lockedAccount', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
||||
//If the users login's are being throttled
|
||||
|
|
@ -56,16 +56,16 @@ module.exports.get = async function(req, res){
|
|||
const challenge = await altchaUtils.genCaptcha(difficulty, user);
|
||||
|
||||
//Render page
|
||||
return res.render('login', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('login', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
//otherwise
|
||||
}else{
|
||||
//Render generic page
|
||||
return res.render('login', {instance: config.instanceName, links: config.links, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('login', {instance: config.instanceName, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
//if we received invalid input
|
||||
}else{
|
||||
//Render pretend nothing happened, send out a generic page
|
||||
return res.render('login', {instance: config.instanceName, links: config.links, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('login', {instance: config.instanceName, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//Config
|
||||
const config = require('../../config.json');
|
||||
|
||||
//Local Imports
|
||||
const altchaUtils = require('../utils/altchaUtils');
|
||||
const csrfUtils = require('../utils/csrfUtils');
|
||||
|
||||
//register page functions
|
||||
module.exports.get = async function(req, res){
|
||||
//Generate captcha
|
||||
const challenge = await altchaUtils.genCaptcha();
|
||||
|
||||
//Render page
|
||||
return res.render('migrate', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -27,5 +27,5 @@ module.exports.get = async function(req, res){
|
|||
const challenge = await altchaUtils.genCaptcha();
|
||||
|
||||
//render the page
|
||||
return res.render('newChannel', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('newChannel', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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/>.*/
|
||||
|
||||
//root index functions
|
||||
module.exports.get = async function(req, res){
|
||||
res.render('partial/panels/pm', {});
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -19,5 +19,5 @@ const config = require('../../../config.json');
|
|||
|
||||
//popout panel container functions
|
||||
module.exports.get = async function(req, res){
|
||||
res.render('popoutContainer', {instance: config.instanceName, links: config.links});
|
||||
res.render('popoutContainer', {instance: config.instanceName});
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,11 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//NPM Imports
|
||||
const {validationResult, matchedData} = require('express-validator');
|
||||
|
||||
//NPM Imports
|
||||
const validator = require('validator');//No express here, so regular validator it is!
|
||||
|
||||
//local imports
|
||||
const presenceUtils = require('../../utils/presenceUtils');
|
||||
const {userModel} = require('../../schemas/user/userSchema');
|
||||
const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils');
|
||||
|
||||
|
|
@ -34,10 +30,7 @@ module.exports.get = async function(req, res){
|
|||
const data = matchedData(req);
|
||||
const profile = await userModel.findProfile({user: data.user});
|
||||
|
||||
//Pull presence (should be quick since everyone whos been on since last startup will be backed in RAM)
|
||||
const presence = await presenceUtils.getPresence(profile.user);
|
||||
|
||||
return res.render('partial/panels/profile', {profile, presence, unescape: validator.unescape});
|
||||
return res.render('partial/panels/profile', {profile});
|
||||
}else{
|
||||
res.status(400);
|
||||
return res.send({errors: validResult.array()})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -47,11 +47,11 @@ module.exports.get = async function(req, res){
|
|||
*/
|
||||
|
||||
//Render page
|
||||
return res.render('passwordReset', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, token, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('passwordReset', {instance: config.instanceName, user: req.session.user, challenge, token, csrfToken: csrfUtils.generateToken(req)});
|
||||
//If we didn't get a valid token
|
||||
}else{
|
||||
//otherwise render generic page
|
||||
return res.render('passwordReset', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, token: null, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('passwordReset', {instance: config.instanceName, user: req.session.user, challenge, token: null, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
}catch(err){
|
||||
return exceptionHandler(res, err);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -16,5 +16,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
|
||||
//root index functions
|
||||
module.exports.get = async function(req, res){
|
||||
res.render('partial/panels/settings', {});
|
||||
try{
|
||||
res.render(`partial/popup${req.url}`, {});
|
||||
}catch(err){
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,12 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//Local Imports
|
||||
const {userModel} = require('../schemas/user/userSchema');
|
||||
const csrfUtils = require('../utils/csrfUtils');
|
||||
const presenceUtils = require('../utils/presenceUtils');
|
||||
const {exceptionHandler, errorHandler} = require('../utils/loggerUtils');
|
||||
|
||||
//NPM Imports
|
||||
const validator = require('validator');//No express here, so regular validator it is!
|
||||
|
||||
//Config
|
||||
const config = require('../../config.json');
|
||||
|
||||
|
|
@ -38,27 +34,20 @@ module.exports.get = async function(req, res){
|
|||
//If we have a user, check if the is looking at their own profile
|
||||
const selfProfile = req.session.user ? profile.user == req.session.user.user : false;
|
||||
|
||||
//Pull presence (should be quick since everyone whos been on since last startup will be backed in RAM)
|
||||
const presence = await presenceUtils.getPresence(profile.user);
|
||||
|
||||
res.render('profile', {
|
||||
instance: config.instanceName, links: config.links,
|
||||
instance: config.instanceName,
|
||||
user: req.session.user,
|
||||
profile,
|
||||
selfProfile,
|
||||
presence,
|
||||
csrfToken: csrfUtils.generateToken(req),
|
||||
unescape: validator.unescape
|
||||
csrfToken: csrfUtils.generateToken(req)
|
||||
});
|
||||
}else{
|
||||
res.render('profile', {
|
||||
instance: config.instanceName, links: config.links,
|
||||
instance: config.instanceName,
|
||||
user: req.session.user,
|
||||
profile: null,
|
||||
selfProfile: false,
|
||||
presence: null,
|
||||
csrfToken: csrfUtils.generateToken(req),
|
||||
unescape: validator.unescape
|
||||
csrfToken: csrfUtils.generateToken(req)
|
||||
});
|
||||
}
|
||||
}catch(err){
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -27,5 +27,5 @@ module.exports.get = async function(req, res){
|
|||
const challenge = await altchaUtils.genCaptcha();
|
||||
|
||||
//Render page
|
||||
return res.render('register', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
||||
return res.render('register', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -16,7 +16,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
|
||||
//NPM Imports
|
||||
const {validationResult, matchedData} = require('express-validator');
|
||||
const validator = require('validator');//Because sometimes one isn't enough...
|
||||
|
||||
//local imports
|
||||
const {userModel} = require('../../schemas/user/userSchema');
|
||||
|
|
@ -35,7 +34,7 @@ module.exports.get = async function(req, res){
|
|||
return errorHandler(res, 'Cannot get alts for non-existant user!');
|
||||
}
|
||||
|
||||
return res.render('partial/tooltip/altList', {alts: await userDB.getAltProfiles(), unescape: validator.unescape});
|
||||
return res.render('partial/tooltip/altList', {alts: await userDB.getAltProfiles()});
|
||||
}else{
|
||||
res.status(400);
|
||||
return res.send({errors: validResult.array()})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,9 +17,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//NPM Imports
|
||||
const {validationResult, matchedData} = require('express-validator');
|
||||
|
||||
//NPM Imports
|
||||
const validator = require('validator');//No express here, so regular validator it is!
|
||||
|
||||
//local imports
|
||||
const {userModel} = require('../../schemas/user/userSchema');
|
||||
const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils');
|
||||
|
|
@ -33,10 +30,10 @@ module.exports.get = async function(req, res){
|
|||
const data = matchedData(req);
|
||||
const profile = await userModel.findProfile({user: data.user});
|
||||
|
||||
return res.render('partial/tooltip/profile', {profile, unescape: validator.unescape});
|
||||
return res.render('partial/tooltip/profile', {profile});
|
||||
}else{
|
||||
res.status(400);
|
||||
return res.send({errors: validResult.array()});
|
||||
return res.send({errors: validResult.array()})
|
||||
}
|
||||
|
||||
}catch(err){
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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 { Router } = require('express');
|
||||
|
||||
|
||||
//local imports
|
||||
const aboutController = require("../controllers/aboutController");
|
||||
const presenceUtils = require("../utils/presenceUtils");
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
||||
//Use presence middleware
|
||||
router.use(presenceUtils.presenceMiddleware);
|
||||
|
||||
//routing functions
|
||||
router.get('/', aboutController.get);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,17 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//npm imports
|
||||
const { Router } = require('express');
|
||||
|
||||
|
||||
//local imports
|
||||
const permissionSchema = require("../schemas/permissionSchema");
|
||||
const adminPanelController = require("../controllers/adminPanelController");
|
||||
const presenceUtils = require("../utils/presenceUtils");
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
||||
//Use authentication middleware
|
||||
router.use(permissionSchema.reqPermCheck("adminPanel"))
|
||||
router.use(presenceUtils.presenceMiddleware);
|
||||
|
||||
//routing functions
|
||||
router.get('/', adminPanelController.get);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -22,7 +22,6 @@ const accountValidator = require("../../validators/accountValidator");
|
|||
const loginController = require("../../controllers/api/account/loginController");
|
||||
const logoutController = require("../../controllers/api/account/logoutController");
|
||||
const registerController = require("../../controllers/api/account/registerController");
|
||||
const migrationController = require("../../controllers/api/account/migrationController");
|
||||
const updateController = require("../../controllers/api/account/updateController");
|
||||
const rankEnumController = require("../../controllers/api/account/rankEnumController");
|
||||
const passwordResetRequestController = require("../../controllers/api/account/passwordResetRequestController");
|
||||
|
|
@ -39,32 +38,18 @@ router.post('/login', accountValidator.user(), accountValidator.pass(), loginCon
|
|||
//logout
|
||||
router.post('/logout', logoutController.post);
|
||||
//register
|
||||
router.post('/register',
|
||||
accountValidator.user(),
|
||||
router.post('/register', accountValidator.user(),
|
||||
accountValidator.securePass(),
|
||||
accountValidator.pass('passConfirm'),
|
||||
accountValidator.email(),
|
||||
registerController.post);
|
||||
|
||||
//migrate legacy profile
|
||||
router.post('/migrate',
|
||||
accountValidator.user(),
|
||||
accountValidator.pass('oldPass'),
|
||||
accountValidator.securePass('newPass'),
|
||||
accountValidator.pass('passConfirm'),
|
||||
migrationController.post);
|
||||
|
||||
accountValidator.email(), registerController.post);
|
||||
//update profile
|
||||
router.post('/update',
|
||||
accountValidator.img(),
|
||||
router.post('/update', accountValidator.img(),
|
||||
accountValidator.bio(),
|
||||
accountValidator.signature(),
|
||||
accountValidator.pronouns(),
|
||||
accountValidator.pass('passChange.oldPass'),
|
||||
accountValidator.securePass('passChange.newPass'),
|
||||
accountValidator.pass('passChange.confirmPass'),
|
||||
updateController.post);
|
||||
|
||||
accountValidator.pass('passChange.confirmPass'), updateController.post);
|
||||
//rankEnum
|
||||
//This might seem silly, but it allows us to cleanly get the current rank list to compare against, without storing it in multiple places
|
||||
router.get('/rankEnum', rankEnumController.get);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -22,7 +22,7 @@ const { Router } = require('express');
|
|||
//Models
|
||||
const permissionModel = require("../../schemas/permissionSchema");
|
||||
const channelModel = require("../../schemas/channel/channelSchema");
|
||||
//Validators
|
||||
//Valudators
|
||||
const channelValidator = require("../../validators/channelValidator");
|
||||
const accountValidator = require("../../validators/accountValidator");
|
||||
const {channelPermissionValidator} = require("../../validators/permissionsValidator");
|
||||
|
|
@ -30,8 +30,6 @@ const tokebotValidator = require("../../validators/tokebotValidator");
|
|||
const emoteValidator = require("../../validators/emoteValidator");
|
||||
//Controllers
|
||||
const registerController = require("../../controllers/api/channel/registerController");
|
||||
const thumbnailController = require("../../controllers/api/channel/thumbnailController");
|
||||
const descriptionController = require("../../controllers/api/channel/descriptionController");
|
||||
const listController = require("../../controllers/api/channel/listController");
|
||||
const settingsController = require("../../controllers/api/channel/settingsController");
|
||||
const permissionsController = require("../../controllers/api/channel/permissionsController")
|
||||
|
|
@ -44,10 +42,8 @@ const emoteController = require('../../controllers/api/channel/emoteController')
|
|||
//globals
|
||||
const router = Router();
|
||||
|
||||
//Set validator functions
|
||||
//user authentication middleware
|
||||
router.use("/register",permissionModel.reqPermCheck("registerChannel"));
|
||||
router.use("/thumbnail",channelValidator.name("chanName"));
|
||||
router.use("/description",channelValidator.name("chanName"));
|
||||
router.use("/settings", channelValidator.name('chanName'));
|
||||
router.use("/permissions", channelValidator.name('chanName'));
|
||||
router.use("/rank", channelValidator.name('chanName'));
|
||||
|
|
@ -59,12 +55,6 @@ router.use("/emote", channelValidator.name('chanName'));
|
|||
//routing functions
|
||||
//register
|
||||
router.post('/register', channelValidator.name(), channelValidator.description(), channelValidator.thumbnail(), registerController.post);
|
||||
//Thumbnail
|
||||
router.get('/thumbnail', thumbnailController.get);
|
||||
router.post('/thumbnail', channelValidator.thumbnail(), thumbnailController.post);
|
||||
//Description
|
||||
router.get('/description', descriptionController.get);
|
||||
router.post('/description', channelValidator.description(), descriptionController.post);
|
||||
//list
|
||||
router.get('/list', channelModel.reqPermCheck("manageChannel"), listController.get);
|
||||
//settings
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -18,19 +18,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
const { Router } = require('express');
|
||||
|
||||
//local imports
|
||||
const csrfUtil = require('../utils/csrfUtils');
|
||||
const accountRouter = require("./api/accountRouter");
|
||||
const channelRouter = require("./api/channelRouter");
|
||||
const adminRouter = require("./api/adminRouter");
|
||||
const refreshCSRFTokenController = require("../controllers/api/refreshCSRFTokenController");
|
||||
const csrfUtil = require('../utils/csrfUtils');
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
||||
|
||||
//CSRF token request controller
|
||||
router.get('/refreshToken', refreshCSRFTokenController.get);
|
||||
|
||||
//Apply Cross-Site Request Forgery protection to API calls
|
||||
router.use(csrfUtil.csrfSynchronisedProtection);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -22,7 +22,6 @@ const { Router } = require('express');
|
|||
const channelModel = require("../schemas/channel/channelSchema");
|
||||
const channelController = require("../controllers/channelController");
|
||||
const channelSettingsController = require("../controllers/channelSettingsController");
|
||||
const presenceUtils = require("../utils/presenceUtils");
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
|
@ -30,9 +29,6 @@ const router = Router();
|
|||
//User authentication middleware
|
||||
router.use("/*/settings",channelModel.reqPermCheck("manageChannel","/c/"));
|
||||
|
||||
//Use presence middleware
|
||||
router.use(presenceUtils.presenceMiddleware);
|
||||
|
||||
//routing functions
|
||||
router.get('/*/settings', channelSettingsController.get);
|
||||
router.get('/*/', channelController.get);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 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 { Router } = require('express');
|
||||
|
||||
|
||||
//local imports
|
||||
const hrtController = require("../controllers/hrtController");
|
||||
const presenceUtils = require("../utils/presenceUtils");
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
||||
//Use presence middleware
|
||||
router.use(presenceUtils.presenceMiddleware);
|
||||
|
||||
//routing functions
|
||||
router.get('/', hrtController.get);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -20,14 +20,10 @@ const { Router } = require('express');
|
|||
|
||||
//local imports
|
||||
const indexController = require("../controllers/indexController");
|
||||
const presenceUtils = require("../utils/presenceUtils");
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
||||
//Use presence middleware
|
||||
router.use(presenceUtils.presenceMiddleware);
|
||||
|
||||
//routing functions
|
||||
router.get('/', indexController.get);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -21,7 +21,6 @@ const { Router } = require('express');
|
|||
//local imports
|
||||
const permissionSchema = require("../schemas/permissionSchema");
|
||||
const newChannelController = require("../controllers/newChannelController");
|
||||
const presenceUtils = require("../utils/presenceUtils");
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
|
@ -29,9 +28,6 @@ const router = Router();
|
|||
//user authentication middleware
|
||||
router.use("/",permissionSchema.reqPermCheck("registerChannel"));
|
||||
|
||||
//Use presence middleware
|
||||
router.use(presenceUtils.presenceMiddleware);
|
||||
|
||||
//routing functions
|
||||
router.get('/', newChannelController.get);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -24,8 +24,6 @@ const emoteController = require("../controllers/panel/emoteController");
|
|||
const popoutContainerController = require("../controllers/panel/popoutContainerController");
|
||||
const profileController = require("../controllers/panel/profileController");
|
||||
const queueController = require("../controllers/panel/queueController");
|
||||
const settingsController = require("../controllers/panel/settingsController");
|
||||
const pmController = require("../controllers/panel/pmController");
|
||||
//Validators
|
||||
const accountValidator = require("../validators/accountValidator");
|
||||
|
||||
|
|
@ -38,7 +36,5 @@ router.get('/emote', emoteController.get);
|
|||
router.get('/popoutContainer', popoutContainerController.get);
|
||||
router.get('/profile', accountValidator.user(), profileController.get);
|
||||
router.get('/queue', queueController.get);
|
||||
router.get('/settings', settingsController.get);
|
||||
router.get('/pm', pmController.get);
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -19,12 +19,12 @@ const { Router } = require('express');
|
|||
|
||||
|
||||
//local imports
|
||||
const migrateController = require("../controllers/migrateController");
|
||||
const popupController = require("../controllers/popupController");
|
||||
|
||||
//globals
|
||||
const router = Router();
|
||||
|
||||
//routing functions
|
||||
router.get('/', migrateController.get);
|
||||
router.get('/*', popupController.get);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,9 +17,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//NPM Imports
|
||||
const {mongoose} = require('mongoose');
|
||||
|
||||
/**
|
||||
* DB Schema for Documents representing a user ban from a single channel
|
||||
*/
|
||||
const channelBanSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.SchemaTypes.ObjectID,
|
||||
|
|
@ -44,10 +41,6 @@ const channelBanSchema = new mongoose.Schema({
|
|||
});
|
||||
|
||||
//methods
|
||||
/**
|
||||
* Calculates days until ban expiration
|
||||
* @returns {Number} Days until the given ban expires
|
||||
*/
|
||||
channelBanSchema.methods.getDaysUntilExpiration = function(){
|
||||
//Get ban date
|
||||
const expirationDate = new Date(this.banDate);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community
|
||||
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
|
||||
|
|
@ -17,17 +17,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//NPM Imports
|
||||
const {mongoose} = require('mongoose');
|
||||
|
||||
/**
|
||||
* Rank Enum, lists all known permission ranks from lowest to highest.
|
||||
*
|
||||
* This originally belonged to the permissionSchema, but this avoids circular dependencies.
|
||||
*/
|
||||
//This originally belonged to the permissionSchema, but this avoids circular dependencies.
|
||||
const rankEnum = ["anon", "user", "gold", "bot", "mod", "admin"];
|
||||
|
||||
//Since this is intended to be used as a child schema for multiple parent schemas, we won't export it as a model
|
||||
/**
|
||||
* DB Schema for Sub-Document representing permission structure for a single channel
|
||||
*/
|
||||
const channelPermissionSchema = new mongoose.Schema({
|
||||
manageChannel: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
|
|
@ -89,12 +82,6 @@ const channelPermissionSchema = new mongoose.Schema({
|
|||
default: "admin",
|
||||
required: true
|
||||
},
|
||||
readSchedule: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
enum: rankEnum,
|
||||
default: "admin",
|
||||
required: true
|
||||
},
|
||||
scheduleMedia: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
enum: rankEnum,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue