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 b333dfb..d04e2ca 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Canopy -0.1-Alpha (Panama Red) - Hotfix 2 +0.4-INDEV Hotfix 1 ========= Canopy - /ˈkæ.nə.pi/: diff --git a/package.json b/package.json index ee1cb25..0076162 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { - "name": "canopy-of-alpha", - "version": "0.1.2", - "canopyDisplayVersion": "0.1-Alpha (Panama Red) - Hotfix 2", + "name": "canopy-of-indev", + "version": "0.4", "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "^7.1.1", 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/controllers/aboutController.js b/src/controllers/aboutController.js index 187157b..2b32b84 100644 --- a/src/controllers/aboutController.js +++ b/src/controllers/aboutController.js @@ -16,7 +16,6 @@ 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'); @@ -24,5 +23,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, 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)}); } \ No newline at end of file diff --git a/src/controllers/api/account/deleteController.js b/src/controllers/api/account/deleteController.js index 0552da7..94b9cdf 100644 --- a/src/controllers/api/account/deleteController.js +++ b/src/controllers/api/account/deleteController.js @@ -33,14 +33,17 @@ module.exports.post = async function(req, res){ const data = matchedData(req); //make sure we're not bullshitting ourselves here. - if(user == null || user.user == null){ - return errorHandler(res, 'You must be logged in to delete your account!', 'unauthorized'); + if(user == null){ + res.status(400); + return res.send('Invalid Session! Cannot delete account while logged out!'); } - const userDB = await userModel.findOne({user: user.user}); + const userDB = await userModel.findOne(user); + if(!userDB){ - return errorHandler(res, 'User not found!', 'unauthorized'); + res.status(400); + return res.send('Invalid User! Account must exist in order to delete!'); } await userDB.nuke(data.pass); 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/api/account/updateController.js b/src/controllers/api/account/updateController.js index bb3fd3f..befca96 100644 --- a/src/controllers/api/account/updateController.js +++ b/src/controllers/api/account/updateController.js @@ -46,13 +46,7 @@ module.exports.post = async function(req, res){ const {field, change} = data; const {user} = req.session; - //If the user is null - if(user == null || user.user == null){ - //BEFORE YOU BREAK MY HEART!!! - return errorHandler(res, 'You must be logged in to preform this action!', 'unauthorized'); - } - - const userDB = await userModel.findOne({user: user.user}); + const userDB = await userModel.findOne(user); const update = {}; @@ -92,7 +86,8 @@ module.exports.post = async function(req, res){ res.status(200); return res.send(update); }else{ - return errorHandler(res, 'User not found!', 'unauthorized'); + res.status(400); + return res.send({errors: [{msg:"User not found!"}]}); } }else{ res.status(400); diff --git a/src/controllers/hrtController.js b/src/controllers/hrtController.js deleted file mode 100644 index e03d5ac..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, user: req.session.user, 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..c17ed51 100644 --- a/src/schemas/user/migrationSchema.js +++ b/src/schemas/user/migrationSchema.js @@ -217,22 +217,13 @@ migrationSchema.statics.ingestLegacyUser = async function(rawProfile){ 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 const migrationProfile = new this({ user: profileArray[1], pass: profileArray[2], //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]), date: profileArray[7], }) @@ -323,7 +314,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..50dcda0 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'); @@ -76,7 +75,6 @@ const apiRouter = require('./routers/apiRouter'); //Define Config variables const config = require('../config.json'); -const package = require('../package.json'); const port = config.port; 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 app.use('/', indexRouter); app.use('/about', aboutRouter); -app.use('/hrt', hrtRouter); app.use('/register', registerRouter); app.use('/login', loginRouter); 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?*/ async function asyncKickStart(){ //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 await migrationModel.ingestLegacyDump(); diff --git a/src/views/about.ejs b/src/views/about.ejs index cebfc35..8842ff0 100644 --- a/src/views/about.ejs +++ b/src/views/about.ejs @@ -40,8 +40,6 @@ along with this program. If not, see . %> 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 Canopy, which was first used to run the ourfore.st instance in late 2025.

-
-

Canopy Ver: <%= version %>

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/www/js/channel/channel.js b/www/js/channel/channel.js index 6da26c3..5d50499 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -337,16 +337,13 @@ function onYouTubeIframeAPIReady(){ //Set embed api to 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 - const nowPlaying = client.player.mediaHandler.nowPlaying; + //Get currently playing item + const nowPlaying = client.player.mediaHandler.nowPlaying; - //If we're playing a youtube video and the official embeds are enabled - if(nowPlaying.type == 'yt' && localStorage.getItem('ytPlayerType') == "embed"){ - //Restart the video now that the embed api has loaded - client.player.start({media: nowPlaying}); - } + //If we're playing a youtube video and the official embeds are enabled + if(nowPlaying.type == 'yt' && localStorage.getItem('ytPlayerType') == "embed"){ + //Restart the video now that the embed api has loaded + client.player.start({media: nowPlaying}); } } diff --git a/www/js/channel/chatPostprocessor.js b/www/js/channel/chatPostprocessor.js index a1b7e43..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); diff --git a/www/js/channel/commandPreprocessor.js b/www/js/channel/commandPreprocessor.js index 1dbc218..af05cfe 100644 --- a/www/js/channel/commandPreprocessor.js +++ b/www/js/channel/commandPreprocessor.js @@ -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/mediaHandler.js b/www/js/channel/mediaHandler.js index 158e5cb..3dd11ae 100644 --- a/www/js/channel/mediaHandler.js +++ b/www/js/channel/mediaHandler.js @@ -907,10 +907,6 @@ class hlsLiveStreamHandler extends hlsBase{ 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 const difference = this.video.duration - this.video.currentTime; diff --git a/www/js/channel/panels/pmPanel.js b/www/js/channel/panels/pmPanel.js index bcebc97..aaab30b 100644 --- a/www/js/channel/panels/pmPanel.js +++ b/www/js/channel/panels/pmPanel.js @@ -24,7 +24,7 @@ class pmPanel extends panelObj{ * @param {channel} client - Parent client Management Object * @param {Document} panelDocument - Panel Document */ - constructor(client, panelDocument, startSesh){ + constructor(client, panelDocument){ super(client, "Private Messaging", "/panel/pm", panelDocument); /** @@ -71,17 +71,7 @@ class pmPanel extends panelObj{ //Tell PMHandler to start tracking this panel this.client.pmHandler.panelList.set(this.uuid, null); - //Define network related listeners 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(){ @@ -136,6 +126,7 @@ class pmPanel extends panelObj{ this.seshSendButton.addEventListener("click", this.send.bind(this)); this.seshBuffer.addEventListener('scroll', this.scrollHandler.bind(this)); this.ownerDoc.defaultView.addEventListener('resize', this.handleAutoScroll.bind(this)); + } startSesh(event){ @@ -189,12 +180,6 @@ class pmPanel extends panelObj{ * Render out current sesh array to sesh list UI */ 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 this.seshList.innerHTML = ""; diff --git a/www/js/channel/userlist.js b/www/js/channel/userlist.js index 9edecb0..7db5a5e 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,13 +168,12 @@ 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 let menuMap = new Map([ - ["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))}], + ["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(this.client, `${user.user}`, `/panel/profile?user=${user.user}`))}], ["Mention", ()=>{this.client.chatBox.catChat(`${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; - } } \ 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