Compare commits
No commits in common. "main" and "0.3-indev" have entirely different histories.
264 changed files with 1625 additions and 6765 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
log/*
|
log/crash/*
|
||||||
www/doc/*/*
|
www/doc/*/*
|
||||||
package-lock.json
|
package-lock.json
|
||||||
config.json
|
config.json
|
||||||
|
|
@ -7,7 +7,4 @@ config.json.old
|
||||||
state.json
|
state.json
|
||||||
chatexamples.txt
|
chatexamples.txt
|
||||||
server.cert
|
server.cert
|
||||||
server.key
|
server.key
|
||||||
www/nonfree/*
|
|
||||||
migration/*
|
|
||||||
www/hrt.zip
|
|
||||||
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
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 19 November 2007
|
Version 3, 19 November 2007
|
||||||
|
|
|
||||||
73
README.md
73
README.md
|
|
@ -1,44 +1,31 @@
|
||||||
Canopy
|
Canopy - 0.3-INDEV
|
||||||
======
|
======
|
||||||
|
|
||||||
<img src="https://img.shields.io/badge/developed-while%20high-339933">
|
Canopy - /ˈkæ.nə.pi/:
|
||||||
<img src="https://pride-badges.pony.workers.dev/static/v1?label=enbyware&labelColor=%23555&stripeWidth=8&stripeColors=FCF434%2CFFFFFF%2C9C59D1%2C2C2C2C">
|
- The upper layer of foliage and branches of a forest, containing the majority of animal life.
|
||||||
<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">
|
Canopy is a community chat & synced video embedding web application, intended to replace fore.st as the server software for ourfore.st.
|
||||||
<a href="https://git.ourfore.st/rainbownapkin/canopy/issues" target="_blank"><img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/open.svg"></a>
|
This new codebase intends to solve the following issues with the current CyTube based software:
|
||||||
<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>
|
- Unmaintained upstream codebase.
|
||||||
|
- Different goals.
|
||||||
0.2-Alpha (Durban Poison)
|
- Different coding styles.
|
||||||
=========
|
- Obsolete Technology and Dependencies.
|
||||||
|
- General Clunk
|
||||||
Canopy - /ˈkæ.nə.pi/:
|
- Less Unique Community Identity
|
||||||
- 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 intends to be a simple node/express.js app. It leverages 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 bcrypt, however it IS hobbiest software, and it should be treated as such.
|
||||||
|
|
||||||
Canopy is a community chat & synced video embedding web application, intended to replace fore.st as the server software for ourfore.st.
|
The Canopy codebase does not, nor will it ever contain:
|
||||||
This new codebase intends to solve the following issues with the current CyTube based software:
|
- Advertisements (targetted or otherwise)
|
||||||
|
- Proprietary Code
|
||||||
- Unmaintained upstream codebase.
|
- Cryptocurrency/Blockchain integration
|
||||||
- Different goals.
|
- 'Analytics/Telemtry' spyware
|
||||||
- Different coding styles.
|
- The use of video sources which require proprietary 'Digital ~~Rights Management~~ Ristricitons Malware' such as Widevine.
|
||||||
- Obsolete Technology and Dependencies.
|
|
||||||
- General Clunk
|
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!
|
||||||
- Less Unique Community Identity
|
|
||||||
|
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.
|
||||||
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.
|
|
||||||
|
## License
|
||||||
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 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.
|
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,14 @@
|
||||||
{
|
{
|
||||||
"instanceName": "Canopy",
|
"instanceName": "Canopy",
|
||||||
"verbose": false,
|
"verbose": false,
|
||||||
"port": 8443,
|
"port": 8080,
|
||||||
"proxied": true,
|
"proxied": false,
|
||||||
"protocol": "https",
|
"protocol": "http",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"ytdlpPath": "/home/canopy/.local/pipx/venvs/yt-dlp/bin/yt-dlp",
|
"ytdlpPath": "/home/canopy/.local/pipx/venvs/yt-dlp/bin/yt-dlp",
|
||||||
"migrate": false,
|
"sessionSecret": "CHANGE_ME",
|
||||||
"dropLegacyTokes": false,
|
"altchaSecret": "CHANGE_ME",
|
||||||
"debug": false,
|
"ipSecret": "CHANGE_ME",
|
||||||
"secrets":{
|
|
||||||
"passwordSecret": "CHANGE_ME",
|
|
||||||
"rememberMeSecret": "CHANGE_ME",
|
|
||||||
"sessionSecret": "CHANGE_ME",
|
|
||||||
"altchaSecret": "CHANGE_ME",
|
|
||||||
"ipSecret": "CHANGE_ME"
|
|
||||||
},
|
|
||||||
"ssl":{
|
"ssl":{
|
||||||
"cert": "./server.cert",
|
"cert": "./server.cert",
|
||||||
"key": "./server.key"
|
"key": "./server.key"
|
||||||
|
|
@ -33,11 +26,5 @@
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"address": "toke@42069.weed",
|
"address": "toke@42069.weed",
|
||||||
"pass": "CHANGE_ME"
|
"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."
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
//Scream about exceptions in the console
|
//Scream about exceptions in the console
|
||||||
"verbose": false,
|
"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 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,
|
"port": 8080,
|
||||||
//Lets the server know it's sitting behind a reverse-proxy
|
//Lets the server know it's sitting behind a reverse-proxy
|
||||||
"proxied": true,
|
"proxied": false,
|
||||||
//Protocol (either HTTP or HTTPS)
|
//Protocol (either HTTP or HTTPS)
|
||||||
"protocol": "http",
|
"protocol": "http",
|
||||||
//Domain the server is available at, used for server-side link generation
|
//Domain the server is available at, used for server-side link generation
|
||||||
|
|
@ -16,34 +16,14 @@
|
||||||
//Path to YT-DLP Executable for scraping youtube, dailymotion, and vimeo
|
//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
|
//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",
|
"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
|
//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
|
//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":{
|
//Session secret used to secure session keys
|
||||||
//Password secret used to pepper password hashes
|
"sessionSecret": "CHANGE_ME",
|
||||||
"passwordSecret": "CHANGE_ME",
|
//Altacha secret used to generate altcha challenges
|
||||||
//Password secret used to pepper rememberMe token hashes
|
"altchaSecret": "CHANGE_ME",
|
||||||
"rememberMeSecret": "CHANGE_ME",
|
//IP Secret used to salt IP Hashes
|
||||||
//Session secret used to secure session keys
|
"ipSecret": "CHANGE_ME",
|
||||||
"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 and key locations
|
||||||
"ssl":{
|
"ssl":{
|
||||||
"cert": "./server.cert",
|
"cert": "./server.cert",
|
||||||
|
|
@ -64,13 +44,5 @@
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"address": "toke@42069.weed",
|
"address": "toke@42069.weed",
|
||||||
"pass": "CHANGE_ME"
|
"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
|
|
||||||
20
package.json
20
package.json
|
|
@ -1,17 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "canopy-of-alpha",
|
"name": "canopy-of",
|
||||||
"version": "0.2.0",
|
"version": "0.3",
|
||||||
"canopyDisplayVersion": "0.2-Alpha (Durban Poison)",
|
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^7.1.1",
|
"altcha": "^1.0.7",
|
||||||
"altcha": "^2.3.0",
|
|
||||||
"altcha-lib": "^1.2.0",
|
"altcha-lib": "^1.2.0",
|
||||||
"argon2": "^0.44.0",
|
"bcrypt": "^5.1.1",
|
||||||
"bcrypt": "^6.0.0",
|
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"connect-mongo": "^5.1.0",
|
"connect-mongo": "^5.1.0",
|
||||||
"cookie-parser": "^1.4.7",
|
|
||||||
"csrf-sync": "^4.0.3",
|
"csrf-sync": "^4.0.3",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
|
@ -20,8 +16,8 @@
|
||||||
"hls.js": "^1.6.2",
|
"hls.js": "^1.6.2",
|
||||||
"mongoose": "^8.4.3",
|
"mongoose": "^8.4.3",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"nodemailer": "^8.0.7",
|
"nodemailer": "^6.9.16",
|
||||||
"socket.io": "^4.2.0",
|
"socket.io": "^4.8.1",
|
||||||
"youtube-dl-exec": "^3.0.20"
|
"youtube-dl-exec": "^3.0.20"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -30,7 +26,7 @@
|
||||||
"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/"
|
"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": {
|
"devDependencies": {
|
||||||
"jsdoc": "^4.0.4",
|
"nodemon": "^3.1.10",
|
||||||
"nodemon": "^3.1.10"
|
"jsdoc": "^4.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -46,11 +46,6 @@ class activeChannel{
|
||||||
* List of channel-wide toke commands
|
* List of channel-wide toke commands
|
||||||
*/
|
*/
|
||||||
this.tokeCommands = chanDB.tokeCommands;
|
this.tokeCommands = chanDB.tokeCommands;
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal Database Identifier for the channel
|
|
||||||
*/
|
|
||||||
this._id = chanDB._id;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of connected users
|
* List of connected users
|
||||||
|
|
@ -79,7 +74,6 @@ class activeChannel{
|
||||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
* @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 {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||||
* @param {Socket} socket - Requesting Socket
|
* @param {Socket} socket - Requesting Socket
|
||||||
* @returns {activeUser} active user object generated by the new connection
|
|
||||||
*/
|
*/
|
||||||
async handleConnection(userDB, chanDB, socket){
|
async handleConnection(userDB, chanDB, socket){
|
||||||
//get current user object from the userlist
|
//get current user object from the userlist
|
||||||
|
|
@ -110,13 +104,10 @@ class activeChannel{
|
||||||
this.playlistHandler.defineListeners(socket);
|
this.playlistHandler.defineListeners(socket);
|
||||||
|
|
||||||
//Hand off the connection initiation to it's user object
|
//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
|
//Send out the userlist
|
||||||
this.broadcastUserList(socket.chan);
|
this.broadcastUserList(socket.chan);
|
||||||
|
|
||||||
//Return active user connection object for use by the channelManager object
|
|
||||||
return activeUser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -124,11 +115,11 @@ class activeChannel{
|
||||||
* @param {Socket} socket - Requesting Socket
|
* @param {Socket} socket - Requesting Socket
|
||||||
*/
|
*/
|
||||||
handleDisconnect(socket){
|
handleDisconnect(socket){
|
||||||
//temporarily store userObj
|
|
||||||
let userObj = this.userList.get(socket.user.user);
|
|
||||||
|
|
||||||
//If we have more than one active connection
|
//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
|
//Filter out disconnecting socket from socket list, and set as current socket list for user
|
||||||
userObj.sockets = userObj.sockets.filter((id) => {
|
userObj.sockets = userObj.sockets.filter((id) => {
|
||||||
return id != socket.id;
|
return id != socket.id;
|
||||||
|
|
@ -136,11 +127,7 @@ class activeChannel{
|
||||||
|
|
||||||
//Update the userlist
|
//Update the userlist
|
||||||
this.userList.set(socket.user.user, userObj);
|
this.userList.set(socket.user.user, userObj);
|
||||||
//If this is the last one
|
|
||||||
}else{
|
}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
|
//If this is the last connection for this user, remove them from the userlist
|
||||||
this.userList.delete(socket.user.user);
|
this.userList.delete(socket.user.user);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -20,12 +20,12 @@ const config = require('../../../config.json');
|
||||||
//Local Imports
|
//Local Imports
|
||||||
const channelModel = require('../../schemas/channel/channelSchema');
|
const channelModel = require('../../schemas/channel/channelSchema');
|
||||||
const emoteModel = require('../../schemas/emoteSchema');
|
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 loggerUtils = require('../../utils/loggerUtils');
|
||||||
const presenceUtils = require('../../utils/presenceUtils');
|
const csrfUtils = require('../../utils/csrfUtils');
|
||||||
const activeChannel = require('./activeChannel');
|
const activeChannel = require('./activeChannel');
|
||||||
const chatHandler = require('./chatHandler');
|
const chatHandler = require('./chatHandler');
|
||||||
const queueBroadcastManager = require('./media/queueBroadcastManager');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class containing global server-side channel connection management logic
|
* Class containing global server-side channel connection management logic
|
||||||
|
|
@ -33,7 +33,7 @@ const queueBroadcastManager = require('./media/queueBroadcastManager');
|
||||||
class channelManager{
|
class channelManager{
|
||||||
/**
|
/**
|
||||||
* Instantiates object containing global server-side channel conection management logic
|
* Instantiates object containing global server-side channel conection management logic
|
||||||
* @param {Socket.io} io - Socket.io server instanced passed down from server.js
|
* @param {Server} io - Socket.io server instanced passed down from server.js
|
||||||
*/
|
*/
|
||||||
constructor(io){
|
constructor(io){
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,26 +46,11 @@ class channelManager{
|
||||||
*/
|
*/
|
||||||
this.activeChannels = new Map;
|
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
|
* Global Chat Handler Object
|
||||||
*/
|
*/
|
||||||
this.chatHandler = new chatHandler(this);
|
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)
|
|
||||||
|
|
||||||
//Handle connections from socket.io
|
//Handle connections from socket.io
|
||||||
io.on("connection", this.handleConnection.bind(this) );
|
io.on("connection", this.handleConnection.bind(this) );
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +62,7 @@ class channelManager{
|
||||||
async handleConnection(socket){
|
async handleConnection(socket){
|
||||||
try{
|
try{
|
||||||
//ensure unbanned ip and valid CSRF token
|
//ensure unbanned ip and valid CSRF token
|
||||||
if(!(await socketUtils.validateSocket(socket))){
|
if(!(await this.validateSocket(socket))){
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +70,7 @@ class channelManager{
|
||||||
//Prevent logged out connections and authenticate socket
|
//Prevent logged out connections and authenticate socket
|
||||||
if(socket.request.session.user != null){
|
if(socket.request.session.user != null){
|
||||||
//Authenticate socket
|
//Authenticate socket
|
||||||
const userDB = await socketUtils.authSocket(socket);
|
const userDB = await this.authSocket(socket);
|
||||||
|
|
||||||
//Get the active channel based on the socket
|
//Get the active channel based on the socket
|
||||||
var {activeChan, chanDB} = await this.getActiveChan(socket);
|
var {activeChan, chanDB} = await this.getActiveChan(socket);
|
||||||
|
|
@ -103,48 +88,13 @@ class channelManager{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Connection accepted past this point
|
|
||||||
|
|
||||||
//Define listeners for inter-channel classes
|
//Define listeners for inter-channel classes
|
||||||
this.defineListeners(socket);
|
this.defineListeners(socket);
|
||||||
this.chatHandler.defineListeners(socket);
|
this.chatHandler.defineListeners(socket);
|
||||||
|
|
||||||
//Hand off the connection to it's given active channel object
|
//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?
|
//Lil' hacky to pass chanDB like that, but why double up on DB calls?
|
||||||
const activeUser = await activeChan.handleConnection(userDB, chanDB, socket);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
//Toss out anon's
|
//Toss out anon's
|
||||||
socket.emit("kick", {type: "disconnected", reason: "You must log-in to join this channel!"});
|
socket.emit("kick", {type: "disconnected", reason: "You must log-in to join this channel!"});
|
||||||
|
|
@ -155,7 +105,71 @@ class channelManager{
|
||||||
//Flip a table if something fucks up
|
//Flip a table if something fucks up
|
||||||
return loggerUtils.socketCriticalExceptionHandler(socket, err);
|
return loggerUtils.socketCriticalExceptionHandler(socket, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global server-side validation logic for new connections to any channel
|
||||||
|
* @param {Socket} socket - Requesting Socket
|
||||||
|
* @returns {Boolean} true on success
|
||||||
|
*/
|
||||||
|
async validateSocket(socket){
|
||||||
|
//If we're proxied use passthrough IP
|
||||||
|
const ip = config.proxied ? socket.handshake.headers['x-forwarded-for'] : socket.handshake.address;
|
||||||
|
|
||||||
|
//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();
|
||||||
|
|
||||||
|
//If the ban is permanent
|
||||||
|
if(ipBanDB.permanent){
|
||||||
|
//tell it to fuck off
|
||||||
|
socket.emit("kick", {type: "kicked", reason: `The IP address you are trying to connect from has been permanently banned. Your cleartext IP has been saved to the database. Any associated accounts will be nuked in ${expiration} day(s).`});
|
||||||
|
//Otherwise
|
||||||
|
}else{
|
||||||
|
//tell it to fuck off
|
||||||
|
socket.emit("kick", {type: "kicked", reason: `The IP address you are trying to connect from has been temporarily banned. Your cleartext IP has been saved to the database until the ban expires in ${expiration} day(s).`});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global server-side authorization logic for new connections to any channel
|
||||||
|
* @param {Socket} socket - Requesting Socket
|
||||||
|
* @returns {Mongoose.Document} - Authorized User Document upon success
|
||||||
|
*/
|
||||||
|
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 loggerUtils.exceptionSmith("User not found!", "unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set socket user and channel values
|
||||||
|
socket.user = {
|
||||||
|
id: userDB.id,
|
||||||
|
user: userDB.user,
|
||||||
|
};
|
||||||
|
|
||||||
|
return userDB;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets active channel from a given socket
|
* Gets active channel from a given socket
|
||||||
|
|
@ -163,7 +177,7 @@ class channelManager{
|
||||||
* @returns {Object} Object containing users active channel name and channel document object
|
* @returns {Object} Object containing users active channel name and channel document object
|
||||||
*/
|
*/
|
||||||
async getActiveChan(socket){
|
async getActiveChan(socket){
|
||||||
socket.chan = socketUtils.getChannelName(socket);
|
socket.chan = socket.handshake.headers.referer.split('/c/')[1].split('/')[0];
|
||||||
const chanDB = (await channelModel.findOne({name: socket.chan}));
|
const chanDB = (await channelModel.findOne({name: socket.chan}));
|
||||||
|
|
||||||
//Check if channel exists
|
//Check if channel exists
|
||||||
|
|
@ -203,37 +217,6 @@ class channelManager{
|
||||||
activeChan.handleDisconnect(socket, reason);
|
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
|
* Pulls user information by socket
|
||||||
* @param {Socket} socket - Socket to check
|
* @param {Socket} socket - Socket to check
|
||||||
|
|
@ -274,28 +257,30 @@ class channelManager{
|
||||||
* @param {Function} cb - Callback function to run active connections of a given user against
|
* @param {Function} cb - Callback function to run active connections of a given user against
|
||||||
*/
|
*/
|
||||||
crawlConnections(user, cb){
|
crawlConnections(user, cb){
|
||||||
//Pull connection list from status map
|
//For each channel
|
||||||
const list = this.activeUsers.get(user);
|
this.activeChannels.forEach((channel) => {
|
||||||
|
//Check and see if the user is connected
|
||||||
|
const foundUser = channel.userList.get(user);
|
||||||
|
|
||||||
//If we have active connections
|
//If we found a user and this channel hasn't been added to the list
|
||||||
if(list != null){
|
if(foundUser){
|
||||||
//For each connection
|
cb(foundUser);
|
||||||
for(let user of list){
|
|
||||||
//Run the callback against it
|
|
||||||
cb(user);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterates through connections by a given username, and runs them through a given callback function/method
|
* 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 {String} user - Username to crawl connections against
|
||||||
* @param {Function} cb - Callback function to run active connections of a given user against
|
* @param {Function} cb - Callback function to run active connections of a given user against
|
||||||
*/
|
*/
|
||||||
getConnections(user){
|
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 connects
|
||||||
return connections;
|
return connections;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -14,20 +14,49 @@ GNU Affero General Public License for more details.
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.*/
|
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 representing a single chat message
|
||||||
*/
|
*/
|
||||||
class chat extends chatMetadata{
|
class chat{
|
||||||
/**
|
/**
|
||||||
* Instantiates a chat message object
|
* Instantiates a chat message object
|
||||||
* @param {connectedUser} user - User who sent the message
|
* @param {connectedUser} user - 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){
|
constructor(user, flair, highLevel, msg, type, links){
|
||||||
//Call derived constructor
|
/**
|
||||||
super(user, flair, highLevel, msg, type, links);
|
* 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -18,9 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
const validator = require('validator')
|
const validator = require('validator')
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const chatPreprocessor = require('../chatPreprocessor');
|
const commandPreprocessor = require('./commandPreprocessor');
|
||||||
const commandProcessor = require('./commandProcessor');
|
|
||||||
const tokebot = require('./tokebot');
|
|
||||||
const loggerUtils = require('../../utils/loggerUtils');
|
const loggerUtils = require('../../utils/loggerUtils');
|
||||||
const linkUtils = require('../../utils/linkUtils');
|
const linkUtils = require('../../utils/linkUtils');
|
||||||
const emoteValidator = require('../../validators/emoteValidator');
|
const emoteValidator = require('../../validators/emoteValidator');
|
||||||
|
|
@ -44,10 +42,7 @@ class chatHandler{
|
||||||
/**
|
/**
|
||||||
* Child Command Pre-Processor Object
|
* Child Command Pre-Processor Object
|
||||||
*/
|
*/
|
||||||
this.chatPreprocessor = new chatPreprocessor(
|
this.commandPreprocessor = new commandPreprocessor(server, this)
|
||||||
new commandProcessor(server, this),
|
|
||||||
new tokebot(server, this)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Max chat buffer message count
|
* Max chat buffer message count
|
||||||
|
|
@ -72,21 +67,8 @@ class chatHandler{
|
||||||
* @param {Socket} socket - Socket we're receiving the request from
|
* @param {Socket} socket - Socket we're receiving the request from
|
||||||
* @param {Object} data - Event payload
|
* @param {Object} data - Event payload
|
||||||
*/
|
*/
|
||||||
async handleChat(socket, data){
|
handleChat(socket, data){
|
||||||
try{
|
this.commandPreprocessor.preprocess(socket, data);
|
||||||
//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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -212,12 +194,6 @@ class chatHandler{
|
||||||
* @param {chat} chat - Chat Object representing the message to broadcast to the given channel
|
* @param {chat} chat - Chat Object representing the message to broadcast to the given channel
|
||||||
*/
|
*/
|
||||||
relayChatObject(chan, chat){
|
relayChatObject(chan, chat){
|
||||||
//If we have an empty chat
|
|
||||||
if(chat.msg.length <= 0){
|
|
||||||
//Drop it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Send out chat
|
//Send out chat
|
||||||
this.server.io.in(chan).emit("chatMessage", chat);
|
this.server.io.in(chan).emit("chatMessage", chat);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -14,10 +14,163 @@ GNU Affero General Public License for more details.
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.*/
|
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
|
//Local Imports
|
||||||
|
const tokebot = require('./tokebot');
|
||||||
|
const linkUtils = require('../../utils/linkUtils');
|
||||||
const permissionModel = require('../../schemas/permissionSchema');
|
const permissionModel = require('../../schemas/permissionSchema');
|
||||||
const channelModel = require('../../schemas/channel/channelSchema');
|
const channelModel = require('../../schemas/channel/channelSchema');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class containing global server-side chat/command pre-processing logic
|
||||||
|
*/
|
||||||
|
class commandPreprocessor{
|
||||||
|
/**
|
||||||
|
* Instantiates a commandPreprocessor object
|
||||||
|
* @param {channelManager} server - Parent Server Object
|
||||||
|
* @param {chatHandler} chatHandler - Parent Chat Handler Object
|
||||||
|
*/
|
||||||
|
constructor(server, chatHandler){
|
||||||
|
/**
|
||||||
|
* Parent Server Object
|
||||||
|
*/
|
||||||
|
this.server = server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent Chat Handler Object
|
||||||
|
*/
|
||||||
|
this.chatHandler = chatHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Child Command Processor Object
|
||||||
|
*/
|
||||||
|
this.commandProcessor = new commandProcessor(server, chatHandler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Child Tokebot Object
|
||||||
|
*/
|
||||||
|
this.tokebot = new tokebot(server, chatHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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[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{
|
||||||
|
//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
|
||||||
|
//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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relays chat to channel via parent Chat Handler object
|
||||||
|
* @param {Object} commandObj - Object representing a single given command/chat request
|
||||||
|
*/
|
||||||
|
sendChat(commandObj){
|
||||||
|
//FUCKIN' SEND IT!
|
||||||
|
this.chatHandler.relayUserChat(commandObj.socket, commandObj.message, commandObj.chatType, commandObj.links);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing global server-side chat/command processing logic
|
* Class representing global server-side chat/command processing logic
|
||||||
*/
|
*/
|
||||||
|
|
@ -291,4 +444,4 @@ class commandProcessor{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = commandProcessor;
|
module.exports = commandPreprocessor;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -54,11 +54,6 @@ class connectedUser{
|
||||||
*/
|
*/
|
||||||
this.highLevel = userDB.highLevel;
|
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
|
//Check to make sure users flair entry from DB is good
|
||||||
if(userDB.flair != null){
|
if(userDB.flair != null){
|
||||||
//Set flair from DB
|
//Set flair from DB
|
||||||
|
|
@ -96,7 +91,6 @@ class connectedUser{
|
||||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
* @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 {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||||
* @param {Socket} socket - Requesting Socket
|
* @param {Socket} socket - Requesting Socket
|
||||||
* @returns {activeUser} active user object generated by the new connection
|
|
||||||
*/
|
*/
|
||||||
async handleConnection(userDB, chanDB, socket){
|
async handleConnection(userDB, chanDB, socket){
|
||||||
//send metadata to client
|
//send metadata to client
|
||||||
|
|
@ -121,10 +115,6 @@ class connectedUser{
|
||||||
//Tattoo hashed IP address to user account for seven days
|
//Tattoo hashed IP address to user account for seven days
|
||||||
await userDB.tattooIPRecord(socket.handshake.address);
|
await userDB.tattooIPRecord(socket.handshake.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Return active user object for use by activeChannel and channelManager objects
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -231,6 +221,9 @@ class connectedUser{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Get schedule as a temporary array
|
||||||
|
const queue = await this.channel.queue.prepQueue(chanDB);
|
||||||
|
|
||||||
//Get schedule lock status
|
//Get schedule lock status
|
||||||
const queueLock = this.channel.queue.locked;
|
const queueLock = this.channel.queue.locked;
|
||||||
|
|
||||||
|
|
@ -238,7 +231,7 @@ class connectedUser{
|
||||||
const chatBuffer = this.channel.chatBuffer.buffer;
|
const chatBuffer = this.channel.chatBuffer.buffer;
|
||||||
|
|
||||||
//Send off the metadata to our user's clients
|
//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, chatBuffer});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -26,9 +26,9 @@ class media{
|
||||||
* @param {String} id - Video ID from source (IE: youtube watch code/archive.org file path)
|
* @param {String} id - Video ID from source (IE: youtube watch code/archive.org file path)
|
||||||
* @param {String} type - Original video source
|
* @param {String} type - Original video source
|
||||||
* @param {Number} duration - Length of media in seconds
|
* @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
|
* @param {String} rawLink - URL to raw file copy of media, not applicable to all sources
|
||||||
*/
|
*/
|
||||||
constructor(title, fileName, url, id, type, duration, rawLink){
|
constructor(title, fileName, url, id, type, duration, rawLink = url){
|
||||||
/**
|
/**
|
||||||
* Chosen title of media
|
* Chosen title of media
|
||||||
*/
|
*/
|
||||||
|
|
@ -59,18 +59,10 @@ class media{
|
||||||
*/
|
*/
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
|
|
||||||
if(rawLink == null){
|
/**
|
||||||
/**
|
* URL to raw file copy of media, not applicable to all sources
|
||||||
* URL to raw file copy of media, not applicable to all sources
|
*/
|
||||||
*/
|
this.rawLink = rawLink;
|
||||||
this.rawLink = {
|
|
||||||
audio: [],
|
|
||||||
video: [],
|
|
||||||
combo: [['default',url]]
|
|
||||||
};
|
|
||||||
}else{
|
|
||||||
this.rawLink = rawLink;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -16,18 +16,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
//NPM imports
|
//NPM imports
|
||||||
const validator = require('validator');
|
const validator = require('validator');
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const { BSON } = require('mongodb');
|
|
||||||
|
|
||||||
//Local imports
|
//Local imports
|
||||||
const config = require('../../../../config.json');
|
|
||||||
const queuedMedia = require('./queuedMedia');
|
const queuedMedia = require('./queuedMedia');
|
||||||
const archivedMedia = require('./archivedMedia');
|
|
||||||
const yanker = require('../../../utils/media/yanker');
|
const yanker = require('../../../utils/media/yanker');
|
||||||
const loggerUtils = require('../../../utils/loggerUtils');
|
const loggerUtils = require('../../../utils/loggerUtils');
|
||||||
const channelModel = require('../../../schemas/channel/channelSchema');
|
const channelModel = require('../../../schemas/channel/channelSchema');
|
||||||
const permissionModel = require('../../../schemas/permissionSchema');
|
|
||||||
const archivedMediaModel = require('../../../schemas/channel/media/archivedMediaSchema');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object represneting a single channel's media queue
|
* Object represneting a single channel's media queue
|
||||||
|
|
@ -120,11 +114,6 @@ class queue{
|
||||||
socket.on("move", (data) => {this.moveMedia(socket, data)});
|
socket.on("move", (data) => {this.moveMedia(socket, data)});
|
||||||
socket.on("lock", () => {this.toggleLock(socket)});
|
socket.on("lock", () => {this.toggleLock(socket)});
|
||||||
socket.on("goLive", (data) => {this.goLive(socket, data)});
|
socket.on("goLive", (data) => {this.goLive(socket, data)});
|
||||||
|
|
||||||
//If debug mode is enabled
|
|
||||||
if(config.debug){
|
|
||||||
socket.on("dumpQueue", (data) => {this.dumpQueue(socket, data)});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//--- USER FACING QUEUEING FUNCTIONS ---
|
//--- USER FACING QUEUEING FUNCTIONS ---
|
||||||
|
|
@ -143,12 +132,12 @@ class queue{
|
||||||
let url = data.url;
|
let url = data.url;
|
||||||
|
|
||||||
//If we where given a bad URL
|
//If we where given a bad URL
|
||||||
if(!validator.isURL(url,{require_valid_protocol: true})){
|
if(!validator.isURL(url)){
|
||||||
//Attempt to fix the situation by encoding it
|
//Attempt to fix the situation by encoding it
|
||||||
url = encodeURI(url);
|
url = encodeURI(url);
|
||||||
|
|
||||||
//If it's still bad
|
//If it's still bad
|
||||||
if(!validator.isURL(url,{require_valid_protocol: true})){
|
if(!validator.isURL(url)){
|
||||||
//Bitch, moan, complain...
|
//Bitch, moan, complain...
|
||||||
loggerUtils.socketErrorHandler(socket, "Bad URL!", "validation");
|
loggerUtils.socketErrorHandler(socket, "Bad URL!", "validation");
|
||||||
//and ignore it!
|
//and ignore it!
|
||||||
|
|
@ -357,12 +346,6 @@ class queue{
|
||||||
throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while queue item!`, "queue");
|
throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while queue item!`, "queue");
|
||||||
}
|
}
|
||||||
|
|
||||||
//If the user doesn't have permission to be doing this shit
|
|
||||||
if(!((!this.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin'))){
|
|
||||||
//Fuggettabouttit!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If something is playing
|
//If something is playing
|
||||||
if(this.nowPlaying != null){
|
if(this.nowPlaying != null){
|
||||||
//Capture currently playing object
|
//Capture currently playing object
|
||||||
|
|
@ -409,7 +392,7 @@ class queue{
|
||||||
);
|
);
|
||||||
|
|
||||||
//Validate mode input, and default to overwrite
|
//Validate mode input, and default to overwrite
|
||||||
this.liveMode = (data != null && data.mode == "pushback") ? "pushback" : "overwrite";
|
this.liveMode = (data.mode == "pushback") ? "pushback" : "overwrite";
|
||||||
|
|
||||||
//Throw stream lock
|
//Throw stream lock
|
||||||
this.streamLock = true;
|
this.streamLock = true;
|
||||||
|
|
@ -421,46 +404,6 @@ class queue{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async dumpQueue(socket, data){
|
|
||||||
//If we somehow got here while config.debug is disabled, or the user isn't allowed to preform server-side debugging
|
|
||||||
if(!(config.debug && await permissionModel.permCheck(socket.user, "debug"))){
|
|
||||||
//FUCKIN' CHEESE IT!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If a full data dump was requested
|
|
||||||
if(data != null && data.full){
|
|
||||||
//Pull the channel DB doc
|
|
||||||
const chanDB = await channelModel.findOne({name:this.channel.name});
|
|
||||||
//Pull archived media by channel name
|
|
||||||
const archived = await this.getArchive();
|
|
||||||
|
|
||||||
//Cook and emit a new object from all of the data
|
|
||||||
socket.emit("dumpQueue", {
|
|
||||||
cache: {
|
|
||||||
schedule: Array.from(this.schedule),
|
|
||||||
nowPlaying: this.nowPlaying
|
|
||||||
},
|
|
||||||
DB: {
|
|
||||||
schedule: chanDB.media.scheduled,
|
|
||||||
nowPlaying: chanDB.media.nowPlaying,
|
|
||||||
archived,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//DONE.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Otherwise, just dump whats in RAM
|
|
||||||
socket.emit("dumpQueue", {
|
|
||||||
cache: {
|
|
||||||
schedule: Array.from(this.schedule),
|
|
||||||
nowPlaying: this.nowPlaying
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//--- INTERNAL USE ONLY QUEUEING FUNCTIONS ---
|
//--- INTERNAL USE ONLY QUEUEING FUNCTIONS ---
|
||||||
/**
|
/**
|
||||||
* Clears and scheduling timers
|
* Clears and scheduling timers
|
||||||
|
|
@ -604,25 +547,9 @@ class queue{
|
||||||
//For each item
|
//For each item
|
||||||
for(let item of foundItems){
|
for(let item of foundItems){
|
||||||
//Remove media, passing down chanDB so we're not looking again and again
|
//Remove media, passing down chanDB so we're not looking again and again
|
||||||
await this.removeMedia(item.uuid, socket, chanDB, true, true);
|
await this.removeMedia(item.uuid, socket, chanDB);
|
||||||
|
|
||||||
//If this is the current item
|
|
||||||
if(this.nowPlaying != null && item.uuid == this.nowPlaying.uuid){
|
|
||||||
//End it
|
|
||||||
await this.end(false, true, false, chanDB, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Save data to channe database
|
|
||||||
await chanDB.save();
|
|
||||||
|
|
||||||
//Refresh next timer
|
|
||||||
await this.refreshNextTimer();
|
|
||||||
|
|
||||||
//Broadcast Queue
|
|
||||||
await this.broadcastQueue(chanDB);
|
|
||||||
|
|
||||||
}catch(err){
|
}catch(err){
|
||||||
//If this was originated by someone
|
//If this was originated by someone
|
||||||
if(socket != null){
|
if(socket != null){
|
||||||
|
|
@ -670,8 +597,6 @@ class queue{
|
||||||
|
|
||||||
//Find our media, don't remove it yet since we want to do some more testing first
|
//Find our media, don't remove it yet since we want to do some more testing first
|
||||||
let media = this.getItemByUUID(uuid);
|
let media = this.getItemByUUID(uuid);
|
||||||
//Create value to hold old media in-case somethin' fucks up
|
|
||||||
let oldMedia = null;
|
|
||||||
|
|
||||||
//If we got a bad request
|
//If we got a bad request
|
||||||
if(media == null){
|
if(media == null){
|
||||||
|
|
@ -688,8 +613,6 @@ class queue{
|
||||||
if(media.startTime < new Date().getTime()){
|
if(media.startTime < new Date().getTime()){
|
||||||
//If the item is currently playing
|
//If the item is currently playing
|
||||||
if(media.getEndTime() > new Date().getTime()){
|
if(media.getEndTime() > new Date().getTime()){
|
||||||
//Set old media
|
|
||||||
oldMedia = media;
|
|
||||||
//Dupe media for the rest of the function
|
//Dupe media for the rest of the function
|
||||||
media = media.clone();
|
media = media.clone();
|
||||||
|
|
||||||
|
|
@ -731,13 +654,6 @@ class queue{
|
||||||
//Reset the start time stamp for re-calculation
|
//Reset the start time stamp for re-calculation
|
||||||
media.startTimeStamp = 0;
|
media.startTimeStamp = 0;
|
||||||
|
|
||||||
//If there's a cut-off from moving a now-playing item
|
|
||||||
if(oldMedia != null){
|
|
||||||
//Remove it from the queue to prevent ghost items
|
|
||||||
this.removeMedia(oldMedia.uuid, socket, chanDB, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Schedule in old slot with noSave enabled
|
//Schedule in old slot with noSave enabled
|
||||||
await this.scheduleMedia([media], socket, chanDB, true);
|
await this.scheduleMedia([media], socket, chanDB, true);
|
||||||
}
|
}
|
||||||
|
|
@ -760,11 +676,10 @@ class queue{
|
||||||
* @param {String} uuid - UUID of item to reschedule
|
* @param {String} uuid - UUID of item to reschedule
|
||||||
* @param {Socket} socket - Requesting Socket
|
* @param {Socket} socket - Requesting Socket
|
||||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||||
* @param {Boolean} noScheduling - Disables schedule timer refresh and this.save() calls if true, good for internal function calls
|
* @param {Boolean} noScheduling - Disables schedule timer refresh if true
|
||||||
* @param {Boolean} noBroadcast - Disables schedule broadcasting to clients, good for internal function calls
|
|
||||||
* @returns {Media} Deleted Media Item
|
* @returns {Media} Deleted Media Item
|
||||||
*/
|
*/
|
||||||
async removeMedia(uuid, socket, chanDB, noScheduling = false, noBroadcast = false){
|
async removeMedia(uuid, socket, chanDB, noScheduling = false){
|
||||||
//If we're streamlocked
|
//If we're streamlocked
|
||||||
if(this.streamLock){
|
if(this.streamLock){
|
||||||
//If an originating socket was provided for this request
|
//If an originating socket was provided for this request
|
||||||
|
|
@ -779,14 +694,31 @@ class queue{
|
||||||
//Get requested media
|
//Get requested media
|
||||||
const media = this.getItemByUUID(uuid);
|
const media = this.getItemByUUID(uuid);
|
||||||
|
|
||||||
//If we couldn't find anything in the current channel schedule
|
//If we got a bad request
|
||||||
if(media == null){
|
if(media == null){
|
||||||
try{
|
try{
|
||||||
//Attempt to delete matching item from archived media
|
//If we wheren't handed a channel
|
||||||
const deletedArchive = await archivedMediaModel.deleteOne({channel: this.channel._id, uuid: new BSON.UUID(uuid)});
|
if(chanDB == null){
|
||||||
|
//DO everything ourselves since we don't have a fance end() function to do it
|
||||||
|
chanDB = await channelModel.findOne({name:this.channel.name});
|
||||||
|
}
|
||||||
|
|
||||||
//if nothing got deleted
|
//If we couldn't find the channel
|
||||||
if(deletedArchive == null || deletedArchive.deletedCount == 0){
|
if(chanDB == null){
|
||||||
|
//FUCK
|
||||||
|
throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while queue item!`, "queue");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Keep a copy of the archive that hasn't been changed
|
||||||
|
const preArchive = chanDB.media.archived;
|
||||||
|
|
||||||
|
//Filter out the requested item from the archive
|
||||||
|
chanDB.media.archived = chanDB.media.archived.filter((record)=>{
|
||||||
|
return record.uuid.toString() != uuid;
|
||||||
|
})
|
||||||
|
|
||||||
|
//If nothing changed in the archive
|
||||||
|
if(preArchive.length == chanDB.media.archived.length){
|
||||||
//If an originating socket was provided for this request
|
//If an originating socket was provided for this request
|
||||||
if(socket != null){
|
if(socket != null){
|
||||||
//Yell at the user for being an asshole
|
//Yell at the user for being an asshole
|
||||||
|
|
@ -794,11 +726,10 @@ class queue{
|
||||||
}
|
}
|
||||||
//Otherwise
|
//Otherwise
|
||||||
}else{
|
}else{
|
||||||
//If broadcasting is enabled
|
//Broadcast changes
|
||||||
if(!noBroadcast){
|
this.broadcastQueue(chanDB);
|
||||||
//Broadcast changes
|
//Save changes to the DB
|
||||||
this.broadcastQueue();
|
await chanDB.save();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
//If this was originated by someone
|
//If this was originated by someone
|
||||||
|
|
@ -856,18 +787,12 @@ class queue{
|
||||||
await chanDB.save();
|
await chanDB.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
//If broadcasting is enabled
|
//Broadcast the channel
|
||||||
if(!noBroadcast){
|
this.broadcastQueue(chanDB);
|
||||||
//Broadcast the channel
|
|
||||||
this.broadcastQueue(chanDB);
|
|
||||||
}
|
|
||||||
|
|
||||||
}catch(err){
|
}catch(err){
|
||||||
//If broadcasting is enabled
|
//Broadcast the channel
|
||||||
if(!noBroadcast){
|
this.broadcastQueue();
|
||||||
//Broadcast the channel
|
|
||||||
this.broadcastQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
//If this was originated by someone
|
//If this was originated by someone
|
||||||
if(socket != null){
|
if(socket != null){
|
||||||
|
|
@ -1108,7 +1033,6 @@ class queue{
|
||||||
if(this.nowPlaying != null){
|
if(this.nowPlaying != null){
|
||||||
//Silently end the media in RAM so the database isn't stepping on itself up ahead
|
//Silently end the media in RAM so the database isn't stepping on itself up ahead
|
||||||
//Alternatively we could've used await, but then we'd be doubling up on DB transactions :P
|
//Alternatively we could've used await, but then we'd be doubling up on DB transactions :P
|
||||||
//VOLATILE END
|
|
||||||
this.end(true, true, true);
|
this.end(true, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1134,7 +1058,7 @@ class queue{
|
||||||
//If nowPlaying isn't null and isn't what we're about to throw on
|
//If nowPlaying isn't null and isn't what we're about to throw on
|
||||||
if(chanDB.media.nowPlaying != null && chanDB.media.nowPlaying.uuid.toString() != mediaObj.uuid){
|
if(chanDB.media.nowPlaying != null && chanDB.media.nowPlaying.uuid.toString() != mediaObj.uuid){
|
||||||
//Archive whats already in there since we're about to clobber the fuck out of it
|
//Archive whats already in there since we're about to clobber the fuck out of it
|
||||||
this.archiveMedia(chanDB.media.nowPlaying);
|
chanDB.media.archived.push(chanDB.media.nowPlaying);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Set the now playing queued media document
|
//Set the now playing queued media document
|
||||||
|
|
@ -1147,9 +1071,6 @@ class queue{
|
||||||
|
|
||||||
//Save the channel
|
//Save the channel
|
||||||
await chanDB.save();
|
await chanDB.save();
|
||||||
|
|
||||||
//Broadcast queue since we ended our media item early
|
|
||||||
this.broadcastQueue(chanDB);
|
|
||||||
}catch(err){
|
}catch(err){
|
||||||
loggerUtils.localExceptionHandler(err);
|
loggerUtils.localExceptionHandler(err);
|
||||||
}
|
}
|
||||||
|
|
@ -1205,7 +1126,7 @@ class queue{
|
||||||
* End currently playing media
|
* End currently playing media
|
||||||
* @param {Boolean} quiet - Enable to prevent ending the media client-side
|
* @param {Boolean} quiet - Enable to prevent ending the media client-side
|
||||||
* @param {Boolean} noArchive - Enable to prevent ended media from being written to channel archive. Deletes media if Volatile is false
|
* @param {Boolean} noArchive - Enable to prevent ended media from being written to channel archive. Deletes media if Volatile is false
|
||||||
* @param {Boolean} volatile - Enable to prevent DB Transactions, also prevents queue broadcasting so the calling code can broadcast once it's done.
|
* @param {Boolean} volatile - Enable to prevent DB Transactions
|
||||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||||
*/
|
*/
|
||||||
async end(quiet = false, noArchive = false, volatile = false, chanDB){
|
async end(quiet = false, noArchive = false, volatile = false, chanDB){
|
||||||
|
|
@ -1243,13 +1164,6 @@ class queue{
|
||||||
return this.endLivestream(wasPlaying, chanDB)
|
return this.endLivestream(wasPlaying, chanDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Moved this from the block below to prevent accidental over-caching
|
|
||||||
//We may need to throw this into it's own conditional if it causes issues
|
|
||||||
//Take it out of the active schedule
|
|
||||||
//Ultimitaly though, if something is voltaile it should handle saving chanDB on its own, so this should be a non-issue
|
|
||||||
this.schedule.delete(wasPlaying.startTime);
|
|
||||||
|
|
||||||
//If we're not in volatile mode and we're not ending a livestream
|
//If we're not in volatile mode and we're not ending a livestream
|
||||||
if(!volatile){
|
if(!volatile){
|
||||||
//If we wheren't handed a channel
|
//If we wheren't handed a channel
|
||||||
|
|
@ -1270,10 +1184,13 @@ class queue{
|
||||||
await chanDB.media.nowPlaying.deleteOne();
|
await chanDB.media.nowPlaying.deleteOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Take it out of the active schedule
|
||||||
|
this.schedule.delete(wasPlaying.startTime);
|
||||||
|
|
||||||
//If archiving is enabled
|
//If archiving is enabled
|
||||||
if(!noArchive){
|
if(!noArchive){
|
||||||
//Add the item to the channel archive
|
//Add the item to the channel archive
|
||||||
await this.archiveMedia(wasPlaying);
|
chanDB.media.archived.push(wasPlaying);
|
||||||
}
|
}
|
||||||
|
|
||||||
//broadcast queue using unsaved archive, run this before chanDB.save() for better responsiveness
|
//broadcast queue using unsaved archive, run this before chanDB.save() for better responsiveness
|
||||||
|
|
@ -1281,6 +1198,9 @@ class queue{
|
||||||
|
|
||||||
//Save our changes to the DB
|
//Save our changes to the DB
|
||||||
await chanDB.save();
|
await chanDB.save();
|
||||||
|
}else{
|
||||||
|
//broadcast queue using unsaved archive
|
||||||
|
this.broadcastQueue(chanDB);
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
this.broadcastQueue();
|
this.broadcastQueue();
|
||||||
|
|
@ -1372,7 +1292,7 @@ class queue{
|
||||||
let finished = false;
|
let finished = false;
|
||||||
|
|
||||||
//Throw the livestream into the archive
|
//Throw the livestream into the archive
|
||||||
this.archiveMedia(wasPlaying);
|
chanDB.media.archived.push(wasPlaying);
|
||||||
|
|
||||||
//Save the DB
|
//Save the DB
|
||||||
await chanDB.save();
|
await chanDB.save();
|
||||||
|
|
@ -1457,7 +1377,7 @@ class queue{
|
||||||
}
|
}
|
||||||
|
|
||||||
//Throw the livestream into the archive
|
//Throw the livestream into the archive
|
||||||
this.archiveMedia(wasPlaying);
|
chanDB.media.archived.push(wasPlaying);
|
||||||
|
|
||||||
//Set the current place to schedule items at 5ms after the end of the live stream
|
//Set the current place to schedule items at 5ms after the end of the live stream
|
||||||
let curPlace = wasPlaying.getEndTime() + 5;
|
let curPlace = wasPlaying.getEndTime() + 5;
|
||||||
|
|
@ -1495,7 +1415,7 @@ class queue{
|
||||||
const mediaObj = entry[1];
|
const mediaObj = entry[1];
|
||||||
|
|
||||||
//Remove media from queue without calling chanDB.save() to make room before we move everything
|
//Remove media from queue without calling chanDB.save() to make room before we move everything
|
||||||
await this.removeMedia(mediaObj.uuid, null, chanDB, true, true);
|
await this.removeMedia(mediaObj.uuid, null, chanDB, true);
|
||||||
|
|
||||||
mediaObj.genUUID();
|
mediaObj.genUUID();
|
||||||
|
|
||||||
|
|
@ -1511,9 +1431,6 @@ class queue{
|
||||||
|
|
||||||
//Schedule the moved schedule, letting scheduleMedia save our changes for us, starting w/o saves to prevent over-saving
|
//Schedule the moved schedule, letting scheduleMedia save our changes for us, starting w/o saves to prevent over-saving
|
||||||
await this.scheduleMedia(newSched, null, chanDB);
|
await this.scheduleMedia(newSched, null, chanDB);
|
||||||
|
|
||||||
//Broadcast the queue now that everything is hunky dory
|
|
||||||
await this.broadcastQueue(chanDB);
|
|
||||||
}catch(err){
|
}catch(err){
|
||||||
//Null out live remainder for the next stream
|
//Null out live remainder for the next stream
|
||||||
this.liveRemainder = null;
|
this.liveRemainder = null;
|
||||||
|
|
@ -1690,18 +1607,7 @@ class queue{
|
||||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||||
*/
|
*/
|
||||||
async broadcastQueue(chanDB){
|
async broadcastQueue(chanDB){
|
||||||
//Broadcast queue to authenticated sockets within the channels room inside the protected 'queue-broadcast' namespace
|
this.server.io.in(this.channel.name).emit('queue',{queue: await this.prepQueue(chanDB)});
|
||||||
this.server.queueBroadcastManager.namespace.in(this.channel.name).emit('queue',{queue: await this.prepQueue(chanDB)});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcasts channel queue to a single socket
|
|
||||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
|
||||||
* @param {Socket} socket - Socket to send queue to
|
|
||||||
*/
|
|
||||||
async emitQueue(chanDB, socket){
|
|
||||||
//Broadcast queue to authenticated sockets within the channels room inside the protected 'queue-broadcast' namespace
|
|
||||||
socket.emit('queue',{queue: await this.prepQueue(chanDB)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1726,28 +1632,30 @@ class queue{
|
||||||
//Create an empty array to hold our schedule
|
//Create an empty array to hold our schedule
|
||||||
let schedule = [];
|
let schedule = [];
|
||||||
|
|
||||||
//Get todays epoch
|
//Get yestedays epoch
|
||||||
const yesterday = new Date();
|
const yesterday = new Date().setDate(new Date().getDate() - 1);
|
||||||
|
|
||||||
//Make it yesterdays
|
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
|
||||||
|
|
||||||
//Get archive since yesterday
|
|
||||||
const archive = await this.getArchive(yesterday);
|
|
||||||
|
|
||||||
//Iterate through the channel archive backwards to save time
|
//Iterate through the channel archive backwards to save time
|
||||||
for(let mediaIndex = archive.length - 1; mediaIndex >= 0; mediaIndex--){
|
for(let mediaIndex = chanDB.media.archived.length - 1; mediaIndex >= 0; mediaIndex--){
|
||||||
//Grab the current media record
|
//Grab the current media record
|
||||||
let media = archive[mediaIndex].rehydrate();
|
let media = chanDB.media.archived[mediaIndex].rehydrate();
|
||||||
|
|
||||||
//If we're sending out the live remainder during a live stream
|
//If the media started within the last 24 hours
|
||||||
if(this.liveRemainder != null && media.uuid.toString() == this.liveRemainder.uuid.toString()){
|
if(media.startTime > yesterday){
|
||||||
//Throw out the early end before sending it off, so it looks like it hasn't been cut off yet (smoke n mirrors :P)
|
//If we're sending out the live remainder during a live stream
|
||||||
media.earlyEnd = null;
|
if(this.liveRemainder != null && media.uuid.toString() == this.liveRemainder.uuid.toString()){
|
||||||
|
//Throw out the early end before sending it off, so it looks like it hasn't been cut off yet (smoke n mirrors :P)
|
||||||
|
media.earlyEnd = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add it to the schedule array as if it where part of the actual schedule map
|
||||||
|
schedule.push([media.startTime, media]);
|
||||||
|
//Otherwise if it's older
|
||||||
|
}else{
|
||||||
|
//Then we should be done as archived items are added as they are played/end.
|
||||||
|
//No newer items should be beyond this point!
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add it to the temporary schedule array as if it where part of the actual schedule map
|
|
||||||
schedule.unshift([media.startTime, media]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Concatonate the actual schedule to the items we pulled out of the archive return it
|
//Concatonate the actual schedule to the items we pulled out of the archive return it
|
||||||
|
|
@ -1797,7 +1705,7 @@ class queue{
|
||||||
chanDB.media.nowPlaying = null;
|
chanDB.media.nowPlaying = null;
|
||||||
|
|
||||||
//Archive the bitch
|
//Archive the bitch
|
||||||
this.archiveMedia(wasPlaying);
|
chanDB.media.archived.push(wasPlaying);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1827,36 +1735,36 @@ class queue{
|
||||||
//If it's been ended
|
//If it's been ended
|
||||||
}else{
|
}else{
|
||||||
//Archive ended media
|
//Archive ended media
|
||||||
this.archiveMedia(record);
|
chanDB.media.archived.push(record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//If we have a remainder from a livestream
|
//If we have a remainder from a livestream
|
||||||
if(chanDB.media.liveRemainder){
|
if(chanDB.media.liveRemainder){
|
||||||
let archive = await this.getArchive();
|
//Iterate backwards through the archive to pull the newest first, since that's probably where this fucker is
|
||||||
|
for(let archiveIndex = (chanDB.media.archived.length - 1); archiveIndex > 0; archiveIndex--){
|
||||||
|
//Grab the current media object
|
||||||
|
const archivedMedia = chanDB.media.archived[archiveIndex];
|
||||||
|
|
||||||
|
|
||||||
//Iterate backwards through channel archive
|
|
||||||
for(let archiveIndex = (archive.length - 1); archiveIndex > 0; archiveIndex--){
|
|
||||||
//If the current object matches our remainder UUID
|
//If the current object matches our remainder UUID
|
||||||
if((archive[archiveIndex].uuid.toString() == chanDB.media.liveRemainder.toString())){
|
if((archivedMedia.uuid.toString() == chanDB.media.liveRemainder.toString())){
|
||||||
//Null out any early end
|
//Null out any early end
|
||||||
archive[archiveIndex].earlyEnd = null;
|
archivedMedia.earlyEnd = null;
|
||||||
|
|
||||||
//Re-hydrate the item
|
//Re-hydrate the item
|
||||||
const archivedMediaObject = archive[archiveIndex].rehydrate();
|
const archivedMediaObject = archivedMedia.rehydrate();
|
||||||
|
|
||||||
//if we still have a video to finish
|
//if we still have a video to finish
|
||||||
if(archivedMediaObject.getEndTime() > now){
|
if(archivedMediaObject.getEndTime() > now){
|
||||||
//Convert the fucker back to queuedMedia and save as now playing
|
//Set the fucker as now playing
|
||||||
chanDB.media.nowPlaying = queuedMedia.fromArchivedMedia(archivedMediaObject);
|
chanDB.media.nowPlaying = archivedMediaObject;
|
||||||
|
|
||||||
//Schedule the fucker in RAM, w/ the start function also running in RAM-Only mode
|
//Schedule the fucker in RAM, w/ the start function also running in RAM-Only mode
|
||||||
this.scheduleMedia([archivedMediaObject], null, chanDB, true, true, true);
|
this.scheduleMedia([archivedMediaObject], null, chanDB, true, true, true);
|
||||||
|
|
||||||
//Splice the fucker out of the archive
|
//Splice the fucker out of the archive
|
||||||
await archivedMediaModel.deleteOne({channel: this.channel._id, uuid: new BSON.UUID(chanDB.media.liveRemainder)});
|
chanDB.media.archived.splice(archiveIndex, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Break out of the loop
|
//Break out of the loop
|
||||||
|
|
@ -1869,9 +1777,7 @@ class queue{
|
||||||
chanDB.media.scheduled = newSched;
|
chanDB.media.scheduled = newSched;
|
||||||
|
|
||||||
//Save the DB
|
//Save the DB
|
||||||
await chanDB.save()
|
await chanDB.save();
|
||||||
|
|
||||||
//End the media;
|
|
||||||
|
|
||||||
//if something fucked up
|
//if something fucked up
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
|
@ -1879,36 +1785,6 @@ class queue{
|
||||||
loggerUtils.localExceptionHandler(err);
|
loggerUtils.localExceptionHandler(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Commits a queuedMedia object to the media archives
|
|
||||||
* @param {queuedMedia} media - Media object to be archived
|
|
||||||
*/
|
|
||||||
async archiveMedia(media){
|
|
||||||
//Convert queuedMedia object to archivedMedia object
|
|
||||||
let archived = archivedMedia.fromQueuedMedia(media, this.channel._id);
|
|
||||||
//Save archivedMedia object to site-wide media archive (gross but performant)
|
|
||||||
let archivedDoc = await archivedMediaModel.create(archived);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Channel Archive
|
|
||||||
*/
|
|
||||||
async getArchive(startDate){
|
|
||||||
//If we provided a start date
|
|
||||||
if(startDate != null){
|
|
||||||
//Return archived media after start date
|
|
||||||
return await archivedMediaModel.find({
|
|
||||||
channel:this.channel._id,
|
|
||||||
//Prevent mongoose from attempting to sanatize and invalidate our query object, plus I think we can trust an epoch that came from server-side :)
|
|
||||||
startTime: mongoose.trusted({$gt: startDate.getTime()})
|
|
||||||
});
|
|
||||||
//Otherwise
|
|
||||||
}else{
|
|
||||||
//return all archived media
|
|
||||||
return await archivedMediaModel.find({channel:this.channel._id});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = queue;
|
module.exports = queue;
|
||||||
|
|
@ -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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -88,27 +88,6 @@ class queuedMedia extends media{
|
||||||
startTimeStamp);
|
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
|
* Converts array of media objects into array of queuedMedia objects
|
||||||
* @param {Array} mediaList - Array of media objects to queue
|
* @param {Array} mediaList - Array of media objects to queue
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -16,8 +16,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
//Local Imports
|
//Local Imports
|
||||||
const tokeCommandModel = require('../../schemas/tokebot/tokeCommandSchema');
|
const tokeCommandModel = require('../../schemas/tokebot/tokeCommandSchema');
|
||||||
const tokeModel = require('../../schemas/tokebot/tokeSchema');
|
|
||||||
const {userModel} = require('../../schemas/user/userSchema');
|
const {userModel} = require('../../schemas/user/userSchema');
|
||||||
|
const statSchema = require('../../schemas/statSchema');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -148,15 +148,6 @@ class tokebot{
|
||||||
|
|
||||||
//Add the toking user to the tokers map
|
//Add the toking user to the tokers map
|
||||||
this.tokers.set(commandObj.socket.user.user, commandObj.argumentArray[0].toLowerCase());
|
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
|
//If the user is already in the toke
|
||||||
}else{
|
}else{
|
||||||
//Tell them to fuck off
|
//Tell them to fuck off
|
||||||
|
|
@ -199,8 +190,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
|
//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);
|
userModel.tattooToke(this.tokers);
|
||||||
//Do the same for the global toke statistics collection
|
//Do the same for the global stat schema
|
||||||
tokeModel.tattooToke(this.tokers);
|
statSchema.tattooToke(this.tokers);
|
||||||
|
|
||||||
//Set the toke cooldown
|
//Set the toke cooldown
|
||||||
this.cooldownCounter = this.cooldownTime;
|
this.cooldownCounter = this.cooldownTime;
|
||||||
|
|
@ -219,7 +210,7 @@ class tokebot{
|
||||||
//Decrement toke time
|
//Decrement toke time
|
||||||
this.tokeCounter--;
|
this.tokeCounter--;
|
||||||
//try again in another second
|
//try again in another second
|
||||||
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000);
|
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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);
|
res.status(404);
|
||||||
|
|
||||||
//Render page
|
//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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//Config
|
||||||
const config = require('../../config.json');
|
const config = require('../../config.json');
|
||||||
|
|
||||||
//NPM Imports
|
|
||||||
const validator = require('validator');//No express here, so regular validator it is!
|
|
||||||
|
|
||||||
//Local Imports
|
//Local Imports
|
||||||
const {userModel} = require('../schemas/user/userSchema');
|
const {userModel} = require('../schemas/user/userSchema');
|
||||||
const permissionModel = require('../schemas/permissionSchema');
|
const permissionModel = require('../schemas/permissionSchema');
|
||||||
|
|
@ -42,14 +39,13 @@ module.exports.get = async function(req, res){
|
||||||
|
|
||||||
//Render out the page
|
//Render out the page
|
||||||
return res.render('adminPanel', {
|
return res.render('adminPanel', {
|
||||||
instance: config.instanceName, links: config.links,
|
instance: config.instanceName,
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
rankEnum: permissionModel.rankEnum,
|
rankEnum: permissionModel.rankEnum,
|
||||||
chanGuide: chanGuide,
|
chanGuide: chanGuide,
|
||||||
userList: userList,
|
userList: userList,
|
||||||
permList: permList,
|
permList: permList,
|
||||||
csrfToken: csrfUtils.generateToken(req),
|
csrfToken: csrfUtils.generateToken(req)
|
||||||
unescape: validator.unescape
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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);
|
const data = matchedData(req);
|
||||||
|
|
||||||
//make sure we're not bullshitting ourselves here.
|
//make sure we're not bullshitting ourselves here.
|
||||||
if(user == null || user.user == null){
|
if(user == null){
|
||||||
return errorHandler(res, 'You must be logged in to delete your account!', 'unauthorized');
|
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){
|
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);
|
await userDB.nuke(data.pass);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -43,7 +43,7 @@ module.exports.post = async function(req, res){
|
||||||
|
|
||||||
//Check to make sure the user is logged in
|
//Check to make sure the user is logged in
|
||||||
if(req.session.user == null){
|
if(req.session.user == null){
|
||||||
return errorHandler(res, "Invalid user!");
|
errorHandler(res, "Invalid user!");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Authenticate and find user model from DB
|
//Authenticate and find user model from DB
|
||||||
|
|
@ -51,22 +51,11 @@ module.exports.post = async function(req, res){
|
||||||
|
|
||||||
//If we have an invalid user
|
//If we have an invalid user
|
||||||
if(userDB == null){
|
if(userDB == null){
|
||||||
return errorHandler(res, "Invalid user!");
|
errorHandler(res, "Invalid user!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(userDB.email == email){
|
if(userDB.email == email){
|
||||||
return errorHandler(res, "Cannot set current email!");
|
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!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Generate the password reset link
|
//Generate the password reset link
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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');
|
const {validationResult, matchedData} = require('express-validator');
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const migrationModel = require('../../../schemas/user/migrationSchema.js');
|
|
||||||
const rememberMeModel = require('../../../schemas/user/rememberMeSchema.js');
|
|
||||||
const sessionUtils = require('../../../utils/sessionUtils');
|
const sessionUtils = require('../../../utils/sessionUtils');
|
||||||
const hashUtils = require('../../../utils/hashUtils.js');
|
|
||||||
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||||
|
const altchaUtils = require('../../../utils/altchaUtils');
|
||||||
|
const session = require('express-session');
|
||||||
|
|
||||||
//api account functions
|
//api account functions
|
||||||
module.exports.post = async function(req, res){
|
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 we don't have errors
|
||||||
if(validResult.isEmpty()){
|
if(validResult.isEmpty()){
|
||||||
//Pull sanatzied/validated data
|
//Pull sanatzied/validated data
|
||||||
const data = matchedData(req);
|
const {user, pass} = matchedData(req);
|
||||||
|
|
||||||
//try to authenticate the session, throwing an error and breaking the current code block if user is un-authorized
|
//try to authenticate the session, and return a successful code if it works
|
||||||
const userDB = await sessionUtils.authenticateSession(data.user, data.pass, req);
|
await sessionUtils.authenticateSession(user, 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
|
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
}else{
|
}else{
|
||||||
res.status(400);
|
res.status(400);
|
||||||
|
|
@ -87,35 +51,21 @@ module.exports.post = async function(req, res){
|
||||||
//if we don't have errors
|
//if we don't have errors
|
||||||
if(validResult.isEmpty()){
|
if(validResult.isEmpty()){
|
||||||
//Get login attempts for current user
|
//Get login attempts for current user
|
||||||
const {user, pass} = matchedData(req);
|
const {user} = 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 attempts = sessionUtils.getLoginAttempts(user)
|
const attempts = sessionUtils.getLoginAttempts(user)
|
||||||
|
|
||||||
//if we've gone over max attempts
|
//if we've gone over max attempts and
|
||||||
if(attempts != null && attempts.count > sessionUtils.throttleAttempts){
|
if(attempts.count > sessionUtils.throttleAttempts){
|
||||||
//tell client it needs a captcha
|
//tell client it needs a captcha
|
||||||
return res.sendStatus(429);
|
return res.sendStatus(429);
|
||||||
}else{
|
|
||||||
//Scream about any un-caught errors
|
|
||||||
return exceptionHandler(res, err);
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
res.status(400);
|
res.status(400);
|
||||||
return res.send({errors: validResult.array()})
|
return res.send({errors: validResult.array()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
return exceptionHandler(res, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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/>.*/
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const rememberMeModel = require('../../../schemas/user/rememberMeSchema');
|
const accountUtils = require('../../../utils/sessionUtils');
|
||||||
const sessionUtils = require('../../../utils/sessionUtils');
|
const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils');
|
||||||
const {exceptionHandler} = require('../../../utils/loggerUtils');
|
|
||||||
const {validationResult, matchedData} = require('express-validator');
|
|
||||||
|
|
||||||
module.exports.post = async function(req, res){
|
module.exports.post = async function(req, res){
|
||||||
if(req.session.user){
|
if(req.session.user){
|
||||||
try{
|
try{
|
||||||
sessionUtils.killSession(req.session);
|
accountUtils.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
|
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
}catch(err){
|
}catch(err){
|
||||||
return exceptionHandler(res, 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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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 {field, change} = data;
|
||||||
const {user} = req.session;
|
const {user} = req.session;
|
||||||
|
|
||||||
//If the user is null
|
const userDB = await userModel.findOne(user);
|
||||||
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 update = {};
|
const update = {};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -92,7 +86,8 @@ module.exports.post = async function(req, res){
|
||||||
res.status(200);
|
res.status(200);
|
||||||
return res.send(update);
|
return res.send(update);
|
||||||
}else{
|
}else{
|
||||||
return errorHandler(res, 'User not found!', 'unauthorized');
|
res.status(400);
|
||||||
|
return res.send({errors: [{msg:"User not found!"}]});
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
res.status(400);
|
res.status(400);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -22,5 +22,5 @@ const csrfUtils = require('../utils/csrfUtils');
|
||||||
|
|
||||||
//channel functions
|
//channel functions
|
||||||
module.exports.get = function(req, res){
|
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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//Config
|
||||||
const config = require('../../config.json');
|
const config = require('../../config.json');
|
||||||
|
|
||||||
//NPM Imports
|
|
||||||
const validator = require('validator');//No express here, so regular validator it is!
|
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const channelModel = require('../schemas/channel/channelSchema');
|
const channelModel = require('../schemas/channel/channelSchema');
|
||||||
const permissionModel = require('../schemas/permissionSchema');
|
const permissionModel = require('../schemas/permissionSchema');
|
||||||
|
|
@ -42,7 +39,7 @@ module.exports.get = async function(req, res){
|
||||||
throw loggerUtils.exceptionSmith("Channel not found.", "queue");
|
throw loggerUtils.exceptionSmith("Channel not found.", "queue");
|
||||||
}
|
}
|
||||||
|
|
||||||
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){
|
}catch(err){
|
||||||
return exceptionHandler(res, err);
|
return exceptionHandler(res, err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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 we have an invalid request
|
||||||
if(requestDB == null){
|
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)
|
//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
|
//Consume the request
|
||||||
await requestDB.consume();
|
await requestDB.consume();
|
||||||
}else{
|
}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){
|
}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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//Config
|
||||||
const config = require('../../config.json');
|
const config = require('../../config.json');
|
||||||
|
|
||||||
//NPM Imports
|
|
||||||
const validator = require('validator');//No express here, so regular validator it is!
|
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const channelModel = require('../schemas/channel/channelSchema');
|
const channelModel = require('../schemas/channel/channelSchema');
|
||||||
const csrfUtils = require('../utils/csrfUtils');
|
const csrfUtils = require('../utils/csrfUtils');
|
||||||
|
|
@ -29,7 +26,7 @@ const {exceptionHandler, errorHandler} = require('../utils/loggerUtils');
|
||||||
module.exports.get = async function(req, res){
|
module.exports.get = async function(req, res){
|
||||||
try{
|
try{
|
||||||
const chanGuide = await channelModel.getChannelList();
|
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){
|
}catch(err){
|
||||||
return exceptionHandler(res, err);
|
return exceptionHandler(res, err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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 we have previous attempts for this user
|
||||||
if(attempts != null){
|
if(attempts != null){
|
||||||
if(attempts.count > sessionUtils.maxAttempts){
|
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
|
//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);
|
const challenge = await altchaUtils.genCaptcha(difficulty, user);
|
||||||
|
|
||||||
//Render page
|
//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
|
//otherwise
|
||||||
}else{
|
}else{
|
||||||
//Render generic page
|
//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
|
//if we received invalid input
|
||||||
}else{
|
}else{
|
||||||
//Render pretend nothing happened, send out a generic page
|
//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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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();
|
const challenge = await altchaUtils.genCaptcha();
|
||||||
|
|
||||||
//render the page
|
//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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//popout panel container functions
|
||||||
module.exports.get = async function(req, res){
|
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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//NPM Imports
|
||||||
const {validationResult, matchedData} = require('express-validator');
|
const {validationResult, matchedData} = require('express-validator');
|
||||||
|
|
||||||
//NPM Imports
|
|
||||||
const validator = require('validator');//No express here, so regular validator it is!
|
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const presenceUtils = require('../../utils/presenceUtils');
|
|
||||||
const {userModel} = require('../../schemas/user/userSchema');
|
const {userModel} = require('../../schemas/user/userSchema');
|
||||||
const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils');
|
const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils');
|
||||||
|
|
||||||
|
|
@ -34,10 +30,7 @@ module.exports.get = async function(req, res){
|
||||||
const data = matchedData(req);
|
const data = matchedData(req);
|
||||||
const profile = await userModel.findProfile({user: data.user});
|
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)
|
return res.render('partial/panels/profile', {profile});
|
||||||
const presence = await presenceUtils.getPresence(profile.user);
|
|
||||||
|
|
||||||
return res.render('partial/panels/profile', {profile, presence, unescape: validator.unescape});
|
|
||||||
}else{
|
}else{
|
||||||
res.status(400);
|
res.status(400);
|
||||||
return res.send({errors: validResult.array()})
|
return res.send({errors: validResult.array()})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//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
|
//If we didn't get a valid token
|
||||||
}else{
|
}else{
|
||||||
//otherwise render generic page
|
//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){
|
}catch(err){
|
||||||
return exceptionHandler(res, err);
|
return exceptionHandler(res, err);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//Local Imports
|
||||||
const {userModel} = require('../schemas/user/userSchema');
|
const {userModel} = require('../schemas/user/userSchema');
|
||||||
const csrfUtils = require('../utils/csrfUtils');
|
const csrfUtils = require('../utils/csrfUtils');
|
||||||
const presenceUtils = require('../utils/presenceUtils');
|
|
||||||
const {exceptionHandler, errorHandler} = require('../utils/loggerUtils');
|
const {exceptionHandler, errorHandler} = require('../utils/loggerUtils');
|
||||||
|
|
||||||
//NPM Imports
|
|
||||||
const validator = require('validator');//No express here, so regular validator it is!
|
|
||||||
|
|
||||||
//Config
|
//Config
|
||||||
const config = require('../../config.json');
|
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
|
//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;
|
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', {
|
res.render('profile', {
|
||||||
instance: config.instanceName, links: config.links,
|
instance: config.instanceName,
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
profile,
|
profile,
|
||||||
selfProfile,
|
selfProfile,
|
||||||
presence,
|
csrfToken: csrfUtils.generateToken(req)
|
||||||
csrfToken: csrfUtils.generateToken(req),
|
|
||||||
unescape: validator.unescape
|
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
res.render('profile', {
|
res.render('profile', {
|
||||||
instance: config.instanceName, links: config.links,
|
instance: config.instanceName,
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
profile: null,
|
profile: null,
|
||||||
selfProfile: false,
|
selfProfile: false,
|
||||||
presence: null,
|
csrfToken: csrfUtils.generateToken(req)
|
||||||
csrfToken: csrfUtils.generateToken(req),
|
|
||||||
unescape: validator.unescape
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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();
|
const challenge = await altchaUtils.genCaptcha();
|
||||||
|
|
||||||
//Render page
|
//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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//NPM Imports
|
||||||
const {validationResult, matchedData} = require('express-validator');
|
const {validationResult, matchedData} = require('express-validator');
|
||||||
const validator = require('validator');//Because sometimes one isn't enough...
|
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const {userModel} = require('../../schemas/user/userSchema');
|
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 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{
|
}else{
|
||||||
res.status(400);
|
res.status(400);
|
||||||
return res.send({errors: validResult.array()})
|
return res.send({errors: validResult.array()})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//NPM Imports
|
||||||
const {validationResult, matchedData} = require('express-validator');
|
const {validationResult, matchedData} = require('express-validator');
|
||||||
|
|
||||||
//NPM Imports
|
|
||||||
const validator = require('validator');//No express here, so regular validator it is!
|
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const {userModel} = require('../../schemas/user/userSchema');
|
const {userModel} = require('../../schemas/user/userSchema');
|
||||||
const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils');
|
const {exceptionHandler, errorHandler} = require('../../utils/loggerUtils');
|
||||||
|
|
@ -33,10 +30,10 @@ module.exports.get = async function(req, res){
|
||||||
const data = matchedData(req);
|
const data = matchedData(req);
|
||||||
const profile = await userModel.findProfile({user: data.user});
|
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{
|
}else{
|
||||||
res.status(400);
|
res.status(400);
|
||||||
return res.send({errors: validResult.array()});
|
return res.send({errors: validResult.array()})
|
||||||
}
|
}
|
||||||
|
|
||||||
}catch(err){
|
}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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
//npm imports
|
||||||
const { Router } = require('express');
|
const { Router } = require('express');
|
||||||
|
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const permissionSchema = require("../schemas/permissionSchema");
|
const permissionSchema = require("../schemas/permissionSchema");
|
||||||
const adminPanelController = require("../controllers/adminPanelController");
|
const adminPanelController = require("../controllers/adminPanelController");
|
||||||
const presenceUtils = require("../utils/presenceUtils");
|
|
||||||
|
|
||||||
//globals
|
//globals
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
//Use authentication middleware
|
//Use authentication middleware
|
||||||
router.use(permissionSchema.reqPermCheck("adminPanel"))
|
router.use(permissionSchema.reqPermCheck("adminPanel"))
|
||||||
router.use(presenceUtils.presenceMiddleware);
|
|
||||||
|
|
||||||
//routing functions
|
//routing functions
|
||||||
router.get('/', adminPanelController.get);
|
router.get('/', adminPanelController.get);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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 loginController = require("../../controllers/api/account/loginController");
|
||||||
const logoutController = require("../../controllers/api/account/logoutController");
|
const logoutController = require("../../controllers/api/account/logoutController");
|
||||||
const registerController = require("../../controllers/api/account/registerController");
|
const registerController = require("../../controllers/api/account/registerController");
|
||||||
const migrationController = require("../../controllers/api/account/migrationController");
|
|
||||||
const updateController = require("../../controllers/api/account/updateController");
|
const updateController = require("../../controllers/api/account/updateController");
|
||||||
const rankEnumController = require("../../controllers/api/account/rankEnumController");
|
const rankEnumController = require("../../controllers/api/account/rankEnumController");
|
||||||
const passwordResetRequestController = require("../../controllers/api/account/passwordResetRequestController");
|
const passwordResetRequestController = require("../../controllers/api/account/passwordResetRequestController");
|
||||||
|
|
@ -39,32 +38,18 @@ router.post('/login', accountValidator.user(), accountValidator.pass(), loginCon
|
||||||
//logout
|
//logout
|
||||||
router.post('/logout', logoutController.post);
|
router.post('/logout', logoutController.post);
|
||||||
//register
|
//register
|
||||||
router.post('/register',
|
router.post('/register', accountValidator.user(),
|
||||||
accountValidator.user(),
|
|
||||||
accountValidator.securePass(),
|
accountValidator.securePass(),
|
||||||
accountValidator.pass('passConfirm'),
|
accountValidator.pass('passConfirm'),
|
||||||
accountValidator.email(),
|
accountValidator.email(), registerController.post);
|
||||||
registerController.post);
|
|
||||||
|
|
||||||
//migrate legacy profile
|
|
||||||
router.post('/migrate',
|
|
||||||
accountValidator.user(),
|
|
||||||
accountValidator.pass('oldPass'),
|
|
||||||
accountValidator.securePass('newPass'),
|
|
||||||
accountValidator.pass('passConfirm'),
|
|
||||||
migrationController.post);
|
|
||||||
|
|
||||||
//update profile
|
//update profile
|
||||||
router.post('/update',
|
router.post('/update', accountValidator.img(),
|
||||||
accountValidator.img(),
|
|
||||||
accountValidator.bio(),
|
accountValidator.bio(),
|
||||||
accountValidator.signature(),
|
accountValidator.signature(),
|
||||||
accountValidator.pronouns(),
|
accountValidator.pronouns(),
|
||||||
accountValidator.pass('passChange.oldPass'),
|
accountValidator.pass('passChange.oldPass'),
|
||||||
accountValidator.securePass('passChange.newPass'),
|
accountValidator.securePass('passChange.newPass'),
|
||||||
accountValidator.pass('passChange.confirmPass'),
|
accountValidator.pass('passChange.confirmPass'), updateController.post);
|
||||||
updateController.post);
|
|
||||||
|
|
||||||
//rankEnum
|
//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
|
//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);
|
router.get('/rankEnum', rankEnumController.get);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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 channelModel = require("../schemas/channel/channelSchema");
|
||||||
const channelController = require("../controllers/channelController");
|
const channelController = require("../controllers/channelController");
|
||||||
const channelSettingsController = require("../controllers/channelSettingsController");
|
const channelSettingsController = require("../controllers/channelSettingsController");
|
||||||
const presenceUtils = require("../utils/presenceUtils");
|
|
||||||
|
|
||||||
//globals
|
//globals
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
@ -30,9 +29,6 @@ const router = Router();
|
||||||
//User authentication middleware
|
//User authentication middleware
|
||||||
router.use("/*/settings",channelModel.reqPermCheck("manageChannel","/c/"));
|
router.use("/*/settings",channelModel.reqPermCheck("manageChannel","/c/"));
|
||||||
|
|
||||||
//Use presence middleware
|
|
||||||
router.use(presenceUtils.presenceMiddleware);
|
|
||||||
|
|
||||||
//routing functions
|
//routing functions
|
||||||
router.get('/*/settings', channelSettingsController.get);
|
router.get('/*/settings', channelSettingsController.get);
|
||||||
router.get('/*/', channelController.get);
|
router.get('/*/', channelController.get);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
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
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -20,14 +20,10 @@ const { Router } = require('express');
|
||||||
|
|
||||||
//local imports
|
//local imports
|
||||||
const indexController = require("../controllers/indexController");
|
const indexController = require("../controllers/indexController");
|
||||||
const presenceUtils = require("../utils/presenceUtils");
|
|
||||||
|
|
||||||
//globals
|
//globals
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
//Use presence middleware
|
|
||||||
router.use(presenceUtils.presenceMiddleware);
|
|
||||||
|
|
||||||
//routing functions
|
//routing functions
|
||||||
router.get('/', indexController.get);
|
router.get('/', indexController.get);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,30 +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 migrateController = require("../controllers/migrateController");
|
|
||||||
|
|
||||||
//globals
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
//routing functions
|
|
||||||
router.get('/', migrateController.get);
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -21,7 +21,6 @@ const { Router } = require('express');
|
||||||
//local imports
|
//local imports
|
||||||
const permissionSchema = require("../schemas/permissionSchema");
|
const permissionSchema = require("../schemas/permissionSchema");
|
||||||
const newChannelController = require("../controllers/newChannelController");
|
const newChannelController = require("../controllers/newChannelController");
|
||||||
const presenceUtils = require("../utils/presenceUtils");
|
|
||||||
|
|
||||||
//globals
|
//globals
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
@ -29,9 +28,6 @@ const router = Router();
|
||||||
//user authentication middleware
|
//user authentication middleware
|
||||||
router.use("/",permissionSchema.reqPermCheck("registerChannel"));
|
router.use("/",permissionSchema.reqPermCheck("registerChannel"));
|
||||||
|
|
||||||
//Use presence middleware
|
|
||||||
router.use(presenceUtils.presenceMiddleware);
|
|
||||||
|
|
||||||
//routing functions
|
//routing functions
|
||||||
router.get('/', newChannelController.get);
|
router.get('/', newChannelController.get);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -25,7 +25,6 @@ const popoutContainerController = require("../controllers/panel/popoutContainerC
|
||||||
const profileController = require("../controllers/panel/profileController");
|
const profileController = require("../controllers/panel/profileController");
|
||||||
const queueController = require("../controllers/panel/queueController");
|
const queueController = require("../controllers/panel/queueController");
|
||||||
const settingsController = require("../controllers/panel/settingsController");
|
const settingsController = require("../controllers/panel/settingsController");
|
||||||
const pmController = require("../controllers/panel/pmController");
|
|
||||||
//Validators
|
//Validators
|
||||||
const accountValidator = require("../validators/accountValidator");
|
const accountValidator = require("../validators/accountValidator");
|
||||||
|
|
||||||
|
|
@ -39,6 +38,5 @@ router.get('/popoutContainer', popoutContainerController.get);
|
||||||
router.get('/profile', accountValidator.user(), profileController.get);
|
router.get('/profile', accountValidator.user(), profileController.get);
|
||||||
router.get('/queue', queueController.get);
|
router.get('/queue', queueController.get);
|
||||||
router.get('/settings', settingsController.get);
|
router.get('/settings', settingsController.get);
|
||||||
router.get('/pm', pmController.get);
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -89,12 +89,6 @@ const channelPermissionSchema = new mongoose.Schema({
|
||||||
default: "admin",
|
default: "admin",
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
readSchedule: {
|
|
||||||
type: mongoose.SchemaTypes.String,
|
|
||||||
enum: rankEnum,
|
|
||||||
default: "admin",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
scheduleMedia: {
|
scheduleMedia: {
|
||||||
type: mongoose.SchemaTypes.String,
|
type: mongoose.SchemaTypes.String,
|
||||||
enum: rankEnum,
|
enum: rankEnum,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
/*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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -30,7 +30,7 @@ const emoteModel = require('../emoteSchema');
|
||||||
const channelPermissionSchema = require('./channelPermissionSchema');
|
const channelPermissionSchema = require('./channelPermissionSchema');
|
||||||
const channelBanSchema = require('./channelBanSchema');
|
const channelBanSchema = require('./channelBanSchema');
|
||||||
const queuedMediaSchema = require('./media/queuedMediaSchema');
|
const queuedMediaSchema = require('./media/queuedMediaSchema');
|
||||||
const {playlistSchema} = require('./media/playlist/playlistSchema');
|
const playlistSchema = require('./media/playlistSchema');
|
||||||
const chatSchema = require('./chatSchema');
|
const chatSchema = require('./chatSchema');
|
||||||
//Utils
|
//Utils
|
||||||
const { exceptionHandler, errorHandler } = require('../../utils/loggerUtils');
|
const { exceptionHandler, errorHandler } = require('../../utils/loggerUtils');
|
||||||
|
|
@ -60,7 +60,7 @@ const channelSchema = new mongoose.Schema({
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
type: mongoose.SchemaTypes.String,
|
type: mongoose.SchemaTypes.String,
|
||||||
required: true,
|
required: true,
|
||||||
default: "/nonfree/johnny.png"
|
default: "/img/johnny.png"
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
hidden: {
|
hidden: {
|
||||||
|
|
@ -666,6 +666,77 @@ channelSchema.methods.getEmotes = function(){
|
||||||
return emoteList;
|
return emoteList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates Network-Friendly Browser-Digestable list of channel playlists
|
||||||
|
* @returns {Array} Network-Friendly Browser-Digestable list of channel playlists
|
||||||
|
*/
|
||||||
|
channelSchema.methods.getPlaylists = function(){
|
||||||
|
//Create an empty array to hold our emote list
|
||||||
|
const playlists = [];
|
||||||
|
|
||||||
|
//For each channel emote
|
||||||
|
for(let playlist of this.media.playlists){
|
||||||
|
//Push an object with select information from the emote to the emote list
|
||||||
|
playlists.push(playlist.dehydrate());
|
||||||
|
}
|
||||||
|
|
||||||
|
//return the emote list
|
||||||
|
return playlists;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crawls through channel playlists, running a given callback function against each one
|
||||||
|
* @param {Function} cb - Callback function to run against channel playlists
|
||||||
|
*/
|
||||||
|
channelSchema.methods.playlistCrawl = function(cb){
|
||||||
|
for(let listIndex in this.media.playlists){
|
||||||
|
//Grab the associated playlist
|
||||||
|
playlist = this.media.playlists[listIndex];
|
||||||
|
|
||||||
|
//Call the callback with the playlist and list index as arguments
|
||||||
|
cb(playlist, listIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds channel playlist by playlist name
|
||||||
|
* @param {String} name - name of given playlist to find
|
||||||
|
* @returns {Mongoose.Document} - Sub-Document representing a single playlist
|
||||||
|
*/
|
||||||
|
channelSchema.methods.getPlaylistByName = function(name){
|
||||||
|
//Create null value to hold our found playlist
|
||||||
|
let foundPlaylist = null;
|
||||||
|
|
||||||
|
//Crawl through active playlists
|
||||||
|
this.playlistCrawl((playlist, listIndex) => {
|
||||||
|
//If we found a match based on name
|
||||||
|
if(playlist.name == name){
|
||||||
|
//Keep it
|
||||||
|
foundPlaylist = playlist;
|
||||||
|
//Pass down the list index
|
||||||
|
foundPlaylist.listIndex = listIndex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//return the given playlist
|
||||||
|
return foundPlaylist;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes channel playlist by playlist name
|
||||||
|
* @param {String} name - name of given playlist to Delete
|
||||||
|
*/
|
||||||
|
channelSchema.methods.deletePlaylistByName = async function(name){
|
||||||
|
//Find the playlist
|
||||||
|
let playlist = this.getPlaylistByName(name);
|
||||||
|
|
||||||
|
//splice out the given playlist
|
||||||
|
this.media.playlists.splice(playlist.listIndex, 1);
|
||||||
|
|
||||||
|
//save the channel document
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates Network-Friendly Browser-Digestable list of Channel-Wide user bans
|
* Generates Network-Friendly Browser-Digestable list of Channel-Wide user bans
|
||||||
* @returns {Array} Network-Friendly Browser-Digestable list of Channel-Wide user bans
|
* @returns {Array} Network-Friendly Browser-Digestable list of Channel-Wide user bans
|
||||||
|
|
|
||||||
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