/*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 .*/ //Define global crypto variable for altcha globalThis.crypto = require('node:crypto').webcrypto; //Define NODE imports const https = require('https'); const fs = require('fs'); //Define NPM imports const express = require('express'); const session = require('express-session'); const {createServer } = require('http'); const cookieParser = require('cookie-parser'); const { Server } = require('socket.io'); const path = require('path'); const mongoStore = require('connect-mongo'); const mongoose = require('mongoose'); //Define Local Imports //Application const channelManager = require('./app/channel/channelManager'); const pmHandler = require('./app/pm/pmHandler'); //Util const configCheck = require('./utils/configCheck'); const scheduler = require('./utils/scheduler'); const {errorMiddleware} = require('./utils/loggerUtils'); const sessionUtils = require('./utils/sessionUtils'); //Validator const accountValidator = require('./validators/accountValidator'); //DB Model const statModel = require('./schemas/statSchema'); const flairModel = require('./schemas/flairSchema'); const emoteModel = require('./schemas/emoteSchema'); const tokeModel = require('./schemas/tokebot/tokeSchema'); const tokeCommandModel = require('./schemas/tokebot/tokeCommandSchema'); const migrationModel = require('./schemas/user/migrationSchema'); //Controller const fileNotFoundController = require('./controllers/404Controller'); //Router //Humie-Friendly const indexRouter = require('./routers/indexRouter'); const aboutRouter = require('./routers/aboutRouter'); const registerRouter = require('./routers/registerRouter'); const loginRouter = require('./routers/loginRouter'); const profileRouter = require('./routers/profileRouter'); const adminPanelRouter = require('./routers/adminPanelRouter'); const channelRouter = require('./routers/channelRouter'); const newChannelRouter = require('./routers/newChannelRouter'); const passwordResetRouter = require('./routers/passwordResetRouter'); const emailChangeRouter = require('./routers/emailChangeController'); const migrateRouter = require('./routers/migrateRouter'); //Panel const panelRouter = require('./routers/panelRouter'); //Popup //const popupRouter = require('./routers/popupRouter'); //Tooltip const tooltipRouter = require('./routers/tooltipRouter'); //Api const apiRouter = require('./routers/apiRouter'); //Define Config variables const config = require('../config.json'); const port = config.port; const dbUrl = `mongodb://${config.db.user}:${config.db.pass}@${config.db.address}:${config.db.port}/${config.db.database}`; //Define express const app = express(); //Define session-store (exported so we can kill sessions from user schema) module.exports.store = mongoStore.create({mongoUrl: dbUrl}); //define sessionMiddleware const sessionMiddleware = session({ secret: config.secrets.sessionSecret, resave: false, saveUninitialized: false, store: module.exports.store, cookie: { sameSite: "strict", secure: config.protocol.toLowerCase() == "https" } }); //Declare web server let webServer = null; //If we're using HTTPS if(config.protocol.toLowerCase() == "https"){ try{ //Read key/cert files and store contents const httpsOptions = { key: fs.readFileSync(config.ssl.key), cert: fs.readFileSync(config.ssl.cert) }; //Start HTTPS Server webServer = https.createServer(httpsOptions, app); }catch(err){ //If the error has a path if(err.path != null && err.path != ""){ //Tell the user to fix their shit console.log(`Error opening key/cert file: ${err.path}`); //Otherwise }else{ //Shit our pants console.log("Unknown error occured while starting HTTPS server! Bailing out!"); console.log(err); } //and run for the hills process.exit(); } //Otherwise }else{ //Default to HTTP webServer = createServer(app) } const io = new Server(webServer, {}); //Connect mongoose to the database mongoose.set("sanitizeFilter", true).connect(dbUrl).then(() => { console.log("Connected to DB"); }).catch((err) => { console.error("Unable to connecto to DB: "); console.error(err); process.exit(); }); //Static File Server, set this up first to avoid middleware running on top of it //Serve client-side libraries app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons'))); //Icon set app.use('/lib/altcha',express.static(path.join(__dirname, '../node_modules/altcha/dist_external'))); //Self-Hosted PoW-based Captcha app.use('/lib/hls.js',express.static(path.join(__dirname, '../node_modules/hls.js/dist'))); //HLS Media Handler //Server public 'www' folder app.use(express.static(path.join(__dirname, '../www'))); //Set View Engine app.set('view engine', 'ejs'); app.set('views', __dirname + '/views'); //Middlware //Enable Express app.use(express.json()); //Enable Express Ccokie-Parser app.use(cookieParser()); //Enable Express-Sessions app.use(sessionMiddleware); //Enable Express-Session w/ Socket.IO io.engine.use(sessionMiddleware); //Use rememberMe validators accross all requests. app.use(accountValidator.rememberMeID()); app.use(accountValidator.rememberMeToken()); //Use remember me middleware app.use(sessionUtils.rememberMeMiddleware); //Routes //Humie-Friendly app.use('/', indexRouter); app.use('/about', aboutRouter); app.use('/register', registerRouter); app.use('/login', loginRouter); app.use('/profile', profileRouter); app.use('/adminPanel', adminPanelRouter); app.use('/c', channelRouter); app.use('/newChannel', newChannelRouter); app.use('/passwordReset', passwordResetRouter); app.use('/emailChange', emailChangeRouter); app.use('/migrate', migrateRouter); //Panel app.use('/panel', panelRouter); //tooltip app.use('/tooltip', tooltipRouter); //Bot-Ready app.use('/api', apiRouter); //Handle error checking app.use(errorMiddleware); //Basic 404 handler app.use(fileNotFoundController); asyncKickStart(); /*Asyncronous Kickstarter function Allows us to force server startup to wait on the DB to be ready. Might be better if she kicked off everything at once, and ran a while loop to check when they where all done. 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) is booting up!`); //Run legacy migration await migrationModel.ingestLegacyDump(); //Build migration cache await migrationModel.buildMigrationCache(); //Calculate Toke Map await tokeModel.calculateTokeMap(); //Load default toke commands await tokeCommandModel.loadDefaults(); //Load default flairs await flairModel.loadDefaults(); //Load default emotes await emoteModel.loadDefaults(); //Kick off scheduled-jobs scheduler.kickoff(); //Check for insecure config configCheck.securityCheck(); //Increment launch counter await statModel.incrementLaunchCount(); //Hand over general-namespace socket.io connections to the channel manager module.exports.channelManager = new channelManager(io) module.exports.pmHandler = new pmHandler(io, module.exports.channelManager); //Listen Function webServer.listen(port, () => { console.log(`Tokes up on port \x1b[4m\x1b[35m${port}\x1b[0m!\n`); }); }