diff --git a/.gitignore b/.gitignore index 88e898a..76cfb45 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,4 @@ chatexamples.txt server.cert server.key www/nonfree/* -migration/* -www/hrt.zip \ No newline at end of file +migration/* \ No newline at end of file diff --git a/README.md b/README.md index a61d209..c6f2762 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Canopy -0.1-Alpha (Panama Red) - Hotfix 3 +0.4-INDEV Hotfix 3 ========= Canopy - /ˈkæ.nə.pi/: diff --git a/config.example.json b/config.example.json index 7b869b3..5d8f957 100644 --- a/config.example.json +++ b/config.example.json @@ -34,10 +34,5 @@ "address": "toke@42069.weed", "pass": "CHANGE_ME" }, - "links":{ - "About": "/about", - "Code": "https://git.ourfore.st/rainbownapkin/canopy", - "HRT": "/hrt" - }, "aboutText":"ourfore.st 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." } \ No newline at end of file diff --git a/config.example.jsonc b/config.example.jsonc index 3669d09..f315d9a 100644 --- a/config.example.jsonc +++ b/config.example.jsonc @@ -65,12 +65,6 @@ "address": "toke@42069.weed", "pass": "CHANGE_ME" }, - //Provides customizable links for navbar - "links":{ - "About": "/about", - "Code": "https://git.ourfore.st/rainbownapkin/canopy", - "HRT": "/hrt" - }, //Fills the 'about ${instanceName}' section on the /about page, lets users know about your specific instance "aboutText":"ourfore.st 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." } \ No newline at end of file diff --git a/package.json b/package.json index 54af4f0..e0c23a8 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "canopy-of-alpha", - "version": "0.1.3", - "canopyDisplayVersion": "0.1-Alpha (Panama Red) - Hotfix 3", + "name": "canopy-of-indev", + "version": "0.4.3", + "canopyDisplayVersion": "0.4-Indev Hotfix 3", "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "^7.1.1", - "altcha": "^2.3.0", + "altcha": "^1.0.7", "altcha-lib": "^1.2.0", "argon2": "^0.44.0", - "bcrypt": "^6.0.0", + "bcrypt": "^5.1.1", "bootstrap-icons": "^1.11.3", "connect-mongo": "^5.1.0", "cookie-parser": "^1.4.7", @@ -20,8 +20,8 @@ "hls.js": "^1.6.2", "mongoose": "^8.4.3", "node-cron": "^3.0.3", - "nodemailer": "^8.0.7", - "socket.io": "^4.2.0", + "nodemailer": "^7.0.9", + "socket.io": "^4.8.1", "youtube-dl-exec": "^3.0.20" }, "scripts": { diff --git a/src/app/channel/chatHandler.js b/src/app/channel/chatHandler.js index 218f561..bc1e751 100644 --- a/src/app/channel/chatHandler.js +++ b/src/app/channel/chatHandler.js @@ -212,12 +212,6 @@ class chatHandler{ * @param {chat} chat - Chat Object representing the message to broadcast to the given channel */ relayChatObject(chan, chat){ - //If we have an empty chat - if(chat.msg.length <= 0){ - //Drop it - return; - } - //Send out chat this.server.io.in(chan).emit("chatMessage", chat); diff --git a/src/app/channel/tokebot.js b/src/app/channel/tokebot.js index 52743eb..6e41471 100644 --- a/src/app/channel/tokebot.js +++ b/src/app/channel/tokebot.js @@ -148,15 +148,6 @@ class tokebot{ //Add the toking user to the tokers map this.tokers.set(commandObj.socket.user.user, commandObj.argumentArray[0].toLowerCase()); - - if(this.tokeCounter <= 3){ - //Drop the toke timer - clearTimeout(this.tokeTimer); - //Roll the toke counter back to 3 - this.tokeCounter = 3; - //Re-start the toke timer - this.tokeTimer = setTimeout(this.countdown.bind(this), 1000); - } //If the user is already in the toke }else{ //Tell them to fuck off @@ -219,7 +210,7 @@ class tokebot{ //Decrement toke time this.tokeCounter--; //try again in another second - this.tokeTimer = setTimeout(this.countdown.bind(this), 1000); + this.tokeTimer = setTimeout(this.countdown.bind(this), 1000) } /** diff --git a/src/app/chatPreprocessor.js b/src/app/chatPreprocessor.js index dc3d4bd..a4331ab 100644 --- a/src/app/chatPreprocessor.js +++ b/src/app/chatPreprocessor.js @@ -57,7 +57,7 @@ class chatPreprocessor{ //If we don't pass sanatization/validation turn this car around if(!this.sanatizeCommand(commandObj)){ - return false; + return; } //split the command diff --git a/src/controllers/404Controller.js b/src/controllers/404Controller.js index 3e7a4e0..471ea16 100644 --- a/src/controllers/404Controller.js +++ b/src/controllers/404Controller.js @@ -26,5 +26,5 @@ module.exports = async function(req, res){ res.status(404); //Render page - return res.render('404', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req)}); + return res.render('404', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/controllers/aboutController.js b/src/controllers/aboutController.js index 249e931..187157b 100644 --- a/src/controllers/aboutController.js +++ b/src/controllers/aboutController.js @@ -24,5 +24,5 @@ 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)}); + return res.render('about', {aboutText: config.aboutText, instance: config.instanceName, user: req.session.user, version: package.canopyDisplayVersion, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/controllers/adminPanelController.js b/src/controllers/adminPanelController.js index 6ecdaee..ca1a74c 100644 --- a/src/controllers/adminPanelController.js +++ b/src/controllers/adminPanelController.js @@ -42,7 +42,7 @@ module.exports.get = async function(req, res){ //Render out the page return res.render('adminPanel', { - instance: config.instanceName, links: config.links, + instance: config.instanceName, user: req.session.user, rankEnum: permissionModel.rankEnum, chanGuide: chanGuide, diff --git a/src/controllers/api/account/emailChangeRequestController.js b/src/controllers/api/account/emailChangeRequestController.js index 3108ab6..4791ba2 100644 --- a/src/controllers/api/account/emailChangeRequestController.js +++ b/src/controllers/api/account/emailChangeRequestController.js @@ -60,7 +60,7 @@ module.exports.post = async function(req, res){ //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()); //If the email is in use diff --git a/src/controllers/api/account/loginController.js b/src/controllers/api/account/loginController.js index be8685b..d509342 100644 --- a/src/controllers/api/account/loginController.js +++ b/src/controllers/api/account/loginController.js @@ -90,7 +90,7 @@ module.exports.post = async function(req, res){ const {user, pass} = matchedData(req); //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(migrationDB != null){ diff --git a/src/controllers/channelController.js b/src/controllers/channelController.js index fef37a7..dd27fcd 100644 --- a/src/controllers/channelController.js +++ b/src/controllers/channelController.js @@ -22,5 +22,5 @@ const csrfUtils = require('../utils/csrfUtils'); //channel functions module.exports.get = function(req, res){ - res.render('channel', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req)}); + res.render('channel', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/controllers/channelSettingsController.js b/src/controllers/channelSettingsController.js index 3559503..e310773 100644 --- a/src/controllers/channelSettingsController.js +++ b/src/controllers/channelSettingsController.js @@ -42,7 +42,7 @@ module.exports.get = async function(req, res){ 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){ return exceptionHandler(res, err); } diff --git a/src/controllers/emailChangeController.js b/src/controllers/emailChangeController.js index 6b0b99d..fb9cabd 100644 --- a/src/controllers/emailChangeController.js +++ b/src/controllers/emailChangeController.js @@ -40,18 +40,18 @@ module.exports.get = async function(req, res){ //If we have an invalid request if(requestDB == null){ - return res.render('emailChange', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false}); + return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false}); } //Speak of our success (don't wait for the emails to be sent) - res.render('emailChange', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: true}); + res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: true}); //Consume the request await requestDB.consume(); }else{ - return res.render('emailChange', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false}); + return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false}); } }catch(err){ - return res.render('emailChange', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false}); + return res.render('emailChange', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req), valid: false}); } } \ No newline at end of file diff --git a/src/controllers/hrtController.js b/src/controllers/hrtController.js deleted file mode 100644 index 7b1b920..0000000 --- a/src/controllers/hrtController.js +++ /dev/null @@ -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 .*/ - -//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)}); -} \ No newline at end of file diff --git a/src/controllers/indexController.js b/src/controllers/indexController.js index a06cfec..30e9682 100644 --- a/src/controllers/indexController.js +++ b/src/controllers/indexController.js @@ -29,7 +29,7 @@ const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); module.exports.get = async function(req, res){ try{ const chanGuide = await channelModel.getChannelList(); - return res.render('index', {instance: config.instanceName, links: config.links, user: req.session.user, chanGuide: chanGuide, csrfToken: csrfUtils.generateToken(req), unescape: validator.unescape}); + return res.render('index', {instance: config.instanceName, user: req.session.user, chanGuide: chanGuide, csrfToken: csrfUtils.generateToken(req), unescape: validator.unescape}); }catch(err){ return exceptionHandler(res, err); } diff --git a/src/controllers/loginController.js b/src/controllers/loginController.js index ffa5fc8..a25d316 100644 --- a/src/controllers/loginController.js +++ b/src/controllers/loginController.js @@ -45,7 +45,7 @@ module.exports.get = async function(req, res){ //if we have previous attempts for this user if(attempts != null){ if(attempts.count > sessionUtils.maxAttempts){ - return res.render('lockedAccount', {instance: config.instanceName, links: config.links, user: req.session.user, csrfToken: csrfUtils.generateToken(req)}); + return res.render('lockedAccount', {instance: config.instanceName, user: req.session.user, csrfToken: csrfUtils.generateToken(req)}); } //If the users login's are being throttled @@ -56,16 +56,16 @@ module.exports.get = async function(req, res){ const challenge = await altchaUtils.genCaptcha(difficulty, user); //Render page - return res.render('login', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); + return res.render('login', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); } //otherwise }else{ //Render generic page - return res.render('login', {instance: config.instanceName, links: config.links, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)}); + return res.render('login', {instance: config.instanceName, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)}); } //if we received invalid input }else{ //Render pretend nothing happened, send out a generic page - return res.render('login', {instance: config.instanceName, links: config.links, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)}); + return res.render('login', {instance: config.instanceName, user: req.session.user, challenge: null, csrfToken: csrfUtils.generateToken(req)}); } } \ No newline at end of file diff --git a/src/controllers/migrateController.js b/src/controllers/migrateController.js index a46d3b2..ea82e5e 100644 --- a/src/controllers/migrateController.js +++ b/src/controllers/migrateController.js @@ -27,5 +27,5 @@ module.exports.get = async function(req, res){ 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)}); + return res.render('migrate', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/controllers/newChannelController.js b/src/controllers/newChannelController.js index 233c99a..ee77166 100644 --- a/src/controllers/newChannelController.js +++ b/src/controllers/newChannelController.js @@ -27,5 +27,5 @@ module.exports.get = async function(req, res){ const challenge = await altchaUtils.genCaptcha(); //render the page - return res.render('newChannel', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); + return res.render('newChannel', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/controllers/panel/popoutContainerController.js b/src/controllers/panel/popoutContainerController.js index 619cf18..6993f9e 100644 --- a/src/controllers/panel/popoutContainerController.js +++ b/src/controllers/panel/popoutContainerController.js @@ -19,5 +19,5 @@ const config = require('../../../config.json'); //popout panel container functions module.exports.get = async function(req, res){ - res.render('popoutContainer', {instance: config.instanceName, links: config.links}); + res.render('popoutContainer', {instance: config.instanceName}); } \ No newline at end of file diff --git a/src/controllers/passwordResetController.js b/src/controllers/passwordResetController.js index 5fd392c..1d47fae 100644 --- a/src/controllers/passwordResetController.js +++ b/src/controllers/passwordResetController.js @@ -47,11 +47,11 @@ module.exports.get = async function(req, res){ */ //Render page - return res.render('passwordReset', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, token, csrfToken: csrfUtils.generateToken(req)}); + return res.render('passwordReset', {instance: config.instanceName, user: req.session.user, challenge, token, csrfToken: csrfUtils.generateToken(req)}); //If we didn't get a valid token }else{ //otherwise render generic page - return res.render('passwordReset', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, token: null, csrfToken: csrfUtils.generateToken(req)}); + return res.render('passwordReset', {instance: config.instanceName, user: req.session.user, challenge, token: null, csrfToken: csrfUtils.generateToken(req)}); } }catch(err){ return exceptionHandler(res, err); diff --git a/src/controllers/profileController.js b/src/controllers/profileController.js index df5b9c2..67d1895 100644 --- a/src/controllers/profileController.js +++ b/src/controllers/profileController.js @@ -42,7 +42,7 @@ module.exports.get = async function(req, res){ const presence = await presenceUtils.getPresence(profile.user); res.render('profile', { - instance: config.instanceName, links: config.links, + instance: config.instanceName, user: req.session.user, profile, selfProfile, @@ -52,7 +52,7 @@ module.exports.get = async function(req, res){ }); }else{ res.render('profile', { - instance: config.instanceName, links: config.links, + instance: config.instanceName, user: req.session.user, profile: null, selfProfile: false, diff --git a/src/controllers/registerController.js b/src/controllers/registerController.js index 9f2eb14..91fb5b0 100644 --- a/src/controllers/registerController.js +++ b/src/controllers/registerController.js @@ -27,5 +27,5 @@ module.exports.get = async function(req, res){ const challenge = await altchaUtils.genCaptcha(); //Render page - return res.render('register', {instance: config.instanceName, links: config.links, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); + return res.render('register', {instance: config.instanceName, user: req.session.user, challenge, csrfToken: csrfUtils.generateToken(req)}); } \ No newline at end of file diff --git a/src/routers/hrtRouter.js b/src/routers/hrtRouter.js deleted file mode 100644 index bcf314b..0000000 --- a/src/routers/hrtRouter.js +++ /dev/null @@ -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 .*/ - -//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; diff --git a/src/schemas/user/migrationSchema.js b/src/schemas/user/migrationSchema.js index 4e61ce0..9aff595 100644 --- a/src/schemas/user/migrationSchema.js +++ b/src/schemas/user/migrationSchema.js @@ -323,7 +323,7 @@ migrationSchema.statics.buildMigrationCache = async function(){ migrationSchema.statics.consumeByUsername = async function(ip, migration){ //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(migrationDB == null){ diff --git a/src/schemas/user/userSchema.js b/src/schemas/user/userSchema.js index 783b241..41dfcda 100644 --- a/src/schemas/user/userSchema.js +++ b/src/schemas/user/userSchema.js @@ -256,13 +256,13 @@ userSchema.statics.register = async function(userObj, ip){ //Check password confirmation matches if(pass == passConfirm){ //Setup user query - let userQuery = {user: new RegExp(`^${user}$`, 'i')}; + let userQuery = {user: new RegExp(user, 'i')}; //If we have an email if(email != null && email != ""){ userQuery = {$or: [ 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 - 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(!userDB){ diff --git a/src/server.js b/src/server.js index 228d555..f783a6d 100644 --- a/src/server.js +++ b/src/server.js @@ -55,7 +55,6 @@ const fileNotFoundController = require('./controllers/404Controller'); //Humie-Friendly const indexRouter = require('./routers/indexRouter'); const aboutRouter = require('./routers/aboutRouter'); -const hrtRouter = require('./routers/hrtRouter'); const registerRouter = require('./routers/registerRouter'); const loginRouter = require('./routers/loginRouter'); const profileRouter = require('./routers/profileRouter'); @@ -180,7 +179,6 @@ app.use(sessionUtils.rememberMeMiddleware); //Humie-Friendly app.use('/', indexRouter); app.use('/about', aboutRouter); -app.use('/hrt', hrtRouter); app.use('/register', registerRouter); app.use('/login', loginRouter); app.use('/profile', profileRouter); diff --git a/src/utils/linkUtils.js b/src/utils/linkUtils.js index 76b8e45..9e85870 100644 --- a/src/utils/linkUtils.js +++ b/src/utils/linkUtils.js @@ -14,9 +14,6 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ -//Config -const config = require('../../config.json'); - //NPM Imports const validator = require('validator');//No express here, so regular validator it is! const {sanitizeUrl} = require("@braintree/sanitize-url"); @@ -35,15 +32,6 @@ module.exports.cache = new Map(); module.exports.markLink = async function(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 const cachedLink = module.exports.cache.get(link); @@ -117,4 +105,4 @@ module.exports.markLink = async function(dirtyLink){ //return the link return linkObj; -} +} \ No newline at end of file diff --git a/src/utils/media/yanker.js b/src/utils/media/yanker.js index cb6697b..2dff869 100644 --- a/src/utils/media/yanker.js +++ b/src/utils/media/yanker.js @@ -131,7 +131,7 @@ module.exports.getMediaType = async function(dirtyURL){ } //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 { type: "ia", diff --git a/src/views/hrt.ejs b/src/views/hrt.ejs deleted file mode 100644 index 8777403..0000000 --- a/src/views/hrt.ejs +++ /dev/null @@ -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 . %> - - - - <%- include('partial/styles', {instance, user}); %> - <%- include('partial/csrfToken', {csrfToken}); %> - - <%= instance %> - DIY HRT Archive - - - <%- include('partial/navbar', {user}); %> -
-

Bowie's DIY HRT Archive

-
-
- This page is an attempt at putting together everything I know about DIY HRT. -

- 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. -

- I am currently in the process of figuring out brewing my own sublingual oil. -

-

This zip file contains everything I know.

-
- You should probably use TOR or a decent VPN in either an amnesiac OS or dispoable VM. Everything paid w/ either XMR or cash by mail. -

- - 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. - -

- Much love, and remember to take your meds! -

-    -rainbownapkin <3 -
  -
-
- -
- <%- include('partial/scripts', {user}); %> -
- diff --git a/src/views/partial/navbar.ejs b/src/views/partial/navbar.ejs index c8c3528..aa9a56a 100644 --- a/src/views/partial/navbar.ejs +++ b/src/views/partial/navbar.ejs @@ -20,24 +20,13 @@ along with this program. If not, see . %> <% if(user){ %> - + <% }else{ %> - + <% } %> \ No newline at end of file diff --git a/src/views/partial/panels/settings.ejs b/src/views/partial/panels/settings.ejs index 320c3e7..6fe53d2 100644 --- a/src/views/partial/panels/settings.ejs +++ b/src/views/partial/panels/settings.ejs @@ -16,7 +16,7 @@ along with this program. If not, see . %>

Client Settings

-

Playeback Settings

+

Player Settings

Youtube Player Type:

-

Display Settings

+

Chat Settings

-

Chat Width Minimum While Locked to Aspect Ratio:

+

Aspect-Ratio Lock Chat Width Minimum:

- -

Disable Portrait/Mobile Layout:

- -

Notification Settings

Play Sound for received PMs:

diff --git a/www/img/strikethrough.svg b/www/img/strikethrough.svg index 40e46a2..7c41d20 100644 --- a/www/img/strikethrough.svg +++ b/www/img/strikethrough.svg @@ -1,11 +1,11 @@ - + diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js index 18a6996..6da26c3 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -288,16 +288,6 @@ class channel{ //Set Chat Box Width minimum while Locked to Aspect-Ratio this.chatBox.chatWidthMinimum = value / 100; 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': //If the userlist class isn't loaded in yet if(this.userList == null){ @@ -336,8 +326,7 @@ class channel{ ["rxPMSound", 'unread'], ["txPMSound", false], ["newSeshSound", true], - ["endSeshSound", true], - ["disablePortrait", false] + ["endSeshSound", true] ]); } diff --git a/www/js/channel/chat.js b/www/js/channel/chat.js index ee15e7a..5311fb5 100644 --- a/www/js/channel/chat.js +++ b/www/js/channel/chat.js @@ -38,11 +38,6 @@ class chatBox{ */ 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 */ @@ -79,11 +74,6 @@ class chatBox{ this.chatPostprocessor = new chatPostprocessor(client); //Element Nodes - /** - * Channel Div - */ - this.channelDiv = document.querySelector("#channel-flexbox"); - /** * Chat Panel Container Div */ @@ -483,14 +473,8 @@ class chatBox{ resizeAspect(event){ const playerHidden = this.client.player.playerDiv.style.display == "none"; - //If window is taller than wide and not in portrait mode, or vice-versa - if(this.portrait != (window.innerWidth <= window.innerHeight)){ - //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){ + //If the aspect is locked and the player is hidden + if(this.aspectLock && !playerHidden){ this.sizeToAspect(); //Otherwise }else{ @@ -506,72 +490,24 @@ class chatBox{ * Re-sizes chat box relative to media aspect ratio */ sizeToAspect(){ - //If the chat panel is visible 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; - //Get target chat width my subtracting target media width from total window width - const targetChatWidth = window.innerWidth - targetVidWidth; - //This should be changeable in settings later on, for now it defaults to 20% - const limit = window.innerWidth * this.chatWidthMinimum; + var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height; + const targetChatWidth = window.innerWidth - targetVidWidth; + //This should be changeable in settings later on, for now it defaults to 20% + const limit = window.innerWidth * this.chatWidthMinimum; - //Set width to target or 20vw depending on whether or not we've hit the width limit - this.chatPanel.style.flexBasis = targetChatWidth > limit ? `${targetChatWidth}px` : `${this.chatWidthMinimum * 100}vw`; - - //Fix busted layout - var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width; - 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`; - } + //Set width to target or 20vw depending on whether or not we've hit the width limit + this.chatPanel.style.flexBasis = targetChatWidth > limit ? `${targetChatWidth}px` : `${this.chatWidthMinimum * 100}vw`; + //Fix busted layout + var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width; + this.chatPanel.style.flexBasis = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`; //This sometimes gets called before userList ahs been initiated :p if(this.client.userList != null){ this.client.userList.clickDragger.fixCutoff(); } - } - } - - 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 diff --git a/www/js/channel/chatPostprocessor.js b/www/js/channel/chatPostprocessor.js index 8dd9c31..e6daf7b 100644 --- a/www/js/channel/chatPostprocessor.js +++ b/www/js/channel/chatPostprocessor.js @@ -140,11 +140,10 @@ class chatPostprocessor{ this.messageArray = []; //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, - //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 + //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 //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. - const splitString = utils.unescapeEntities(this.rawData.msg).split(/(? { @@ -260,7 +259,7 @@ class chatPostprocessor{ link.textContent = wordObj.command; //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. injectionArray.push(link); @@ -474,7 +473,7 @@ class chatPostprocessor{ //After eight characters if(charIndex > 8){ //Push an invisible line-break character between every character - wordArray.push("​"); + wordArray.push("ㅤ"); } }); diff --git a/www/js/channel/commandPreprocessor.js b/www/js/channel/commandPreprocessor.js index efed2ab..af05cfe 100644 --- a/www/js/channel/commandPreprocessor.js +++ b/www/js/channel/commandPreprocessor.js @@ -116,14 +116,14 @@ class commandPreprocessor{ */ processEmotes(){ //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 Object.keys(this.emotes).forEach((key) => { //For each emote in the current list this.emotes[key].forEach((emote) => { //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 this.message = this.message.replaceAll('␜',''); //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 this.links = []; splitMessage.forEach((chunk, chunkIndex) => { //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 //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 @@ -269,8 +269,7 @@ class commandPreprocessor{ usernames:{ prefix: '', postfix: '', - //cmds: injectPerms(Array.from(client.userList.colorMap.keys())) - cmds: injectPerms(client.userList.getOnlineUserNames()) + cmds: injectPerms(Array.from(client.userList.colorMap.keys())) }, emotes:{ prefix:'[', diff --git a/www/js/channel/panels/queuePanel/queuePanel.js b/www/js/channel/panels/queuePanel/queuePanel.js index 2bfd1c7..a872c92 100644 --- a/www/js/channel/panels/queuePanel/queuePanel.js +++ b/www/js/channel/panels/queuePanel/queuePanel.js @@ -1233,11 +1233,6 @@ class queuePanel extends panelObj{ //Convert start epoch to JS date object 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(utils.isSameDate(this.day, started)){ //Set entryDiv top-border location based on start time @@ -1251,29 +1246,15 @@ class queuePanel extends panelObj{ entryDiv.style.top = `${this.offsetByDate(dawn)}px`; //Apply rest of the styling rules for items that started yestarday - entryDiv.classList.add('started-yesterday'); + entryDiv.classList.add('started-yesterday') } //Create entry title const entryTitle = document.createElement('p'); 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 - 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'); - } + //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` //Assembly entryDiv entryDiv.appendChild(entryTitle); @@ -1304,11 +1285,8 @@ class queuePanel extends panelObj{ //Append entry div to queue container this.queueContainer.appendChild(entryDiv); }else{ - //If we're looking at today - if(utils.isSameDate(this.day, new Date())){ - //Update existing entry, round offset to match time marker - staleEntry.style.bottom = `${Math.round(this.offsetByDate(date, true))}px` - } + //Update existing entry, round offset to match time marker + staleEntry.style.bottom = `${Math.round(this.offsetByDate(date, true))}px` } //Keep tooltip date seperate so it re-calculates live duration properly @@ -1563,7 +1541,7 @@ class reschedulePopup extends schedulePopup{ this.media = media; } - schedule(event){ + startSesh(event){ //If we clicked or hit enter if(event.key == null || event.key == "Enter"){ //Get localized input date diff --git a/www/js/channel/panels/settingsPanel.js b/www/js/channel/panels/settingsPanel.js index 73e5dc9..e9f0519 100644 --- a/www/js/channel/panels/settingsPanel.js +++ b/www/js/channel/panels/settingsPanel.js @@ -62,11 +62,6 @@ class settingsPanel extends panelObj{ */ 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 */ @@ -104,7 +99,6 @@ class settingsPanel extends panelObj{ this.liveSyncTolerance.addEventListener('change', this.updateLiveSyncTolerance.bind(this)); this.syncDelta.addEventListener('change', this.updateSyncDelta.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.txPMSound.addEventListener('change', this.updateTXPMSound.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.syncDelta.value = localStorage.getItem("syncDelta"); this.chatWidthMinimum.value = localStorage.getItem("chatWidthMin"); - this.disablePortrait.checked = localStorage.getItem("disablePortrait") == 'true'; this.rxPMSound.value = localStorage.getItem('rxPMSound'); this.txPMSound.checked = localStorage.getItem('txPMSound') == 'true'; this.newSeshSound.checked = localStorage.getItem('newSeshSound') == 'true'; @@ -225,20 +218,11 @@ class settingsPanel extends panelObj{ 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 */ updateRXPMSound(){ localStorage.setItem('rxPMSound', this.rxPMSound.value); - client.processConfig("rxPMSound", this.rxPMSound.value); } /** @@ -246,7 +230,6 @@ class settingsPanel extends panelObj{ */ updateTXPMSound(){ localStorage.setItem('txPMSound', this.txPMSound.checked); - client.processConfig("txPMSound", this.txPMSound.checked); } /** @@ -254,7 +237,6 @@ class settingsPanel extends panelObj{ */ updateNewPMSeshSound(){ localStorage.setItem('newSeshSound', this.newSeshSound.checked); - client.processConfig("newSeshSound", this.newSeshSound.checked); } /** @@ -262,6 +244,5 @@ class settingsPanel extends panelObj{ */ updateEndPMSeshSound(){ localStorage.setItem('endSeshSound', this.endSeshSound.checked); - client.processConfig("endSeshSound", this.endSeshSound.checked); } } \ No newline at end of file diff --git a/www/js/channel/userlist.js b/www/js/channel/userlist.js index 9edecb0..5a4f24c 100644 --- a/www/js/channel/userlist.js +++ b/www/js/channel/userlist.js @@ -32,12 +32,6 @@ class userList{ * Click Dragger object for handling userlist resizes */ 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) @@ -52,7 +46,7 @@ class userList{ "userlist-color6"]; /** - * Map of connected and buffered usernames to assigned username color/flair + * Map of usernames to assigned username color */ this.colorMap = new Map(); @@ -64,7 +58,7 @@ class userList{ /** * userlist div */ - this.userListDiv = document.querySelector("#chat-panel-users-list-div"); + this.userList = document.querySelector("#chat-panel-users-list-div"); /** * user count label @@ -109,28 +103,28 @@ class userList{ updateList(list){ //Clear list and set user count 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 - this.userList = list; + //create a new map + var newMap = new Map(); //for each user list.forEach((user) => { //randomly pick a color var color = this.userColors[Math.floor(Math.random()*this.userColors.length)] - //if this user wasn't in the previous colormap - if(this.colorMap.get(user.user) == null){ - //Store new randomly selected color - this.colorMap.set(user.user, color); - }else{ - //Use color from previous entry + //if this user was in the previous colormap + if(this.colorMap.get(user.user) != null){ + //Override with previous color color = this.colorMap.get(user.user); } + newMap.set(user.user, color); this.renderUser(user, color); }); + this.colorMap = newMap; + //Make sure we're not cutting the ux off this.clickDragger.fixCutoff(); } @@ -174,7 +168,7 @@ class userList{ userSpan.addEventListener('click', renderContextMenu.bind(this)); userSpan.addEventListener('contextmenu', renderContextMenu.bind(this)); - this.userListDiv.appendChild(userSpan); + this.userList.appendChild(userSpan); function renderContextMenu(event){ //Setup menu map @@ -217,14 +211,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; - } } \ No newline at end of file diff --git a/www/js/channelSettings.js b/www/js/channelSettings.js index 5ff1aaf..1af50fc 100644 --- a/www/js/channelSettings.js +++ b/www/js/channelSettings.js @@ -198,7 +198,7 @@ class rankList{ imgNode.src = user.img; //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; }else{ //Create rank select