Compare commits
No commits in common. "main" and "0.4-indev-hotfix-2" have entirely different histories.
main
...
0.4-indev-
46 changed files with 101 additions and 446 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -10,4 +10,3 @@ server.cert
|
||||||
server.key
|
server.key
|
||||||
www/nonfree/*
|
www/nonfree/*
|
||||||
migration/*
|
migration/*
|
||||||
www/hrt.zip
|
|
||||||
|
|
@ -9,7 +9,7 @@ Canopy
|
||||||
<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://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>
|
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank"><img src="https://img.shields.io/badge/License-AGPL_v3-663366.svg"></a>
|
||||||
|
|
||||||
0.1-Alpha (Panama Red) - Hotfix 3
|
0.4-INDEV Hotfix 2
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Canopy - /ˈkæ.nə.pi/:
|
Canopy - /ˈkæ.nə.pi/:
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,5 @@
|
||||||
"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."
|
"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."
|
||||||
}
|
}
|
||||||
|
|
@ -65,12 +65,6 @@
|
||||||
"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
|
//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."
|
"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."
|
||||||
}
|
}
|
||||||
13
package.json
13
package.json
|
|
@ -1,14 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "canopy-of-alpha",
|
"name": "canopy-of-indev",
|
||||||
"version": "0.1.3",
|
"version": "0.4.2",
|
||||||
"canopyDisplayVersion": "0.1-Alpha (Panama Red) - Hotfix 3",
|
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^7.1.1",
|
"@braintree/sanitize-url": "^7.1.1",
|
||||||
"altcha": "^2.3.0",
|
"altcha": "^1.0.7",
|
||||||
"altcha-lib": "^1.2.0",
|
"altcha-lib": "^1.2.0",
|
||||||
"argon2": "^0.44.0",
|
"argon2": "^0.44.0",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^5.1.1",
|
||||||
"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",
|
"cookie-parser": "^1.4.7",
|
||||||
|
|
@ -20,8 +19,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": "^7.0.9",
|
||||||
"socket.io": "^4.2.0",
|
"socket.io": "^4.8.1",
|
||||||
"youtube-dl-exec": "^3.0.20"
|
"youtube-dl-exec": "^3.0.20"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -212,12 +212,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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class chatPreprocessor{
|
||||||
|
|
||||||
//If we don't pass sanatization/validation turn this car around
|
//If we don't pass sanatization/validation turn this car around
|
||||||
if(!this.sanatizeCommand(commandObj)){
|
if(!this.sanatizeCommand(commandObj)){
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//split the command
|
//split the command
|
||||||
|
|
|
||||||
|
|
@ -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)});
|
||||||
}
|
}
|
||||||
|
|
@ -16,7 +16,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');
|
||||||
const package = require('../../package.json');
|
|
||||||
|
|
||||||
//Local Imports
|
//Local Imports
|
||||||
const csrfUtils = require('../utils/csrfUtils');
|
const csrfUtils = require('../utils/csrfUtils');
|
||||||
|
|
@ -24,5 +23,5 @@ const csrfUtils = require('../utils/csrfUtils');
|
||||||
//register page functions
|
//register page functions
|
||||||
module.exports.get = async function(req, res){
|
module.exports.get = async function(req, res){
|
||||||
//Render page
|
//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)});
|
return res.render('about', {aboutText: config.aboutText, instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)});
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ 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,
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ module.exports.post = async function(req, res){
|
||||||
|
|
||||||
|
|
||||||
//Look through DB and migration cache for existing email
|
//Look through DB and migration cache for existing email
|
||||||
const existingDB = await userModel.findOne({email: new RegExp(`^${email}$`, 'i')});
|
const existingDB = await userModel.findOne({email: new RegExp(email, 'i')});
|
||||||
const needsMigration = userModel.migrationCache.emails.includes(email.toLowerCase());
|
const needsMigration = userModel.migrationCache.emails.includes(email.toLowerCase());
|
||||||
|
|
||||||
//If the email is in use
|
//If the email is in use
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ module.exports.post = async function(req, res){
|
||||||
const {user, pass} = matchedData(req);
|
const {user, pass} = matchedData(req);
|
||||||
|
|
||||||
//Look for the username in the migration DB
|
//Look for the username in the migration DB
|
||||||
const migrationDB = await migrationModel.findOne({user: new RegExp(`^${user}$`, 'i')});
|
const migrationDB = await migrationModel.findOne({user});
|
||||||
|
|
||||||
//If we found a migration profile
|
//If we found a migration profile
|
||||||
if(migrationDB != null){
|
if(migrationDB != null){
|
||||||
|
|
|
||||||
|
|
@ -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)});
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,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), unescape: validator.unescape});
|
||||||
}catch(err){
|
}catch(err){
|
||||||
return exceptionHandler(res, err);
|
return exceptionHandler(res, err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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-2025 Rainbownapkin and the TTN Community
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|
||||||
|
|
||||||
//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)});
|
|
||||||
}
|
|
||||||
|
|
@ -29,7 +29,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), unescape: validator.unescape});
|
||||||
}catch(err){
|
}catch(err){
|
||||||
return exceptionHandler(res, err);
|
return exceptionHandler(res, err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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('migrate', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
return res.render('migrate', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)});
|
||||||
}
|
}
|
||||||
|
|
@ -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)});
|
||||||
}
|
}
|
||||||
|
|
@ -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});
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ module.exports.get = async function(req, res){
|
||||||
const presence = await presenceUtils.getPresence(profile.user);
|
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,
|
||||||
|
|
@ -52,7 +52,7 @@ module.exports.get = async function(req, res){
|
||||||
});
|
});
|
||||||
}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,
|
||||||
|
|
|
||||||
|
|
@ -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,34 +0,0 @@
|
||||||
/*Canopy - The next generation of stoner streaming software
|
|
||||||
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|
||||||
|
|
||||||
//npm imports
|
|
||||||
const { 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;
|
|
||||||
|
|
@ -217,22 +217,13 @@ migrationSchema.statics.ingestLegacyUser = async function(rawProfile){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Pull rank, dropping over-ranked users down to current enum length
|
|
||||||
let rank = Math.min(Math.max(0, profileArray[3]), permissionModel.rankEnum.length - 1);
|
|
||||||
|
|
||||||
//If this user was a mod on the old site
|
|
||||||
if(rank == 2){
|
|
||||||
//Set them up as a mod here
|
|
||||||
rank = permissionModel.rankEnum.length - 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Create migration profile object from scraped info
|
//Create migration profile object from scraped info
|
||||||
const migrationProfile = new this({
|
const migrationProfile = new this({
|
||||||
user: profileArray[1],
|
user: profileArray[1],
|
||||||
pass: profileArray[2],
|
pass: profileArray[2],
|
||||||
//Clamp rank to 0 and the max setting allowed by the rank enum
|
//Clamp rank to 0 and the max setting allowed by the rank enum
|
||||||
rank,
|
rank: Math.min(Math.max(0, profileArray[3]), permissionModel.rankEnum.length - 1),
|
||||||
email: validator.normalizeEmail(profileArray[4]),
|
email: validator.normalizeEmail(profileArray[4]),
|
||||||
date: profileArray[7],
|
date: profileArray[7],
|
||||||
})
|
})
|
||||||
|
|
@ -323,7 +314,7 @@ migrationSchema.statics.buildMigrationCache = async function(){
|
||||||
|
|
||||||
migrationSchema.statics.consumeByUsername = async function(ip, migration){
|
migrationSchema.statics.consumeByUsername = async function(ip, migration){
|
||||||
//Pull migration doc by case-insensitive username
|
//Pull migration doc by case-insensitive username
|
||||||
const migrationDB = await this.findOne({user: new RegExp(`^${migration.user}$`, 'i')});
|
const migrationDB = await this.findOne({user: new RegExp(migration.user, 'i')});
|
||||||
|
|
||||||
//If we have no migration document
|
//If we have no migration document
|
||||||
if(migrationDB == null){
|
if(migrationDB == null){
|
||||||
|
|
|
||||||
|
|
@ -256,13 +256,13 @@ userSchema.statics.register = async function(userObj, ip){
|
||||||
//Check password confirmation matches
|
//Check password confirmation matches
|
||||||
if(pass == passConfirm){
|
if(pass == passConfirm){
|
||||||
//Setup user query
|
//Setup user query
|
||||||
let userQuery = {user: new RegExp(`^${user}$`, 'i')};
|
let userQuery = {user: new RegExp(user, 'i')};
|
||||||
|
|
||||||
//If we have an email
|
//If we have an email
|
||||||
if(email != null && email != ""){
|
if(email != null && email != ""){
|
||||||
userQuery = {$or: [
|
userQuery = {$or: [
|
||||||
userQuery,
|
userQuery,
|
||||||
{email: new RegExp(`^${email}$`, 'i')}
|
{email: new RegExp(email, 'i')}
|
||||||
]};
|
]};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,7 +319,7 @@ userSchema.statics.authenticate = async function(user, pass, failLine = "Bad Use
|
||||||
}
|
}
|
||||||
|
|
||||||
//get the user if it exists
|
//get the user if it exists
|
||||||
const userDB = await this.findOne({ user: new RegExp(`^${user}$`, 'i')});
|
const userDB = await this.findOne({ user: new RegExp(user, 'i')});
|
||||||
|
|
||||||
//if not scream and shout
|
//if not scream and shout
|
||||||
if(!userDB){
|
if(!userDB){
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ const fileNotFoundController = require('./controllers/404Controller');
|
||||||
//Humie-Friendly
|
//Humie-Friendly
|
||||||
const indexRouter = require('./routers/indexRouter');
|
const indexRouter = require('./routers/indexRouter');
|
||||||
const aboutRouter = require('./routers/aboutRouter');
|
const aboutRouter = require('./routers/aboutRouter');
|
||||||
const hrtRouter = require('./routers/hrtRouter');
|
|
||||||
const registerRouter = require('./routers/registerRouter');
|
const registerRouter = require('./routers/registerRouter');
|
||||||
const loginRouter = require('./routers/loginRouter');
|
const loginRouter = require('./routers/loginRouter');
|
||||||
const profileRouter = require('./routers/profileRouter');
|
const profileRouter = require('./routers/profileRouter');
|
||||||
|
|
@ -76,7 +75,6 @@ const apiRouter = require('./routers/apiRouter');
|
||||||
|
|
||||||
//Define Config variables
|
//Define Config variables
|
||||||
const config = require('../config.json');
|
const config = require('../config.json');
|
||||||
const package = require('../package.json');
|
|
||||||
const port = config.port;
|
const port = config.port;
|
||||||
const dbUrl = `mongodb://${config.db.user}:${config.db.pass}@${config.db.address}:${config.db.port}/${config.db.database}`;
|
const dbUrl = `mongodb://${config.db.user}:${config.db.pass}@${config.db.address}:${config.db.port}/${config.db.database}`;
|
||||||
|
|
||||||
|
|
@ -180,7 +178,6 @@ app.use(sessionUtils.rememberMeMiddleware);
|
||||||
//Humie-Friendly
|
//Humie-Friendly
|
||||||
app.use('/', indexRouter);
|
app.use('/', indexRouter);
|
||||||
app.use('/about', aboutRouter);
|
app.use('/about', aboutRouter);
|
||||||
app.use('/hrt', hrtRouter);
|
|
||||||
app.use('/register', registerRouter);
|
app.use('/register', registerRouter);
|
||||||
app.use('/login', loginRouter);
|
app.use('/login', loginRouter);
|
||||||
app.use('/profile', profileRouter);
|
app.use('/profile', profileRouter);
|
||||||
|
|
@ -211,7 +208,7 @@ Might be better if she kicked off everything at once, and ran a while loop to ch
|
||||||
This runs once at server startup, and most startups will run fairly quickly so... Not worth it?*/
|
This runs once at server startup, and most startups will run fairly quickly so... Not worth it?*/
|
||||||
async function asyncKickStart(){
|
async function asyncKickStart(){
|
||||||
//Lettum fuckin' know wassup
|
//Lettum fuckin' know wassup
|
||||||
console.log(`${config.instanceName}(Powered by Canopy ${package.canopyDisplayVersion}) is booting up!`);
|
console.log(`${config.instanceName}(Powered by Canopy) is booting up!`);
|
||||||
|
|
||||||
//Run legacy migration
|
//Run legacy migration
|
||||||
await migrationModel.ingestLegacyDump();
|
await migrationModel.ingestLegacyDump();
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ GNU Affero General Public License for more details.
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.*/
|
||||||
|
|
||||||
//Config
|
|
||||||
const config = require('../../config.json');
|
|
||||||
|
|
||||||
//NPM Imports
|
//NPM Imports
|
||||||
const validator = require('validator');//No express here, so regular validator it is!
|
const validator = require('validator');//No express here, so regular validator it is!
|
||||||
const {sanitizeUrl} = require("@braintree/sanitize-url");
|
const {sanitizeUrl} = require("@braintree/sanitize-url");
|
||||||
|
|
@ -35,15 +32,6 @@ module.exports.cache = new Map();
|
||||||
module.exports.markLink = async function(dirtyLink){
|
module.exports.markLink = async function(dirtyLink){
|
||||||
const link = sanitizeUrl(dirtyLink);
|
const link = sanitizeUrl(dirtyLink);
|
||||||
|
|
||||||
//If this link is referencing this web server
|
|
||||||
if(link.match(new RegExp(`^${config.protocol}://${config.domain}`)) != null){
|
|
||||||
//Lazily return it as a good link, since we know it'll at least return a good 404 page XP
|
|
||||||
return {
|
|
||||||
link,
|
|
||||||
type: "link"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check link cache for the requested link
|
//Check link cache for the requested link
|
||||||
const cachedLink = module.exports.cache.get(link);
|
const cachedLink = module.exports.cache.get(link);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ module.exports.getMediaType = async function(dirtyURL){
|
||||||
}
|
}
|
||||||
|
|
||||||
//If we have link to a resource from archive.org
|
//If we have link to a resource from archive.org
|
||||||
if(match = url.match(/archive\.org\/(?:details|download)\/(.+)/)){
|
if(match = url.match(/archive\.org\/(?:details|download)\/([a-zA-Z0-9\/._-\s\%]+)/)){
|
||||||
//return internet archive upload id and filepath
|
//return internet archive upload id and filepath
|
||||||
return {
|
return {
|
||||||
type: "ia",
|
type: "ia",
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
it was decided that the original cytube fork, fore.st, had been run past it's prime. In summer/fall 2024, work began on a
|
it was decided that the original cytube fork, fore.st, had been run past it's prime. In summer/fall 2024, work began on a
|
||||||
replacement. The resulting software became <a href="https://git.ourfore.st/rainbownapkin/canopy">Canopy</a>, which was
|
replacement. The resulting software became <a href="https://git.ourfore.st/rainbownapkin/canopy">Canopy</a>, which was
|
||||||
first used to run the ourfore.st instance in late 2025.</p>
|
first used to run the ourfore.st instance in late 2025.</p>
|
||||||
<br>
|
|
||||||
<h2>Canopy Ver: <%= version %></h2>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
<%# Canopy - The next generation of stoner streaming software
|
|
||||||
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<%- include('partial/styles', {instance, user}); %>
|
|
||||||
<%- include('partial/csrfToken', {csrfToken}); %>
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/about.css">
|
|
||||||
<title><%= instance %> - DIY HRT Archive</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<%- include('partial/navbar', {user}); %>
|
|
||||||
<div id="about-div">
|
|
||||||
<h1>Bowie's DIY HRT Archive</h1>
|
|
||||||
<div class="dynamic-container" id="about-text">
|
|
||||||
<br>
|
|
||||||
This page is an attempt at putting together everything I know about DIY HRT.
|
|
||||||
<br><br>
|
|
||||||
So far I have used Homebrew Sublingual Oil from Open Gate Labs with great results, and have received a small batch of raw estradoil from Dragon Ordnance.
|
|
||||||
<br><br>
|
|
||||||
I am currently in the process of figuring out brewing my own sublingual oil.
|
|
||||||
<br><br>
|
|
||||||
<a href="/hrt.zip"><h3>This zip file contains everything I know.</h3></a>
|
|
||||||
<br>
|
|
||||||
<span>You should probably use <a href="https://www.torproject.org/">TOR</a> or a <a href="https://mullvad.net">decent VPN</a> in either an <a href="https://tails.net">amnesiac OS</a> or <a href="https://qubes-os.org">dispoable VM.</a> Everything paid w/ either <a href="https://www.getmonero.org/">XMR</a> or <a href="https://mullvad.net/en/blog/sending-cash-use-our-new-address">cash by mail</a>.</span>
|
|
||||||
<br><br>
|
|
||||||
<span class="critical-danger-text">
|
|
||||||
This page is not intended to be a replacement for professional medical advice, merely an attempt at harm reduction for my friends.
|
|
||||||
It should be used at most as a starting point for research. Everyone's HRT experience, and really transition, are unique and individual journeys.
|
|
||||||
Take the time to do the best research you can, to make sure you're starting and continuing yours correctly.
|
|
||||||
</span>
|
|
||||||
<br><br>
|
|
||||||
Much love, and remember to take your meds!
|
|
||||||
<br><br>
|
|
||||||
-rainbownapkin <3
|
|
||||||
<br>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<footer>
|
|
||||||
<%- include('partial/scripts', {user}); %>
|
|
||||||
</footer>
|
|
||||||
</html>
|
|
||||||
|
|
@ -20,24 +20,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
</span>
|
</span>
|
||||||
<span class="navbar-item" id="right-controls">
|
<span class="navbar-item" id="right-controls">
|
||||||
<% if(user){ %>
|
<% if(user){ %>
|
||||||
<p class="navbar-item">Welcome, <a class="navbar-item" id="username" href="/profile"><%= user.user %></a> - <% if(user.rank == "admin"){ %>
|
<p class="navbar-item">Welcome, <a class="navbar-item" id="username" href="/profile"><%= user.user %></a> - <% if(user.rank == "admin"){ %><a href="/adminPanel" title="Admin Panel" class="bi bi-server navbar-item"></a> - <% } %> <a class="navbar-item" href="/about">About</a> - <a class="navbar-item" href="javascript:" id="logout-button">Logout</a></p>
|
||||||
<a href="/adminPanel" title="Admin Panel" class="bi bi-server navbar-item"></a> -
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<% for(link of Object.keys(links)){ %>
|
|
||||||
<a target="_blank" class="navbar-item" href="<%- links[link] %>"><%= link %></a> -
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<a class="navbar-item" href="javascript:" id="logout-button">Logout</a></p>
|
|
||||||
<% }else{ %>
|
<% }else{ %>
|
||||||
<p class="navbar-item">Remember Me:</p>
|
<p class="navbar-item">Remember Me:</p>
|
||||||
<input class="navbar-item login-prompt" id="remember-me" type="checkbox">
|
<input class="navbar-item login-prompt" id="remember-me" type="checkbox">
|
||||||
<input class="navbar-item login-prompt" id="username-prompt" placeholder="username">
|
<input class="navbar-item login-prompt" id="username-prompt" placeholder="username">
|
||||||
<input class="navbar-item login-prompt" id="password-prompt" placeholder="password" type="password">
|
<input class="navbar-item login-prompt" id="password-prompt" placeholder="password" type="password">
|
||||||
<p class="navbar-item"><a class="navbar-item" href="javascript:" id="login-button">Login</a> - <a class="navbar-item" href="/passwordReset">Forgot Password</a> - <a class="navbar-item" href="/register">Register</a>
|
<p class="navbar-item"><a class="navbar-item" href="javascript:" id="login-button">Login</a> - <a class="navbar-item" href="/passwordReset">Forgot Password</a> - <a class="navbar-item" href="/register">Register</a> - <a class="navbar-item" href="/about">About</a></p>
|
||||||
<% for(link of Object.keys(links)){ %>
|
|
||||||
- <a target="_blank" class="navbar-item" href="<%- links[link] %>"><%= link %></a>
|
|
||||||
<% } %></p>
|
|
||||||
<% } %>
|
<% } %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<link rel="stylesheet" type="text/css" href="/css/panel/settings.css">
|
<link rel="stylesheet" type="text/css" href="/css/panel/settings.css">
|
||||||
<div id="settings-panel">
|
<div id="settings-panel">
|
||||||
<h2>Client Settings</h2>
|
<h2>Client Settings</h2>
|
||||||
<h4>Playeback Settings</h4>
|
<h4>Player Settings</h4>
|
||||||
<span id="settings-panel-youtube-source" class="settings-panel-setting">
|
<span id="settings-panel-youtube-source" class="settings-panel-setting">
|
||||||
<p>Youtube Player Type: </p>
|
<p>Youtube Player Type: </p>
|
||||||
<select>
|
<select>
|
||||||
|
|
@ -40,15 +40,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<p>Syncronization Delta: </p>
|
<p>Syncronization Delta: </p>
|
||||||
<input type="number">
|
<input type="number">
|
||||||
</span>
|
</span>
|
||||||
<h4>Display Settings</h4>
|
<h4>Chat Settings</h4>
|
||||||
<span id="settings-panel-min-chat-width" class="settings-panel-setting">
|
<span id="settings-panel-min-chat-width" class="settings-panel-setting">
|
||||||
<p>Chat Width Minimum While Locked to Aspect Ratio: </p>
|
<p>Aspect-Ratio Lock Chat Width Minimum: </p>
|
||||||
<input type="number">
|
<input type="number">
|
||||||
</span>
|
</span>
|
||||||
<span id="settings-panel-disable-portrait" class="settings-panel-setting">
|
|
||||||
<p>Disable Portrait/Mobile Layout: </p>
|
|
||||||
<input type="checkbox">
|
|
||||||
</span>
|
|
||||||
<h4>Notification Settings</h4>
|
<h4>Notification Settings</h4>
|
||||||
<span id="settings-panel-ping-on-pm-rx" class="settings-panel-setting">
|
<span id="settings-panel-ping-on-pm-rx" class="settings-panel-setting">
|
||||||
<p>Play Sound for received PMs: </p>
|
<p>Play Sound for received PMs: </p>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
<filter id="strikethroughFilter" primitiveUnits="objectBoundingBox">
|
<filter id="strikethroughFilter">
|
||||||
<feFlood
|
<feFlood
|
||||||
result="floodFill"
|
result="floodFill"
|
||||||
height="2%"
|
|
||||||
width="100%"
|
|
||||||
x="0"
|
x="0"
|
||||||
y="49%"
|
y="50%"
|
||||||
|
width="100%"
|
||||||
|
height="1"
|
||||||
flood-color="black"
|
flood-color="black"
|
||||||
flood-opacity="1"
|
flood-opacity="1"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 376 B |
|
|
@ -288,16 +288,6 @@ class channel{
|
||||||
//Set Chat Box Width minimum while Locked to Aspect-Ratio
|
//Set Chat Box Width minimum while Locked to Aspect-Ratio
|
||||||
this.chatBox.chatWidthMinimum = value / 100;
|
this.chatBox.chatWidthMinimum = value / 100;
|
||||||
return;
|
return;
|
||||||
case 'disablePortrait':
|
|
||||||
//If the chat isn't loaded
|
|
||||||
if(this.chatBox == null){
|
|
||||||
//We're fuckin' done here
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Toggle portrait mode
|
|
||||||
this.chatBox.togglePortrait();
|
|
||||||
return;
|
|
||||||
case 'userlistHidden':
|
case 'userlistHidden':
|
||||||
//If the userlist class isn't loaded in yet
|
//If the userlist class isn't loaded in yet
|
||||||
if(this.userList == null){
|
if(this.userList == null){
|
||||||
|
|
@ -336,8 +326,7 @@ class channel{
|
||||||
["rxPMSound", 'unread'],
|
["rxPMSound", 'unread'],
|
||||||
["txPMSound", false],
|
["txPMSound", false],
|
||||||
["newSeshSound", true],
|
["newSeshSound", true],
|
||||||
["endSeshSound", true],
|
["endSeshSound", true]
|
||||||
["disablePortrait", false]
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,8 +337,6 @@ function onYouTubeIframeAPIReady(){
|
||||||
//Set embed api to true
|
//Set embed api to true
|
||||||
client.ytEmbedAPILoaded = true;
|
client.ytEmbedAPILoaded = true;
|
||||||
|
|
||||||
//If the player is ready and has a mediaHandler loaded
|
|
||||||
if(client.player != null && client.player.mediaHandler != null){
|
|
||||||
//Get currently playing item
|
//Get currently playing item
|
||||||
const nowPlaying = client.player.mediaHandler.nowPlaying;
|
const nowPlaying = client.player.mediaHandler.nowPlaying;
|
||||||
|
|
||||||
|
|
@ -359,6 +346,5 @@ function onYouTubeIframeAPIReady(){
|
||||||
client.player.start({media: nowPlaying});
|
client.player.start({media: nowPlaying});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const client = new channel();
|
const client = new channel();
|
||||||
|
|
@ -38,11 +38,6 @@ class chatBox{
|
||||||
*/
|
*/
|
||||||
this.autoScroll = true;
|
this.autoScroll = true;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the screen is currently in portrait mode
|
|
||||||
*/
|
|
||||||
this.portrait = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat-Width Minimum while sized to media Aspect-Ratio
|
* Chat-Width Minimum while sized to media Aspect-Ratio
|
||||||
*/
|
*/
|
||||||
|
|
@ -79,11 +74,6 @@ class chatBox{
|
||||||
this.chatPostprocessor = new chatPostprocessor(client);
|
this.chatPostprocessor = new chatPostprocessor(client);
|
||||||
|
|
||||||
//Element Nodes
|
//Element Nodes
|
||||||
/**
|
|
||||||
* Channel Div
|
|
||||||
*/
|
|
||||||
this.channelDiv = document.querySelector("#channel-flexbox");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat Panel Container Div
|
* Chat Panel Container Div
|
||||||
*/
|
*/
|
||||||
|
|
@ -483,14 +473,8 @@ class chatBox{
|
||||||
resizeAspect(event){
|
resizeAspect(event){
|
||||||
const playerHidden = this.client.player.playerDiv.style.display == "none";
|
const playerHidden = this.client.player.playerDiv.style.display == "none";
|
||||||
|
|
||||||
//If window is taller than wide and not in portrait mode, or vice-versa
|
//If the aspect is locked and the player is hidden
|
||||||
if(this.portrait != (window.innerWidth <= window.innerHeight)){
|
if(this.aspectLock && !playerHidden){
|
||||||
//Toggle portrait mode
|
|
||||||
this.togglePortrait();
|
|
||||||
}
|
|
||||||
|
|
||||||
//If the aspect is locked/the window is portrait and the player isn't hidden
|
|
||||||
if((this.aspectLock || this.portrait) && !playerHidden){
|
|
||||||
this.sizeToAspect();
|
this.sizeToAspect();
|
||||||
//Otherwise
|
//Otherwise
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -506,13 +490,8 @@ class chatBox{
|
||||||
* Re-sizes chat box relative to media aspect ratio
|
* Re-sizes chat box relative to media aspect ratio
|
||||||
*/
|
*/
|
||||||
sizeToAspect(){
|
sizeToAspect(){
|
||||||
//If the chat panel is visible
|
|
||||||
if(this.chatPanel.style.display != "none"){
|
if(this.chatPanel.style.display != "none"){
|
||||||
//If our window width is more than or equal to window height (not portrait mode)
|
|
||||||
if(!this.portrait){
|
|
||||||
//Get target video width by multiplying media ratio by window height
|
|
||||||
var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height;
|
var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height;
|
||||||
//Get target chat width my subtracting target media width from total window width
|
|
||||||
const targetChatWidth = window.innerWidth - targetVidWidth;
|
const targetChatWidth = window.innerWidth - targetVidWidth;
|
||||||
//This should be changeable in settings later on, for now it defaults to 20%
|
//This should be changeable in settings later on, for now it defaults to 20%
|
||||||
const limit = window.innerWidth * this.chatWidthMinimum;
|
const limit = window.innerWidth * this.chatWidthMinimum;
|
||||||
|
|
@ -523,17 +502,6 @@ class chatBox{
|
||||||
//Fix busted layout
|
//Fix busted layout
|
||||||
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
|
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
|
||||||
this.chatPanel.style.flexBasis = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
|
this.chatPanel.style.flexBasis = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
|
||||||
}else{
|
|
||||||
//Calculate target video height from media aspect ratio and window width
|
|
||||||
var targetVidHeight = window.innerWidth / this.client.player.getRatio();
|
|
||||||
//Calculate target chat height from the difference between the channel div height and the target video height
|
|
||||||
var targetChatHeight = this.channelDiv.getBoundingClientRect().height - targetVidHeight;
|
|
||||||
|
|
||||||
//Set div heights accordingly
|
|
||||||
this.client.player.playerDiv.style.height = `${targetVidHeight}px`;
|
|
||||||
this.chatPanel.style.height = `${targetChatHeight}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
//This sometimes gets called before userList ahs been initiated :p
|
//This sometimes gets called before userList ahs been initiated :p
|
||||||
if(this.client.userList != null){
|
if(this.client.userList != null){
|
||||||
this.client.userList.clickDragger.fixCutoff();
|
this.client.userList.clickDragger.fixCutoff();
|
||||||
|
|
@ -541,38 +509,6 @@ class chatBox{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePortrait(){
|
|
||||||
//If our window width is more than or equal to window height (not portrait mode), or portrait mode is on while its supposed to be disabled
|
|
||||||
if(window.innerWidth >= window.innerHeight || (localStorage.getItem("disablePortrait") == 'true' && this.portrait)){
|
|
||||||
//Disable portrait CSS modifiers
|
|
||||||
this.channelDiv.style.flexDirection = "row";
|
|
||||||
this.clickDragger.enabled = true;
|
|
||||||
this.chatPanel.style.width = "";
|
|
||||||
this.client.player.playerDiv.style.height = "";
|
|
||||||
this.chatPanel.style.height = "";
|
|
||||||
|
|
||||||
//Disable portrait behavior modifiers
|
|
||||||
this.portrait = false;
|
|
||||||
|
|
||||||
//resize player in-case of empty flex basis
|
|
||||||
this.resizeAspect();
|
|
||||||
|
|
||||||
//Otherwise, if portrait mode is enabled
|
|
||||||
}else if(localStorage.getItem("disablePortrait") != 'true'){
|
|
||||||
//Modify CSS for portrait displays
|
|
||||||
this.channelDiv.style.flexDirection = "column";
|
|
||||||
this.clickDragger.enabled = false;
|
|
||||||
this.chatPanel.style.width = "100%";
|
|
||||||
this.chatPanel.style.flexBasis = "";
|
|
||||||
|
|
||||||
//Enable portrait behavior modifiers
|
|
||||||
this.portrait = true;
|
|
||||||
|
|
||||||
//resize player to correct height
|
|
||||||
this.resizeAspect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles Chat Box UX
|
* Toggles Chat Box UX
|
||||||
* @param {Boolean} show - Whether or not to show Chat Box UX
|
* @param {Boolean} show - Whether or not to show Chat Box UX
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,10 @@ class chatPostprocessor{
|
||||||
this.messageArray = [];
|
this.messageArray = [];
|
||||||
|
|
||||||
//Unescape any sanatized char codes as we use .textContent for double-safety, and to prevent splitting of char codes
|
//Unescape any sanatized char codes as we use .textContent for double-safety, and to prevent splitting of char codes
|
||||||
//Split string by word-boundries on words and non-word boundries around whitespace,
|
//Split string by word-boundries on words and non-word boundries around whitespace, with negative lookaheads to exclude file seperators so we don't split link placeholders, and dashes so we dont split usernames and other things
|
||||||
//with negative lookaheads to exclude file seperators so we don't split link placeholders, dashes so we dont split usernames and other things, and accented characters to keep those from splitting boundries too
|
|
||||||
//Also split by any invisble whitespace as a crutch to handle mushed links/emotes
|
//Also split by any invisble whitespace as a crutch to handle mushed links/emotes
|
||||||
//If we can one day figure out how to split non-repeating special chars instead of special chars with whitespace, that would be perf, unfortunately my brain hasn't rotted enough to understand regex like that just yet.
|
//If we can one day figure out how to split non-repeating special chars instead of special chars with whitespace, that would be perf, unfortunately my brain hasn't rotted enough to understand regex like that just yet.
|
||||||
const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?<!-)(?<!␜)(?=\w)\b|(?!-|[\u00C0-\u017F])(?<=\w)\b|(?=\s)\B|(?<=\s)\B|/g);
|
const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?<!-)(?<!␜)(?=\w)\b|(?!-)(?<=\w)\b|(?=\s)\B|(?<=\s)\B|ㅤ/g);
|
||||||
|
|
||||||
//for each word in the splitstring
|
//for each word in the splitstring
|
||||||
splitString.forEach((string) => {
|
splitString.forEach((string) => {
|
||||||
|
|
@ -260,7 +259,7 @@ class chatPostprocessor{
|
||||||
link.textContent = wordObj.command;
|
link.textContent = wordObj.command;
|
||||||
|
|
||||||
//Add chatbox functionality
|
//Add chatbox functionality
|
||||||
link.addEventListener('click', () => {this.client.chatBox.transmit(wordObj.command)});
|
link.addEventListener('click', () => {this.client.chatBox.commandPreprocessor.preprocess(wordObj.command)});
|
||||||
|
|
||||||
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
|
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
|
||||||
injectionArray.push(link);
|
injectionArray.push(link);
|
||||||
|
|
@ -474,7 +473,7 @@ class chatPostprocessor{
|
||||||
//After eight characters
|
//After eight characters
|
||||||
if(charIndex > 8){
|
if(charIndex > 8){
|
||||||
//Push an invisible line-break character between every character
|
//Push an invisible line-break character between every character
|
||||||
wordArray.push("");
|
wordArray.push("ㅤ");
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -116,14 +116,14 @@ class commandPreprocessor{
|
||||||
*/
|
*/
|
||||||
processEmotes(){
|
processEmotes(){
|
||||||
//inject invisible whitespace in-between emotes to prevent from mushing links together
|
//inject invisible whitespace in-between emotes to prevent from mushing links together
|
||||||
this.message = this.message.replaceAll('][','][');
|
this.message = this.message.replaceAll('][',']ㅤ[');
|
||||||
|
|
||||||
//For each list of emotes
|
//For each list of emotes
|
||||||
Object.keys(this.emotes).forEach((key) => {
|
Object.keys(this.emotes).forEach((key) => {
|
||||||
//For each emote in the current list
|
//For each emote in the current list
|
||||||
this.emotes[key].forEach((emote) => {
|
this.emotes[key].forEach((emote) => {
|
||||||
//Inject emote links into the message, pad with invisible whitespace to keep link from getting mushed
|
//Inject emote links into the message, pad with invisible whitespace to keep link from getting mushed
|
||||||
this.message = this.message.replaceAll(`[${emote.name}]`, `${emote.link}`);
|
this.message = this.message.replaceAll(`[${emote.name}]`, `ㅤ${emote.link}ㅤ`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -135,13 +135,13 @@ class commandPreprocessor{
|
||||||
//Strip out file seperators in-case the user is being a smart-ass
|
//Strip out file seperators in-case the user is being a smart-ass
|
||||||
this.message = this.message.replaceAll('␜','');
|
this.message = this.message.replaceAll('␜','');
|
||||||
//Split message by links
|
//Split message by links
|
||||||
var splitMessage = this.message.split(/(https?:\/\/[^\s]+)/g);
|
var splitMessage = this.message.split(/(https?:\/\/[^\sㅤ]+)/g);
|
||||||
//Create an empty array to hold links
|
//Create an empty array to hold links
|
||||||
this.links = [];
|
this.links = [];
|
||||||
|
|
||||||
splitMessage.forEach((chunk, chunkIndex) => {
|
splitMessage.forEach((chunk, chunkIndex) => {
|
||||||
//For each chunk that is a link
|
//For each chunk that is a link
|
||||||
if(chunk.match(/(https?:\/\/[^\s]+)/g)){
|
if(chunk.match(/(https?:\/\/[^\sㅤ]+)/g)){
|
||||||
//I looked online for obscure characters that no one would use to prevent people from chatting embed placeholders
|
//I looked online for obscure characters that no one would use to prevent people from chatting embed placeholders
|
||||||
//Then I found this fucker, turns out it's literally made for the job lmao (even if it was originally intended for paper/magnetic tape)
|
//Then I found this fucker, turns out it's literally made for the job lmao (even if it was originally intended for paper/magnetic tape)
|
||||||
//Replace link with indexed placeholder
|
//Replace link with indexed placeholder
|
||||||
|
|
@ -269,8 +269,7 @@ class commandPreprocessor{
|
||||||
usernames:{
|
usernames:{
|
||||||
prefix: '',
|
prefix: '',
|
||||||
postfix: '',
|
postfix: '',
|
||||||
//cmds: injectPerms(Array.from(client.userList.colorMap.keys()))
|
cmds: injectPerms(Array.from(client.userList.colorMap.keys()))
|
||||||
cmds: injectPerms(client.userList.getOnlineUserNames())
|
|
||||||
},
|
},
|
||||||
emotes:{
|
emotes:{
|
||||||
prefix:'[',
|
prefix:'[',
|
||||||
|
|
|
||||||
|
|
@ -907,10 +907,6 @@ class hlsLiveStreamHandler extends hlsBase{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Resize chat box to video aspect, since this is the only event thats reliably called on ratio change
|
|
||||||
//Re-enforcing UX rules a little more often shouldnt cause too many issues anywho.
|
|
||||||
this.client.chatBox.resizeAspect();
|
|
||||||
|
|
||||||
//Calculate distance to end of stream
|
//Calculate distance to end of stream
|
||||||
const difference = this.video.duration - this.video.currentTime;
|
const difference = this.video.duration - this.video.currentTime;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class pmPanel extends panelObj{
|
||||||
* @param {channel} client - Parent client Management Object
|
* @param {channel} client - Parent client Management Object
|
||||||
* @param {Document} panelDocument - Panel Document
|
* @param {Document} panelDocument - Panel Document
|
||||||
*/
|
*/
|
||||||
constructor(client, panelDocument, startSesh){
|
constructor(client, panelDocument){
|
||||||
super(client, "Private Messaging", "/panel/pm", panelDocument);
|
super(client, "Private Messaging", "/panel/pm", panelDocument);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -71,17 +71,7 @@ class pmPanel extends panelObj{
|
||||||
//Tell PMHandler to start tracking this panel
|
//Tell PMHandler to start tracking this panel
|
||||||
this.client.pmHandler.panelList.set(this.uuid, null);
|
this.client.pmHandler.panelList.set(this.uuid, null);
|
||||||
|
|
||||||
//Define network related listeners
|
|
||||||
this.defineListeners();
|
this.defineListeners();
|
||||||
|
|
||||||
//If a start sesh was provided
|
|
||||||
if(startSesh != null && startSesh != ""){
|
|
||||||
//Send message out to server
|
|
||||||
this.client.pmSocket.emit("pm", {
|
|
||||||
recipients: startSesh.split(" "),
|
|
||||||
msg: ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closer(){
|
closer(){
|
||||||
|
|
@ -136,6 +126,7 @@ class pmPanel extends panelObj{
|
||||||
this.seshSendButton.addEventListener("click", this.send.bind(this));
|
this.seshSendButton.addEventListener("click", this.send.bind(this));
|
||||||
this.seshBuffer.addEventListener('scroll', this.scrollHandler.bind(this));
|
this.seshBuffer.addEventListener('scroll', this.scrollHandler.bind(this));
|
||||||
this.ownerDoc.defaultView.addEventListener('resize', this.handleAutoScroll.bind(this));
|
this.ownerDoc.defaultView.addEventListener('resize', this.handleAutoScroll.bind(this));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startSesh(event){
|
startSesh(event){
|
||||||
|
|
@ -189,12 +180,6 @@ class pmPanel extends panelObj{
|
||||||
* Render out current sesh array to sesh list UI
|
* Render out current sesh array to sesh list UI
|
||||||
*/
|
*/
|
||||||
renderSeshList(){
|
renderSeshList(){
|
||||||
//If we don't have a sesh list
|
|
||||||
if(this.seshList == null){
|
|
||||||
//Fuck off, you're not even done building the object yet.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Clear out the sesh list
|
//Clear out the sesh list
|
||||||
this.seshList.innerHTML = "";
|
this.seshList.innerHTML = "";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1233,11 +1233,6 @@ class queuePanel extends panelObj{
|
||||||
//Convert start epoch to JS date object
|
//Convert start epoch to JS date object
|
||||||
const started = new Date(nowPlaying.startTime);
|
const started = new Date(nowPlaying.startTime);
|
||||||
|
|
||||||
//If the date the scheduler is set to isn't within the livestream
|
|
||||||
if(!utils.isSameDate(started, this.day) && !utils.dateWithinRange(started, new Date(), this.day)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If this started today
|
//If this started today
|
||||||
if(utils.isSameDate(this.day, started)){
|
if(utils.isSameDate(this.day, started)){
|
||||||
//Set entryDiv top-border location based on start time
|
//Set entryDiv top-border location based on start time
|
||||||
|
|
@ -1251,29 +1246,15 @@ class queuePanel extends panelObj{
|
||||||
entryDiv.style.top = `${this.offsetByDate(dawn)}px`;
|
entryDiv.style.top = `${this.offsetByDate(dawn)}px`;
|
||||||
|
|
||||||
//Apply rest of the styling rules for items that started yestarday
|
//Apply rest of the styling rules for items that started yestarday
|
||||||
entryDiv.classList.add('started-yesterday');
|
entryDiv.classList.add('started-yesterday')
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create entry title
|
//Create entry title
|
||||||
const entryTitle = document.createElement('p');
|
const entryTitle = document.createElement('p');
|
||||||
entryTitle.textContent = utils.unescapeEntities(nowPlaying.title);
|
entryTitle.textContent = utils.unescapeEntities(nowPlaying.title);
|
||||||
|
|
||||||
|
|
||||||
//If we're looking at today
|
|
||||||
if(utils.isSameDate(this.day, new Date())){
|
|
||||||
//Set entry div bottom-border location based on current time, round to match time marker
|
//Set entry div bottom-border location based on current time, round to match time marker
|
||||||
entryDiv.style.bottom = `${Math.round(this.offsetByDate(date, true))}px`;
|
entryDiv.style.bottom = `${Math.round(this.offsetByDate(date, true))}px`
|
||||||
}else{
|
|
||||||
//Get midnight
|
|
||||||
const dusk = new Date();
|
|
||||||
dusk.setHours(23,59,59,999);
|
|
||||||
|
|
||||||
//Set stream to continue to run into the next morning
|
|
||||||
entryDiv.style.bottom = `${Math.round(this.offsetByDate(dusk, true))}px`;
|
|
||||||
|
|
||||||
//Apply rest of the styling rules for items that end after today
|
|
||||||
entryDiv.classList.add('ends-tomorrow');
|
|
||||||
}
|
|
||||||
|
|
||||||
//Assembly entryDiv
|
//Assembly entryDiv
|
||||||
entryDiv.appendChild(entryTitle);
|
entryDiv.appendChild(entryTitle);
|
||||||
|
|
@ -1304,12 +1285,9 @@ class queuePanel extends panelObj{
|
||||||
//Append entry div to queue container
|
//Append entry div to queue container
|
||||||
this.queueContainer.appendChild(entryDiv);
|
this.queueContainer.appendChild(entryDiv);
|
||||||
}else{
|
}else{
|
||||||
//If we're looking at today
|
|
||||||
if(utils.isSameDate(this.day, new Date())){
|
|
||||||
//Update existing entry, round offset to match time marker
|
//Update existing entry, round offset to match time marker
|
||||||
staleEntry.style.bottom = `${Math.round(this.offsetByDate(date, true))}px`
|
staleEntry.style.bottom = `${Math.round(this.offsetByDate(date, true))}px`
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//Keep tooltip date seperate so it re-calculates live duration properly
|
//Keep tooltip date seperate so it re-calculates live duration properly
|
||||||
function buildTooltip(date = new Date()){
|
function buildTooltip(date = new Date()){
|
||||||
|
|
@ -1563,7 +1541,7 @@ class reschedulePopup extends schedulePopup{
|
||||||
this.media = media;
|
this.media = media;
|
||||||
}
|
}
|
||||||
|
|
||||||
schedule(event){
|
startSesh(event){
|
||||||
//If we clicked or hit enter
|
//If we clicked or hit enter
|
||||||
if(event.key == null || event.key == "Enter"){
|
if(event.key == null || event.key == "Enter"){
|
||||||
//Get localized input date
|
//Get localized input date
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,6 @@ class settingsPanel extends panelObj{
|
||||||
*/
|
*/
|
||||||
this.chatWidthMinimum = this.panelDocument.querySelector("#settings-panel-min-chat-width input");
|
this.chatWidthMinimum = this.panelDocument.querySelector("#settings-panel-min-chat-width input");
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable Portrait/Mobile Layout
|
|
||||||
*/
|
|
||||||
this.disablePortrait = this.panelDocument.querySelector("#settings-panel-disable-portrait input");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audible Ping on PM Recieved
|
* Audible Ping on PM Recieved
|
||||||
*/
|
*/
|
||||||
|
|
@ -104,7 +99,6 @@ class settingsPanel extends panelObj{
|
||||||
this.liveSyncTolerance.addEventListener('change', this.updateLiveSyncTolerance.bind(this));
|
this.liveSyncTolerance.addEventListener('change', this.updateLiveSyncTolerance.bind(this));
|
||||||
this.syncDelta.addEventListener('change', this.updateSyncDelta.bind(this));
|
this.syncDelta.addEventListener('change', this.updateSyncDelta.bind(this));
|
||||||
this.chatWidthMinimum.addEventListener('change', this.updateChatWidthMinimum.bind(this));
|
this.chatWidthMinimum.addEventListener('change', this.updateChatWidthMinimum.bind(this));
|
||||||
this.disablePortrait.addEventListener('change', this.updateDisablePortrait.bind(this));
|
|
||||||
this.rxPMSound.addEventListener('change', this.updateRXPMSound.bind(this));
|
this.rxPMSound.addEventListener('change', this.updateRXPMSound.bind(this));
|
||||||
this.txPMSound.addEventListener('change', this.updateTXPMSound.bind(this));
|
this.txPMSound.addEventListener('change', this.updateTXPMSound.bind(this));
|
||||||
this.newSeshSound.addEventListener('change', this.updateNewPMSeshSound.bind(this));
|
this.newSeshSound.addEventListener('change', this.updateNewPMSeshSound.bind(this));
|
||||||
|
|
@ -121,7 +115,6 @@ class settingsPanel extends panelObj{
|
||||||
this.liveSyncTolerance.value = localStorage.getItem("liveSyncTolerance");
|
this.liveSyncTolerance.value = localStorage.getItem("liveSyncTolerance");
|
||||||
this.syncDelta.value = localStorage.getItem("syncDelta");
|
this.syncDelta.value = localStorage.getItem("syncDelta");
|
||||||
this.chatWidthMinimum.value = localStorage.getItem("chatWidthMin");
|
this.chatWidthMinimum.value = localStorage.getItem("chatWidthMin");
|
||||||
this.disablePortrait.checked = localStorage.getItem("disablePortrait") == 'true';
|
|
||||||
this.rxPMSound.value = localStorage.getItem('rxPMSound');
|
this.rxPMSound.value = localStorage.getItem('rxPMSound');
|
||||||
this.txPMSound.checked = localStorage.getItem('txPMSound') == 'true';
|
this.txPMSound.checked = localStorage.getItem('txPMSound') == 'true';
|
||||||
this.newSeshSound.checked = localStorage.getItem('newSeshSound') == 'true';
|
this.newSeshSound.checked = localStorage.getItem('newSeshSound') == 'true';
|
||||||
|
|
@ -225,20 +218,11 @@ class settingsPanel extends panelObj{
|
||||||
client.processConfig("chatWidthMin", this.chatWidthMinimum.value);
|
client.processConfig("chatWidthMin", this.chatWidthMinimum.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles change toggle of disable/enable portrait
|
|
||||||
*/
|
|
||||||
updateDisablePortrait(){
|
|
||||||
localStorage.setItem('disablePortrait', this.disablePortrait.checked);
|
|
||||||
client.processConfig("disablePortrait", this.disablePortrait.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles changes to RX PM Sound setting
|
* Handles changes to RX PM Sound setting
|
||||||
*/
|
*/
|
||||||
updateRXPMSound(){
|
updateRXPMSound(){
|
||||||
localStorage.setItem('rxPMSound', this.rxPMSound.value);
|
localStorage.setItem('rxPMSound', this.rxPMSound.value);
|
||||||
client.processConfig("rxPMSound", this.rxPMSound.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -246,7 +230,6 @@ class settingsPanel extends panelObj{
|
||||||
*/
|
*/
|
||||||
updateTXPMSound(){
|
updateTXPMSound(){
|
||||||
localStorage.setItem('txPMSound', this.txPMSound.checked);
|
localStorage.setItem('txPMSound', this.txPMSound.checked);
|
||||||
client.processConfig("txPMSound", this.txPMSound.checked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -254,7 +237,6 @@ class settingsPanel extends panelObj{
|
||||||
*/
|
*/
|
||||||
updateNewPMSeshSound(){
|
updateNewPMSeshSound(){
|
||||||
localStorage.setItem('newSeshSound', this.newSeshSound.checked);
|
localStorage.setItem('newSeshSound', this.newSeshSound.checked);
|
||||||
client.processConfig("newSeshSound", this.newSeshSound.checked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -262,6 +244,5 @@ class settingsPanel extends panelObj{
|
||||||
*/
|
*/
|
||||||
updateEndPMSeshSound(){
|
updateEndPMSeshSound(){
|
||||||
localStorage.setItem('endSeshSound', this.endSeshSound.checked);
|
localStorage.setItem('endSeshSound', this.endSeshSound.checked);
|
||||||
client.processConfig("endSeshSound", this.endSeshSound.checked);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -33,12 +33,6 @@ class userList{
|
||||||
*/
|
*/
|
||||||
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-users-drag-handle", "#chat-panel-users-div", true, this.client.chatBox.clickDragger);
|
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-users-drag-handle", "#chat-panel-users-div", true, this.client.chatBox.clickDragger);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of currently connected users
|
|
||||||
*/
|
|
||||||
this.userList = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Userlist color array (Maps to css classes)
|
* Userlist color array (Maps to css classes)
|
||||||
*/
|
*/
|
||||||
|
|
@ -52,7 +46,7 @@ class userList{
|
||||||
"userlist-color6"];
|
"userlist-color6"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of connected and buffered usernames to assigned username color/flair
|
* Map of usernames to assigned username color
|
||||||
*/
|
*/
|
||||||
this.colorMap = new Map();
|
this.colorMap = new Map();
|
||||||
|
|
||||||
|
|
@ -64,7 +58,7 @@ class userList{
|
||||||
/**
|
/**
|
||||||
* userlist div
|
* userlist div
|
||||||
*/
|
*/
|
||||||
this.userListDiv = document.querySelector("#chat-panel-users-list-div");
|
this.userList = document.querySelector("#chat-panel-users-list-div");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* user count label
|
* user count label
|
||||||
|
|
@ -109,28 +103,28 @@ class userList{
|
||||||
updateList(list){
|
updateList(list){
|
||||||
//Clear list and set user count
|
//Clear list and set user count
|
||||||
this.userCount.textContent = list.length == 1 ? '1 User' : `${list.length} Users`;
|
this.userCount.textContent = list.length == 1 ? '1 User' : `${list.length} Users`;
|
||||||
this.userListDiv.innerHTML = null;
|
this.userList.innerHTML = null;
|
||||||
|
|
||||||
//Set userlist array to received list
|
//create a new map
|
||||||
this.userList = list;
|
var newMap = new Map();
|
||||||
|
|
||||||
//for each user
|
//for each user
|
||||||
list.forEach((user) => {
|
list.forEach((user) => {
|
||||||
//randomly pick a color
|
//randomly pick a color
|
||||||
var color = this.userColors[Math.floor(Math.random()*this.userColors.length)]
|
var color = this.userColors[Math.floor(Math.random()*this.userColors.length)]
|
||||||
|
|
||||||
//if this user wasn't in the previous colormap
|
//if this user was in the previous colormap
|
||||||
if(this.colorMap.get(user.user) == null){
|
if(this.colorMap.get(user.user) != null){
|
||||||
//Store new randomly selected color
|
//Override with previous color
|
||||||
this.colorMap.set(user.user, color);
|
|
||||||
}else{
|
|
||||||
//Use color from previous entry
|
|
||||||
color = this.colorMap.get(user.user);
|
color = this.colorMap.get(user.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newMap.set(user.user, color);
|
||||||
this.renderUser(user, color);
|
this.renderUser(user, color);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.colorMap = newMap;
|
||||||
|
|
||||||
//Make sure we're not cutting the ux off
|
//Make sure we're not cutting the ux off
|
||||||
this.clickDragger.fixCutoff();
|
this.clickDragger.fixCutoff();
|
||||||
}
|
}
|
||||||
|
|
@ -174,13 +168,12 @@ class userList{
|
||||||
userSpan.addEventListener('click', renderContextMenu.bind(this));
|
userSpan.addEventListener('click', renderContextMenu.bind(this));
|
||||||
userSpan.addEventListener('contextmenu', renderContextMenu.bind(this));
|
userSpan.addEventListener('contextmenu', renderContextMenu.bind(this));
|
||||||
|
|
||||||
this.userListDiv.appendChild(userSpan);
|
this.userList.appendChild(userSpan);
|
||||||
|
|
||||||
function renderContextMenu(event){
|
function renderContextMenu(event){
|
||||||
//Setup menu map
|
//Setup menu map
|
||||||
let menuMap = new Map([
|
let menuMap = new Map([
|
||||||
["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(this.client, user.user, `/panel/profile?user=${user.user}`))}],
|
["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(this.client, `${user.user}`, `/panel/profile?user=${user.user}`))}],
|
||||||
["PM", ()=>{this.client.cPanel.setActivePanel(new pmPanel(client, undefined, user.user))}],
|
|
||||||
["Mention", ()=>{this.client.chatBox.catChat(`${user.user} `)}],
|
["Mention", ()=>{this.client.chatBox.catChat(`${user.user} `)}],
|
||||||
["Toke With", ()=>{this.client.chatBox.tokeWith(user.user)}],
|
["Toke With", ()=>{this.client.chatBox.tokeWith(user.user)}],
|
||||||
]);
|
]);
|
||||||
|
|
@ -217,14 +210,4 @@ class userList{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//returns list of strings containing all currently online users
|
|
||||||
getOnlineUserNames(){
|
|
||||||
var names = [];
|
|
||||||
|
|
||||||
for(let user of this.userList){
|
|
||||||
names.push(user.user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +198,7 @@ class rankList{
|
||||||
imgNode.src = user.img;
|
imgNode.src = user.img;
|
||||||
|
|
||||||
//If the listed user rank is equal or higher than the signed-in user
|
//If the listed user rank is equal or higher than the signed-in user
|
||||||
if(curUser != null && rankEnum.indexOf(user.rank) >= rankEnum.indexOf(curUser.rank)){
|
if(rankEnum.indexOf(user.rank) >= rankEnum.indexOf(curUser.rank)){
|
||||||
var rankContent = user.rank;
|
var rankContent = user.rank;
|
||||||
}else{
|
}else{
|
||||||
//Create rank select
|
//Create rank select
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue