├── .gitignore ├── .idea ├── Nertivia-Server.iml ├── jsLibraryMappings.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── example.env ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── API │ └── GDrive.js ├── Log.ts ├── ServerEventNames.ts ├── app.ts ├── custom.d.ts ├── emailBlacklist.json ├── index.ts ├── middlewares │ ├── ChannelVerification.js │ ├── GDriveOauthClient.js │ ├── URLEmbed.ts │ ├── UserPresentVerification.js │ ├── authenticate.js │ ├── avatarCacheLegacy.js │ ├── channelRateLimit.js │ ├── checkGDriveAvatarLegacy.js │ ├── checkRolePermissions.js │ ├── cors.ts │ ├── disAllowBlockedUser.js │ ├── isAdmin.js │ ├── loadMedia.ts │ ├── rateLimit.js │ ├── realIP.ts │ ├── redisSession.ts │ └── serverChannelPermissions.js ├── models │ ├── AdminActions.ts │ ├── BannedIPs.ts │ ├── BlockedUsers.ts │ ├── Channels.ts │ ├── CustomEmojis.ts │ ├── Devices.ts │ ├── Friends.ts │ ├── MessageQuotes.ts │ ├── MessageReactions.ts │ ├── Messages.ts │ ├── Notifications.ts │ ├── PublicServers.ts │ ├── PublicThemes.ts │ ├── ServerInvites.ts │ ├── ServerMembers.ts │ ├── ServerRoles.ts │ ├── Servers.ts │ ├── Themes.ts │ └── Users.ts ├── newRedisWrapper.ts ├── policies │ ├── RolesPolicies.js │ ├── ServerPolicies.js │ ├── ThemePolicies.js │ ├── UserPolicies.js │ ├── authenticationPolicies.js │ ├── errorReportPolicies.js │ ├── forceCaptcha.js │ ├── messagePolicies.js │ ├── policyHandler.js │ ├── publicThemePolicies.js │ ├── reCaptchaPolicie.js │ ├── relationshipPolicies.js │ ├── settingsPolicies.js │ └── surveyPolicies.js ├── redis.js ├── redis │ └── instance.ts ├── routes │ ├── admin │ │ ├── Stats.ts │ │ ├── approveTheme.js │ │ ├── deleteServer.js │ │ ├── getTheme.js │ │ ├── index.js │ │ ├── onlineUsers.js │ │ ├── recentAdminActions.ts │ │ ├── recentServers.ts │ │ ├── recentUsers.ts │ │ ├── sameIPUsers.js │ │ ├── searchServers.js │ │ ├── searchUsers.js │ │ ├── suspendUser.js │ │ ├── unsuspendUser.js │ │ └── waitingThemes.js │ ├── api.js │ ├── bots │ │ ├── botJoin.ts │ │ ├── createBot.ts │ │ ├── deleteBot.ts │ │ ├── getBot.ts │ │ ├── getCommands.ts │ │ ├── index.js │ │ ├── myBots.ts │ │ ├── resetBotToken.ts │ │ └── updateBot.ts │ ├── channels │ │ ├── deleteChannel.js │ │ ├── getChannel.js │ │ ├── index.js │ │ └── openChannel.js │ ├── chat.js │ ├── chatBeta.js │ ├── devices │ │ ├── index.js │ │ └── registerDevice.js │ ├── errorReport │ │ ├── index.js │ │ └── reportError.js │ ├── explore │ │ ├── index.js │ │ ├── servers │ │ │ ├── addPublicServersList.js │ │ │ ├── deletePublicServersList.js │ │ │ ├── getPublicServersList.js │ │ │ ├── getServer.js │ │ │ ├── index.js │ │ │ └── updatePublicServersList.js │ │ └── themes │ │ │ ├── AddLike.js │ │ │ ├── addTheme.js │ │ │ ├── applyPublicTheme.js │ │ │ ├── getAllThemes.js │ │ │ ├── getPublicTheme.js │ │ │ ├── index.js │ │ │ ├── removeLike.js │ │ │ └── updateTheme.js │ ├── files.js │ ├── messages │ │ ├── addReaction.js │ │ ├── deleteMessage.js │ │ ├── deleteMessageBulk.ts │ │ ├── fileMessage.ts │ │ ├── getMessage.js │ │ ├── getMessages.js │ │ ├── getReactedUsers.js │ │ ├── index.js │ │ ├── messageButtonCallback.js │ │ ├── messageButtonClick.js │ │ ├── removeReaction.js │ │ ├── sendOrUpdateMessage.ts │ │ └── sendTypingIndicator.js │ ├── servers │ │ ├── banMember.js │ │ ├── bannedMembers.js │ │ ├── channels │ │ │ ├── channelPositions.js │ │ │ ├── createServerChannel.js │ │ │ ├── deleteServerChannel.js │ │ │ ├── getServerChannels.js │ │ │ ├── index.js │ │ │ ├── muteServerChannel.js │ │ │ ├── unmuteServerChannel.js │ │ │ └── updateServerChannel.js │ │ ├── createServer.js │ │ ├── deleteServer.js │ │ ├── getServer.js │ │ ├── index.js │ │ ├── invites │ │ │ ├── createCustomInvite.js │ │ │ ├── createInvite.js │ │ │ ├── deleteInvite.js │ │ │ ├── getInvites.js │ │ │ ├── index.js │ │ │ ├── inviteDetails.js │ │ │ └── joinServer.js │ │ ├── kickMember.js │ │ ├── leaveServer.js │ │ ├── muteServer.js │ │ ├── roles │ │ │ ├── applyRoleToMember.js │ │ │ ├── createRole.js │ │ │ ├── deleteRole.js │ │ │ ├── index.js │ │ │ ├── removeRoleFromMember.js │ │ │ ├── updateRole.js │ │ │ └── updateRolePosition.js │ │ ├── unBanMember.js │ │ └── updateServer.js │ ├── settings │ │ ├── addCustomEmoji.js │ │ ├── changeAppearance.js │ │ ├── changeCustomStatus.js │ │ ├── changeStatus.js │ │ ├── deleteCustomEmoji.js │ │ ├── index.js │ │ ├── linkGoogleDrive │ │ │ ├── auth.js │ │ │ ├── index.js │ │ │ └── url.js │ │ ├── renameCustomEmoji.js │ │ └── serverPosition.js │ ├── tenor │ │ ├── getCategories.ts │ │ ├── getSearches.ts │ │ └── index.ts │ ├── themes │ │ ├── deleteTheme.ts │ │ ├── getTheme.ts │ │ ├── getThemes.ts │ │ ├── index.ts │ │ ├── saveTheme.ts │ │ └── updateTheme.ts │ ├── users │ │ ├── agreeingPolicies.js │ │ ├── blockUser.js │ │ ├── confirmEmail.js │ │ ├── deleteAccount.js │ │ ├── htmlProfile │ │ │ ├── addHtmlProfile.ts │ │ │ ├── deleteHtmlProfile.ts │ │ │ ├── getHtmlProfile.ts │ │ │ └── index.ts │ │ ├── index.js │ │ ├── login.js │ │ ├── logout.js │ │ ├── register.js │ │ ├── relationship │ │ │ ├── acceptFriend.js │ │ │ ├── addFriend.js │ │ │ ├── index.js │ │ │ └── removeFriend.js │ │ ├── reset.js │ │ ├── resetRequest.js │ │ ├── survey │ │ │ ├── index.js │ │ │ ├── surveyDetails.js │ │ │ └── surveyUpdate.js │ │ ├── unblockUser.js │ │ ├── userDetails.js │ │ ├── userUpdate.js │ │ └── welcomeDone.js │ └── voice │ │ ├── index.ts │ │ ├── join.ts │ │ └── leave.ts ├── services │ └── Themes.ts ├── socket │ └── instance.ts ├── socketController │ ├── emitToAll.js │ └── emitUserStatus.js ├── socketEvents │ ├── index.js │ └── notificationDismiss.js ├── socketIO.js └── utils │ ├── SendMessageNotification.js │ ├── compressImage.ts │ ├── cropImage.ts │ ├── deleteServer.ts │ ├── genFlakeId.ts │ ├── getUserDetails.ts │ ├── joinServer.ts │ ├── kickUser.ts │ ├── rolePermConstants.js │ ├── sendPushNotification.ts │ ├── tempSaveImage.ts │ ├── tenor.ts │ ├── uploadBase64Image.js │ ├── uploadCDN │ ├── googleDrive.ts │ └── nertiviaCDN.ts │ └── zip.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/config.ts 3 | src/public/ 4 | src/fb-fcm.json 5 | dist 6 | .env 7 | server.env 8 | local.env -------------------------------------------------------------------------------- /.idea/Nertivia-Server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nertivia---Server 2 | # Screenshots 3 | ![alt text](https://raw.githubusercontent.com/supertiger1234/nertivia-desktop-app/master/Preview.png) 4 | 5 | To run Nertivia Server, please create the config.js and fb-fcm.json files, then run `node index.js` 6 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | MONGODB_ADDRESS="mongodb://localhost" 2 | JWT_SECRET="somesecret" 3 | SESSION_SECRET="anothersecret" 4 | 5 | REDIS_HOST="localhost" 6 | REDIS_PORT=6379 7 | REDIS_PASS="" 8 | 9 | # https://tenor.com/developer/dashboard 10 | TENOR_KEY="" 11 | 12 | CAPTCHA_KEY="0x0000000000000000000000000000000000000000" 13 | CAPTCHA_SITE_KEY="" 14 | 15 | 16 | DOMAIN="localhost" 17 | ALLOWED_ORIGINS=["http://localhost:8080"] 18 | 19 | SMTP_SERVICE="gmail" 20 | SMTP_USER="" 21 | SMTP_PASS="" 22 | SMTP_FROM="" 23 | 24 | FILE_CDN_SECRET="www" 25 | 26 | # Google Drive 27 | DRIVE_CLIENT_ID="" 28 | DRIVE_CLIENT_SECRET="" 29 | DRIVE_URL="" 30 | DRIVE_KEY="" 31 | 32 | 33 | DEV_MODE=false 34 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["**/*.test.ts", "**/*.spec.ts", ".git", "node_modules"], 3 | "exec": "node index.js" 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testss", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "compile": "tsc && npm run copyEnv && node dist", 8 | "build": "tsc", 9 | "copyEnv": "copyfiles -f ./.env dist", 10 | "runCompiled": "node dist", 11 | "watch": "tsc-watch --onSuccess \"npm run runCompiled\"", 12 | "dev": "npm run watch" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "@socket.io/redis-adapter": "^7.0.0", 19 | "abort-controller": "^3.0.0", 20 | "bcryptjs": "^2.4.3", 21 | "body-parser": "^1.19.0", 22 | "cheerio": "^1.0.0-rc.3", 23 | "connect-busboy": "0.0.2", 24 | "connect-history-api-fallback": "^1.6.0", 25 | "connect-redis": "^4.0.4", 26 | "cookie-parser": "^1.4.5", 27 | "cors": "^2.8.5", 28 | "deep-email-validator": "^0.1.18", 29 | "dotenv": "^8.2.0", 30 | "express": "^4.17.1", 31 | "express-session": "^1.17.1", 32 | "express-validator": "^6.5.0", 33 | "fcm-node": "^1.5.2", 34 | "firebase-admin": "^9.3.0", 35 | "flakeid": "^1.0.0", 36 | "form-data": "^4.0.0", 37 | "gm": "^1.23.1", 38 | "googleapis": "^52.1.0", 39 | "html-safe-checker": "^1.1.2", 40 | "ip-range-check": "^0.2.0", 41 | "jsdom": "^16.2.2", 42 | "jsonhtmlfyer": "^0.3.3", 43 | "jsonwebtoken": "^8.5.1", 44 | "match-all": "^1.2.5", 45 | "mongoose": "^5.12.14", 46 | "node-fetch": "^2.6.6", 47 | "nodemailer": "^6.4.8", 48 | "pako": "^1.0.11", 49 | "redis": "^3.1.2", 50 | "sharp": "^0.25.4", 51 | "socket.io": "^4.1.2", 52 | "vhost": "^3.0.2" 53 | }, 54 | "devDependencies": { 55 | "@types/bcryptjs": "^2.4.2", 56 | "@types/cheerio": "^0.22.18", 57 | "@types/connect-busboy": "0.0.2", 58 | "@types/connect-redis": "0.0.14", 59 | "@types/cors": "^2.8.6", 60 | "@types/express": "^4.17.6", 61 | "@types/express-serve-static-core": "^4.17.7", 62 | "@types/express-session": "^1.17.0", 63 | "@types/gm": "^1.18.8", 64 | "@types/jsonwebtoken": "^8.5.0", 65 | "@types/node-fetch": "^2.5.7", 66 | "@types/nodemailer": "^6.4.0", 67 | "@types/pako": "^1.0.1", 68 | "@types/redis": "^2.8.29", 69 | "@types/sharp": "^0.25.0", 70 | "copyfiles": "^2.3.0", 71 | "tsc-watch": "^4.5.0", 72 | "typescript": "^4.4.4" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Log.ts: -------------------------------------------------------------------------------- 1 | enum color { 2 | Reset = "\x1b[0m", 3 | Bright = "\x1b[1m", 4 | Dim = "\x1b[2m", 5 | Underscore = "\x1b[4m", 6 | Blink = "\x1b[5m", 7 | Reverse = "\x1b[7m", 8 | Hidden = "\x1b[8m", 9 | 10 | FgBlack = "\x1b[30m", 11 | FgRed = "\x1b[31m", 12 | FgGreen = "\x1b[32m", 13 | FgYellow = "\x1b[33m", 14 | FgBlue = "\x1b[34m", 15 | FgMagenta = "\x1b[35m", 16 | FgCyan = "\x1b[36m", 17 | FgWhite = "\x1b[37m", 18 | 19 | BgBlack = "\x1b[40m", 20 | BgRed = "\x1b[41m", 21 | BgGreen = "\x1b[42m", 22 | BgYellow = "\x1b[43m", 23 | BgBlue = "\x1b[44m", 24 | BgMagenta = "\x1b[45m", 25 | BgCyan = "\x1b[46m", 26 | BgWhite = "\x1b[47m" 27 | } 28 | 29 | function getTime() { 30 | const date = new Date(); 31 | 32 | const hours = numberHandler(date.getHours()); 33 | const minutes = numberHandler(date.getMinutes()); 34 | const seconds = numberHandler(date.getSeconds()); 35 | 36 | return `${hours}:${minutes}:${seconds}`; 37 | } 38 | function numberHandler(value: number) { 39 | return value > 9 ? value : `0${value}`; 40 | } 41 | 42 | 43 | export const Log = { 44 | info(...args: any) { 45 | console.log(`${color.FgCyan}[${getTime()}] ${color.FgGreen}[INFO]:${color.Reset}`, ...args); 46 | }, 47 | warn(...args: any) { 48 | console.log(`${color.FgCyan}[${getTime()}] ${color.FgYellow}[INFO]:${color.Reset}`, ...args); 49 | 50 | }, 51 | error(...args: any) { 52 | console.log(`${color.FgCyan}[${getTime()}] ${color.FgRed}[INFO]:${color.Reset}`, ...args); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import {getIOInstance} from './socket/instance' 2 | 3 | import express from "express"; 4 | import http from "http"; 5 | // middlewares 6 | import realIP from "./middlewares/realIP"; 7 | import redisSession from './middlewares/redisSession' 8 | import cors from "./middlewares/cors"; 9 | import bodyParser from 'body-parser'; 10 | 11 | 12 | export default function initServer() { 13 | const app = express(); 14 | app.disable('x-powered-by'); 15 | 16 | const server = new http.Server(app); 17 | 18 | const io = getIOInstance(server); 19 | 20 | //redis://[USER]:[PASSWORD]@[SERVICE-IP]:[PORT] 21 | // io.adapter(redisAdapter({ 22 | // host: process.env.REDIS_HOST, 23 | // port: process.env.REDIS_PORT, 24 | // auth_pass: process.env.REDIS_PASS 25 | // })); 26 | 27 | // middlewares 28 | app.use(bodyParser.json({limit: '10mb'})); 29 | app.use(realIP); 30 | app.use(cors); 31 | app.use(function(req, res, next){ 32 | req.io = io; 33 | next(); 34 | }) 35 | app.use(redisSession); 36 | 37 | 38 | // routes 39 | app.use('/api', require('./routes/api')); 40 | 41 | return server; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/custom.d.ts: -------------------------------------------------------------------------------- 1 | import socketio from "socket.io"; 2 | 3 | declare global { 4 | namespace Express { 5 | export interface Request { 6 | io: socketio.Server, 7 | userIP?: string | string[] | undefined, 8 | user: User, 9 | uploadFile: uploadFile, 10 | message_id: string, 11 | channel: Channel, 12 | permErrorMessage?: string, 13 | server: Server 14 | oauth2Client: any 15 | } 16 | } 17 | namespace NodeJS { 18 | interface ProcessEnv { 19 | MONGODB_ADDRESS: string 20 | JWT_SECRET: string 21 | JWT_HEADER: string 22 | SESSION_SECRET: string 23 | 24 | REDIS_HOST: string 25 | REDIS_PORT: string 26 | REDIS_PASS: string 27 | 28 | CAPTCHA_KEY: string 29 | 30 | DOMAIN: string 31 | ALLOWED_ORIGINS: string 32 | 33 | SMTP_SERVICE: string 34 | SMTP_USER: string 35 | SMTP_PASS: string 36 | SMTP_FROM: string 37 | 38 | FILE_CDN_SECRET: string 39 | 40 | DRIVE_CLIENT_ID: string 41 | DRIVE_CLIENT_SECRET: string 42 | DRIVE_URL:string 43 | DRIVE_KEY: string 44 | 45 | DEV_MODE: string 46 | } 47 | } 48 | } 49 | interface User { 50 | id: string 51 | _id: string 52 | username: string 53 | tag: string 54 | avatar: string 55 | admin: string 56 | bot?: boolean, 57 | badges?: number 58 | } 59 | 60 | interface uploadFile { 61 | file: object 62 | message: string 63 | } 64 | interface Channel { 65 | _id: string, 66 | channelId: string, 67 | server: Server 68 | recipients: any[] 69 | rateLimit?: number 70 | } 71 | interface Server { 72 | server_id: string 73 | _id: string 74 | } -------------------------------------------------------------------------------- /src/middlewares/GDriveOauthClient.js: -------------------------------------------------------------------------------- 1 | const { 2 | google 3 | } = require('googleapis'); 4 | import { Users } from "../models/Users"; 5 | 6 | module.exports = async (req, res, next) => { 7 | const oauth2Client = new google.auth.OAuth2( 8 | process.env.DRIVE_CLIENT_ID, 9 | process.env.DRIVE_CLIENT_SECRET, 10 | process.env.DRIVE_URL 11 | ); 12 | req.oauth2Client = oauth2Client; 13 | 14 | if (!req.session || !req.session['user']) return next() 15 | // check if GDriveRefreshToken exists in db 16 | if (!req.session['user'].GDriveRefreshToken) { 17 | const user = await Users.findById(req.session['user']._id, {_id: 0}).select('GDriveRefreshToken'); 18 | if (user && user.GDriveRefreshToken) { 19 | req.session['user'].GDriveRefreshToken = user.GDriveRefreshToken 20 | } 21 | } 22 | 23 | if (req.session['user'].GDriveRefreshToken && !req.session['GDriveTokens']) { 24 | try { 25 | oauth2Client.setCredentials({ 26 | refresh_token: req.session['user'].GDriveRefreshToken 27 | }) 28 | const request_access_token = await oauth2Client.getAccessToken(); 29 | req.session['GDriveTokens'] = request_access_token.res.data; 30 | oauth2Client.setCredentials(request_access_token.res.data); 31 | } catch (e) { 32 | console.log(e) 33 | } 34 | } else if (req.session['GDriveTokens']){ 35 | oauth2Client.setCredentials(req.session['GDriveTokens']); 36 | } 37 | 38 | next() 39 | } 40 | -------------------------------------------------------------------------------- /src/middlewares/avatarCacheLegacy.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const sharp = require('sharp') 3 | const fs = require('fs'); 4 | module.exports = async (req, res, next) => { 5 | 6 | const id = req.params["0"].split("/")[0]; 7 | const type = req.query.type; 8 | 9 | // find file 10 | fs.readdir( "public/cache", (err, files) => { 11 | if (!files) return next(); 12 | const filePath = files.find(f => { 13 | const name = path.parse(f).name; 14 | return name === id 15 | }) 16 | if (!filePath) return next(); 17 | fs.readFile(`public/cache/${filePath}`, (err, buffer) => { 18 | res.set('Cache-Control', 'public, max-age=31536000'); 19 | if (type && type === 'webp') { 20 | res.type('image/webp') 21 | sharp(buffer) 22 | .webp() 23 | .toBuffer() 24 | .then( data => { res.end(data); }) 25 | .catch( err => {next()}); 26 | } else { 27 | res.end(buffer); 28 | } 29 | }) 30 | }); 31 | 32 | 33 | }; -------------------------------------------------------------------------------- /src/middlewares/channelRateLimit.js: -------------------------------------------------------------------------------- 1 | const checkRolePermissions = require("./checkRolePermissions"); 2 | const rateLimit = require("./rateLimit"); 3 | const permissions = require('../utils/rolePermConstants'); 4 | 5 | module.exports = function (req, res, next) { 6 | if (!req.channel.rateLimit) { 7 | return next(); 8 | } 9 | checkRolePermissions("channel rate limit", permissions.roles.ADMIN, false)(req, res, () => { 10 | if (!req.permErrorMessage) { 11 | return next(); 12 | } 13 | rateLimit({name: 'message-' + req.channel.channelId, expire: req.channel.rateLimit, requestsLimit: 1})(req, res, next); 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/middlewares/checkGDriveAvatarLegacy.js: -------------------------------------------------------------------------------- 1 | const sharp = require("sharp"); 2 | const fs = require("fs"); 3 | const {google} = require('googleapis'); 4 | 5 | module.exports = async (req, res, next) => { 6 | const id = req.params["0"].split("/")[0]; 7 | if (id === "default.png") return next(); 8 | const url = `https://drive.google.com/uc?export=view&id=${id}`; 9 | const type = req.query.type; 10 | 11 | 12 | google.drive("v3").files.get({ 13 | fileId: id, 14 | key: process.env.DRIVE_KEY, 15 | alt: 'media', 16 | }, { 17 | responseType: 'arraybuffer' 18 | }, (err, resp) => { 19 | if (err) return next() 20 | let contentType = resp.headers["content-type"]; 21 | if (!contentType.startsWith("image/")) { 22 | res.status(404).end(); 23 | return; 24 | } 25 | res.set("Cache-Control", "public, max-age=31536000"); 26 | var buffer = new Buffer.from(resp.data, 'base64'); 27 | if (type && type === "webp") { 28 | res.type('image/webp') 29 | sharp(buffer) 30 | .webp() 31 | .toBuffer() 32 | .then(data => { 33 | res.end(data); 34 | }) 35 | .catch(err => { 36 | return next(); 37 | }); 38 | } else { 39 | res.end(buffer); 40 | } 41 | // save image to cache 42 | fs.writeFile( 43 | `public/cache/${id}.${contentType.split("/")[1]}`, 44 | buffer, 45 | "binary", 46 | () => {} 47 | ); 48 | }) 49 | }; 50 | -------------------------------------------------------------------------------- /src/middlewares/checkRolePermissions.js: -------------------------------------------------------------------------------- 1 | const { containsPerm, roles: {ADMIN} } = require('../utils/rolePermConstants'); 2 | module.exports = function (name, flag, sendError = true) { 3 | return async function (req, res, next) { 4 | 5 | 6 | const server = req.server || req.channel.server; 7 | if (!req.channel) { 8 | if (!req.server) { 9 | sendErrorMessage(req, res, `No server!`, sendError, next) 10 | return; 11 | } 12 | } else if (!server) { 13 | return next(); 14 | } 15 | // owner always has the permission 16 | const isCreator = server.creator === req.user._id 17 | if (isCreator) { 18 | return next(); 19 | } 20 | 21 | if (req.permissions === undefined) { 22 | sendErrorMessage(req, res, `Missing permission! (${name})`, sendError, next) 23 | return; 24 | } 25 | if (containsPerm(req.permissions, flag | ADMIN)) { 26 | return next(); 27 | } else { 28 | sendErrorMessage(req, res, `Missing permission! (${name})`, sendError, next) 29 | return; 30 | } 31 | } 32 | } 33 | 34 | function sendErrorMessage(req, res, message, sendError, next) { 35 | if (sendError) { 36 | res.status(403).json({ message }) 37 | return; 38 | } 39 | req.permErrorMessage = message; 40 | next(); 41 | } -------------------------------------------------------------------------------- /src/middlewares/cors.ts: -------------------------------------------------------------------------------- 1 | import cors from "cors"; 2 | 3 | export default cors({ 4 | origin: function(origin, callback) { 5 | if (!origin) return callback(null, true); 6 | if (JSON.parse(process.env.ALLOWED_ORIGINS).indexOf(origin) === -1) { 7 | return callback(null, false) 8 | } 9 | return callback(null, true) 10 | 11 | }, 12 | credentials: true, 13 | }); 14 | -------------------------------------------------------------------------------- /src/middlewares/disAllowBlockedUser.js: -------------------------------------------------------------------------------- 1 | module.exports = async (req, res, next) => { 2 | 3 | if (req.channel.isBlocked) { 4 | return res.status(403).json({ 5 | message: "The user is either blocked by you, or them." 6 | }); 7 | } 8 | next() 9 | } 10 | -------------------------------------------------------------------------------- /src/middlewares/isAdmin.js: -------------------------------------------------------------------------------- 1 | module.exports = async (req, res, next) => { 2 | if (req.user.type !== "CREATOR" && req.user.type !== "ADMIN" ) { 3 | return res.status(401).json({message: 'Admin only! Go away.'}) 4 | } 5 | next(); 6 | }; -------------------------------------------------------------------------------- /src/middlewares/loadMedia.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import sharp from "sharp"; 3 | 4 | import fetch from "node-fetch"; 5 | export default async (req: Request, res: Response, next: NextFunction) => { 6 | const id = req.params["0"].split("/")[0]; 7 | 8 | const encode = encodeURIComponent( 9 | `https://drive.google.com/uc?export=view&id=${id}` 10 | ); 11 | const url = `https://proxi.bree.workers.dev/cdn/${encode}`; 12 | const type = req.query.type; 13 | fetch(url).then(async (fetchResponse) => { 14 | if (fetchResponse.status !== 200) return res.status(404).end(); 15 | const buffer = await fetchResponse.buffer(); 16 | res.set("Cache-Control", "public, max-age=31536000"); 17 | if (type !== "webp") { 18 | res.set( 19 | "content-type", 20 | fetchResponse.headers.get("content-type") || "image/png" 21 | ); 22 | res.end(buffer); 23 | return; 24 | } 25 | res.set("content-type", "image/webp"); 26 | res.type("image/webp"); 27 | await sharp(buffer) 28 | .webp() 29 | .toBuffer() 30 | .then((data) => { 31 | res.end(data); 32 | }) 33 | }).catch(() => { 34 | res.status(404).end(); 35 | }); 36 | }; -------------------------------------------------------------------------------- /src/middlewares/rateLimit.js: -------------------------------------------------------------------------------- 1 | const { checkRateLimited } = require('../newRedisWrapper.js'); 2 | module.exports = function (options) { 3 | return async function (req, res, next) { 4 | const {name, expire, requestsLimit, useIP, nextIfInvalid} = options; 5 | 6 | const ttl = await checkRateLimited({ 7 | id: req.userIP, 8 | userId: req.user?.id, 9 | expire, 10 | name, 11 | requestsLimit: requestsLimit 12 | }) 13 | 14 | if (ttl && !nextIfInvalid) { 15 | res.status(429).json({ 16 | message: 'Slow down!', 17 | ttl, 18 | }) 19 | return; 20 | } 21 | if (ttl && nextIfInvalid) { 22 | req.rateLimited = true; 23 | } 24 | next(); 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /src/middlewares/realIP.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import ipRangeCheck from "ip-range-check"; 3 | 4 | 5 | export default (req: Request, res: Response, next: NextFunction) => { 6 | req.userIP = (req.headers["cf-connecting-ip"] || 7 | req.headers["x-forwarded-for"] || 8 | req.connection.remoteAddress)?.toString(); 9 | next(); 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /src/middlewares/redisSession.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import connectRedis from "connect-redis"; 3 | import session from "express-session"; 4 | import JWT from 'jsonwebtoken'; 5 | import {getRedisInstance} from '../redis/instance' 6 | import { RequestHandler } from "express-serve-static-core"; 7 | 8 | const RedisStore = connectRedis(session); 9 | 10 | 11 | process.on('warning', (warning) => { 12 | console.warn(warning.name); // Print the warning name 13 | console.warn(warning.message); // Print the warning message 14 | console.warn(warning.stack); // Print the stack trace 15 | }); 16 | 17 | let sessionInstance:RequestHandler | null = null; 18 | 19 | function getSessionInstance() { 20 | if (sessionInstance !== null) return sessionInstance; 21 | sessionInstance = session({ 22 | secret: process.env.SESSION_SECRET, 23 | store: new RedisStore({ 24 | client: getRedisInstance(), 25 | ttl: 600 26 | }), 27 | genid: req => { 28 | const token = process.env.JWT_HEADER + req.headers.authorization; 29 | try { 30 | // will contain user id 31 | const decryptedToken = JWT.verify(token, process.env.JWT_SECRET); 32 | return decryptedToken.toString().split("-")[0]; 33 | } catch (err) { 34 | return "notLoggedIn"; 35 | } 36 | }, 37 | saveUninitialized: false, 38 | resave: false, 39 | }) 40 | return sessionInstance; 41 | } 42 | 43 | export default (req: Request, res: Response, next: NextFunction) => { 44 | getSessionInstance()(req, res, next) 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /src/middlewares/serverChannelPermissions.js: -------------------------------------------------------------------------------- 1 | const { containsPerm, roles: {ADMIN} } = require("../utils/rolePermConstants"); 2 | 3 | function Permission(permission, defaultAllowed) { 4 | return Permission[permission, defaultAllowed] || (Permission[permission, defaultAllowed] = function(req, res, next) { 5 | if (!req.channel.server) return next(); 6 | const permissions = req.channel.permissions; 7 | 8 | if (req.channel.server.creator === req.user._id) return next() 9 | // if user has admin role. 10 | if (containsPerm(req.permissions, ADMIN)) return next() 11 | 12 | if (defaultAllowed === false) { 13 | if (!permissions) { 14 | return res.status(403).json({ 15 | status: false, 16 | message: "Missing permission: " + permission 17 | }); 18 | } 19 | if (!permissions[permission]) { 20 | return res.status(403).json({ 21 | status: false, 22 | message: "Missing permission: " + permission 23 | }); 24 | } 25 | return next(); 26 | } 27 | 28 | 29 | if (!permissions) { 30 | return next() 31 | } 32 | 33 | if (permissions[permission] === false) { 34 | return res.status(403).json({ 35 | status: false, 36 | message: "Missing permission: " + permission 37 | }); 38 | } 39 | if (permissions[permission] === true) { 40 | next() 41 | } 42 | }) 43 | } 44 | 45 | module.exports = Permission -------------------------------------------------------------------------------- /src/models/AdminActions.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose'; 2 | 3 | 4 | export interface AdminAction { 5 | action: string 6 | bannedIP: string, 7 | reason: string, 8 | admin: any, 9 | user: any, 10 | date: number, 11 | expireAt: number 12 | } 13 | 14 | const schema = new Schema({ 15 | action: {type: String, enum: [ 16 | "SUSPEND_USER", 17 | "UNSUSPEND_USER", 18 | "BAN_IP", 19 | "UNBAN_IP", 20 | "APPROVE_THEME" 21 | ]}, 22 | bannedIP: String, 23 | reason: String, 24 | admin: { type: Schema.Types.ObjectId, ref: 'users'}, 25 | user: { type: Schema.Types.ObjectId, ref: 'users'}, 26 | date: Number, 27 | expireAt: { 28 | type: Date, 29 | default: Date.now, 30 | index: {expires: 398999 } // 4.6 days 31 | }, 32 | }); 33 | 34 | 35 | export const AdminActions = model('admin_actions', schema); 36 | -------------------------------------------------------------------------------- /src/models/BannedIPs.ts: -------------------------------------------------------------------------------- 1 | import {Schema, model} from 'mongoose'; 2 | 3 | export interface BannedIP { 4 | ip: string 5 | expireAt: number 6 | } 7 | 8 | const schema = new Schema({ 9 | ip: {type: String, unique: true}, 10 | expireAt: { 11 | type: Date, 12 | default: Date.now, 13 | index: {expires: 398999 } // 4.6 days 14 | }, 15 | }); 16 | 17 | 18 | export const BannedIPs = model('banned_ips', schema); 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/models/BlockedUsers.ts: -------------------------------------------------------------------------------- 1 | import {Schema, model} from 'mongoose'; 2 | 3 | interface BlockedUser { 4 | requester: any; 5 | recipient: any; 6 | } 7 | 8 | const schema = new Schema({ 9 | requester: { type: Schema.Types.ObjectId, ref: 'users'}, 10 | recipient: { type: Schema.Types.ObjectId, ref: 'users'}, 11 | }) 12 | 13 | 14 | export const BlockedUsers = model('blocked_users', schema); -------------------------------------------------------------------------------- /src/models/Channels.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Schema, model} from 'mongoose'; 3 | 4 | 5 | // TODO: replace this with bitwise permissions 6 | interface Permissions { 7 | send_message: boolean 8 | } 9 | 10 | export enum ChannelType { 11 | DM_CHANNEL = 0, 12 | SERVER_CHANNEL = 1, 13 | SERVER_CATEGORY = 2, 14 | } 15 | interface Channel { 16 | name: string, 17 | type: ChannelType, 18 | channelId: string 19 | categoryId: string; 20 | visibility: boolean 21 | creator: any 22 | recipients: any[] 23 | hide: boolean 24 | server: any 25 | server_id: any 26 | icon: string, 27 | lastMessaged: number 28 | rateLimit: number, 29 | status: number, 30 | permissions: Permissions 31 | } 32 | const permissionsSchema = new Schema({ 33 | send_message: Boolean 34 | }) 35 | 36 | const schema = new Schema({ 37 | name: {type: String}, 38 | channelId: { type: String, required: true }, 39 | categoryId: {type: String, required: false}, 40 | type: { type: Number, required: true, enums: [ 41 | 0, // DM Channel 42 | 1, // Server Channel 43 | 2 // Server Category 44 | ]}, 45 | visibility: {type: Boolean}, 46 | creator: { type: Schema.Types.ObjectId, ref: 'users'}, 47 | recipients: [{type: Schema.Types.ObjectId, ref: 'users'}], 48 | hide: {type: Boolean, select: false, required: false}, // only used for recent dms. 49 | server: {type: Schema.Types.ObjectId, ref: 'servers'}, 50 | server_id: {type: String, required: false}, 51 | icon: {type: String, required: false}, 52 | lastMessaged: {type: Number, required: false}, 53 | // in seconds 54 | rateLimit: {type: Number, required: false}, 55 | status: { 56 | type: Number, 57 | default: 0, 58 | enums: [ 59 | 0, //'not blocked', 60 | 1, //'blocked', 61 | ] 62 | }, 63 | permissions: {type: permissionsSchema, select: true,} 64 | }) 65 | 66 | 67 | export const Channels = model('channels', schema); -------------------------------------------------------------------------------- /src/models/CustomEmojis.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose'; 2 | 3 | interface CustomEmoji { 4 | id: string 5 | user: any 6 | name: string 7 | gif: boolean 8 | } 9 | 10 | const schema = new Schema({ 11 | id: {type: String, required: true}, 12 | user: { type: Schema.Types.ObjectId, ref: 'users', required: true}, 13 | name: { type: String, required: true}, 14 | gif: {type: Boolean} 15 | }) 16 | 17 | 18 | export const CustomEmojis = model('custom_emojis', schema); -------------------------------------------------------------------------------- /src/models/Devices.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose'; 2 | 3 | interface Device { 4 | user: any 5 | userId: string, 6 | token: string, 7 | platform: string, 8 | } 9 | 10 | 11 | const schema = new Schema({ 12 | user: { type: Schema.Types.ObjectId, ref: "users" }, 13 | userId: {type: String}, 14 | token: { type: String, unique: true }, 15 | platform: { type: String } 16 | }); 17 | 18 | export const Devices = model("devices", schema); 19 | -------------------------------------------------------------------------------- /src/models/Friends.ts: -------------------------------------------------------------------------------- 1 | import {Schema, model} from 'mongoose'; 2 | 3 | interface Friend { 4 | requester: any; 5 | recipient: any; 6 | status: number 7 | } 8 | 9 | const schema = new Schema({ 10 | requester: { type: Schema.Types.ObjectId, ref: 'users'}, 11 | recipient: { type: Schema.Types.ObjectId, ref: 'users'}, 12 | status: { 13 | type: Number, 14 | enums: [ 15 | 0, //'requested', 16 | 1, //'pending', 17 | 2, //'friends', 18 | ] 19 | } 20 | }) 21 | 22 | 23 | export const Friends = model('friends', schema); -------------------------------------------------------------------------------- /src/models/MessageQuotes.ts: -------------------------------------------------------------------------------- 1 | import {Schema, model} from 'mongoose' 2 | 3 | 4 | 5 | export interface MessageQuote { 6 | message: string, 7 | messageID: string, 8 | quotedChannel: any 9 | creator: any 10 | } 11 | 12 | 13 | const schema = new Schema({ 14 | message: String, 15 | messageID: String, 16 | quotedChannel: { type : Schema.Types.ObjectId, ref: 'channels' }, 17 | creator: { type : Schema.Types.ObjectId, ref: 'users' } 18 | }); 19 | 20 | 21 | export const MessageQuotes = model('message_quotes', schema); 22 | 23 | -------------------------------------------------------------------------------- /src/models/MessageReactions.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose'; 2 | 3 | interface MessageReaction { 4 | messageID: string, 5 | emojiID: string, 6 | unicode: string, 7 | gif: boolean, 8 | reactedBy: any[] 9 | } 10 | 11 | 12 | const schema = new Schema({ 13 | messageID: String, 14 | emojiID: String, 15 | unicode: String, 16 | gif: Boolean, 17 | reactedBy: [{ type : Schema.Types.ObjectId, ref: 'users' }], 18 | }) 19 | 20 | 21 | 22 | 23 | 24 | export const MessageReactions = model('message_reactions', schema); -------------------------------------------------------------------------------- /src/models/Messages.ts: -------------------------------------------------------------------------------- 1 | import {Schema, Document, model} from 'mongoose' 2 | import flake from '../utils/genFlakeId' 3 | import { MessageQuote } from './MessageQuotes' 4 | 5 | 6 | interface Embed { 7 | title: string, 8 | type: string, 9 | url: string, 10 | image: any, 11 | site_name: string, 12 | description: string, 13 | } 14 | export interface Message { 15 | channelId: string 16 | messageID: string 17 | files?: any[] 18 | message: string 19 | creator: any 20 | created: number 21 | embed?: Embed 22 | buttons?: any[] 23 | htmlEmbed?: string 24 | mentions?: any[] 25 | quotes?: (MessageQuote & Document)['_id'] 26 | timeEdited?: number 27 | color?: string 28 | type: MessageType 29 | } 30 | 31 | enum MessageType { 32 | MESSAGE = 0, 33 | JOIN_MESSAGE = 1, 34 | LEAVE_MESSAGE = 2, 35 | KICK_MESSAGE = 3, 36 | BAN_MESSAGE = 4 37 | } 38 | 39 | const embedSchema = new Schema({ 40 | title: String, 41 | type: String, 42 | url: String, 43 | image: Object, 44 | site_name: String, 45 | description: String, 46 | }) 47 | 48 | 49 | const messagesSchema = new Schema({ 50 | channelId: { type: String, required: true }, 51 | messageID: { type: String, required: true, unique: true }, 52 | files: { type: Array, required: false }, 53 | message: { type: String, required: false }, 54 | creator: { type: Schema.Types.ObjectId, ref: 'users' }, 55 | created: { type: Number }, 56 | embed: {type: embedSchema}, 57 | buttons: {type: Array}, 58 | htmlEmbed: {type: String}, 59 | mentions: [{ type : Schema.Types.ObjectId, ref: 'users' }], 60 | quotes: [{ type : Schema.Types.ObjectId, ref: 'message_quotes' }], 61 | timeEdited: { type: Number, required: false}, 62 | color: {type: String, required: false}, 63 | type: {type: Number, default: 0, enum: [ 64 | 0, // Message 65 | 1, // Join message 66 | 2, // leave message, 67 | 3, // kick message, 68 | 4, // ban message 69 | ]} 70 | }) 71 | 72 | messagesSchema.pre('save', function() { 73 | this.messageID = flake.gen(); 74 | this.created = Date.now(); 75 | }) 76 | 77 | 78 | export const Messages = model('messages', messagesSchema); -------------------------------------------------------------------------------- /src/models/Notifications.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose'; 2 | 3 | interface Notification { 4 | recipient: string 5 | type: string 6 | mentioned: boolean 7 | channelId: string 8 | lastMessageID: string 9 | sender: any 10 | count: number 11 | } 12 | 13 | 14 | // type MESSAGE_CREATED 15 | const schema = new Schema({ 16 | recipient: { type: String, required: true }, 17 | type: { type: String, required: true }, 18 | mentioned: {type: Boolean}, 19 | channelId: { type: String, required: false }, 20 | lastMessageID: {type: String, required: false }, 21 | sender: { type: Schema.Types.ObjectId, ref: 'users', required: false}, 22 | count: {type: Number, required: false } 23 | }) 24 | 25 | 26 | export const Notifications = model('notifications', schema); -------------------------------------------------------------------------------- /src/models/PublicServers.ts: -------------------------------------------------------------------------------- 1 | import {Schema, model} from 'mongoose'; 2 | 3 | interface PublicServer { 4 | id: string 5 | creator: any 6 | server: any 7 | description: string 8 | created: number; 9 | } 10 | 11 | 12 | const schema = new Schema({ 13 | server: {type: Schema.Types.ObjectId, ref: 'servers'}, 14 | id: {type: String}, 15 | description: {type: String}, 16 | created: {type: Number, default: 0}, 17 | creator: {type: Schema.Types.ObjectId, ref: 'users'}, 18 | }); 19 | 20 | 21 | 22 | schema.pre('save', async function(next) { 23 | // Date created 24 | this.created = Date.now(); 25 | next(); 26 | }) 27 | 28 | 29 | 30 | export const PublicServers = model('public_servers', schema); -------------------------------------------------------------------------------- /src/models/PublicThemes.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose'; 2 | 3 | interface PublicTheme { 4 | id: string 5 | css: string 6 | updatedCss: string, 7 | description: string 8 | created: number 9 | approved: boolean 10 | theme: any 11 | screenshot: string 12 | creator: any 13 | likes: any[] 14 | compatible_client_version: string 15 | } 16 | 17 | const schema = new Schema({ 18 | id: {type: String, required: true}, 19 | css: {type: String, required: true}, 20 | updatedCss: {type: String, required: false}, // When the creator updates their css, it will be added here for me to approve them. 21 | description: {type: String}, 22 | created: {type: Number, default: 0}, 23 | approved: {type: Boolean, default: false}, 24 | theme: { type: Schema.Types.ObjectId, ref: 'themes' }, 25 | screenshot: {type: String}, 26 | creator: { type: Schema.Types.ObjectId, ref: 'users' }, 27 | likes: {type: [Schema.Types.ObjectId], ref: 'users'}, 28 | compatible_client_version: {type: String} 29 | }) 30 | 31 | 32 | schema.pre('save', async function(next) { 33 | // Date created 34 | this.created = Date.now(); 35 | next(); 36 | }) 37 | 38 | 39 | export const PublicThemes = model('public_themes', schema); -------------------------------------------------------------------------------- /src/models/ServerInvites.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose' 2 | 3 | 4 | interface ServerInvite { 5 | server: any 6 | creator: any 7 | invite_code: string 8 | uses: number 9 | custom: boolean 10 | } 11 | 12 | const schema = new Schema({ 13 | server: { type: Schema.Types.ObjectId, ref: 'servers', required: true, select: false }, 14 | creator: { type: Schema.Types.ObjectId, ref: 'users', required: true, select: false }, 15 | invite_code: { type: String, unique: true, required: true }, 16 | uses: { type: Number, default: 0, select: false}, 17 | custom: {type: Boolean} 18 | }) 19 | 20 | 21 | 22 | export const ServerInvites = model('server_invite', schema); 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/models/ServerMembers.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Schema, model} from 'mongoose'; 3 | 4 | 5 | interface ServerMember { 6 | member: any 7 | server: any 8 | server_id: string 9 | type: string 10 | roles: any[] 11 | muted: number 12 | muted_channels: any[] 13 | last_seen_channels: any 14 | } 15 | 16 | const schema = new Schema({ 17 | 18 | member: { type: Schema.Types.ObjectId, ref: 'users'}, 19 | server: {type: Schema.Types.ObjectId, ref: 'servers'}, 20 | server_id: {type: String}, 21 | type: {type: String, default: "MEMBER", enum: ['MEMBER','OWNER', 'ADMIN', 'BOT']}, 22 | roles: [{type: String, required: false, select: false}], 23 | muted: {type: Number, select: false, enum: [0, 1, 2]}, // enable, sound, mute all 24 | muted_channels: [{type: String, required: false, select: false}], 25 | last_seen_channels: {type: Object, select: false} 26 | 27 | }); 28 | 29 | schema.index({member: 1, server: 1}, {unique: true}); 30 | 31 | export const ServerMembers = model('server_members', schema); -------------------------------------------------------------------------------- /src/models/ServerRoles.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose'; 2 | 3 | interface ServerRole { 4 | name: string; 5 | id: string 6 | color: boolean; 7 | hideRole: boolean; 8 | permissions: number 9 | server: any 10 | server_id: string 11 | default: boolean, 12 | bot: any 13 | deletable: boolean 14 | order: number 15 | } 16 | 17 | const schema = new Schema({ 18 | name: {type: String, default: 'New Role'}, 19 | id: {type: String}, 20 | color: {type: String}, 21 | hideRole: {type: Boolean}, 22 | permissions:{type: Number, default: 0}, 23 | server: {type: Schema.Types.ObjectId, ref: 'servers'}, 24 | server_id: {type: String}, 25 | default: {type: Boolean, default: false}, // prevents them from changing certain things eg: change name of the role. 26 | bot: {type: Schema.Types.ObjectId, ref: 'user'}, 27 | deletable: {type: Boolean, default: true}, 28 | order: {type: Number}, 29 | }); 30 | 31 | 32 | 33 | export const ServerRoles = model('server_roles', schema); -------------------------------------------------------------------------------- /src/models/Servers.ts: -------------------------------------------------------------------------------- 1 | import {model, Schema} from 'mongoose'; 2 | 3 | interface UserBan { 4 | user: any, 5 | reason: string 6 | } 7 | 8 | interface Server { 9 | verified: boolean 10 | name: string 11 | avatar: string 12 | banner: string 13 | creator: any 14 | server_id: string 15 | created: number 16 | default_channel_id: string 17 | public: boolean 18 | user_bans: UserBan[] 19 | channel_position: string[] 20 | FCM_devices: any[] 21 | } 22 | 23 | // TODO: separate this into another model. 24 | const userBansSchema = new Schema({ 25 | user: { type: Schema.Types.ObjectId, ref: "users" }, 26 | reason: { type: String, required: false } 27 | }); 28 | 29 | 30 | 31 | const schema = new Schema({ 32 | verified: {type: Boolean, select: false}, 33 | name: { 34 | type: String, 35 | required: true 36 | }, 37 | avatar: { type: String, default: null}, 38 | banner: { type: String }, 39 | creator: { type: Schema.Types.ObjectId, ref: "users" }, 40 | server_id: { 41 | type: String, 42 | unique: true 43 | }, 44 | created: { 45 | type: Number 46 | }, 47 | default_channel_id: { type: String }, 48 | public: { type: Boolean }, 49 | user_bans: { type: [userBansSchema], select: false }, 50 | channel_position: [{type: String, required: false, select: false}], 51 | FCM_devices: { type: [{ type: Schema.Types.ObjectId, ref: 'devices' }], select: false }, 52 | }); 53 | 54 | schema.pre('save', async function(next) { 55 | // Date created 56 | this.created = Date.now(); 57 | next(); 58 | }) 59 | 60 | export const Servers = model("servers", schema); 61 | 62 | -------------------------------------------------------------------------------- /src/models/Themes.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model } from 'mongoose'; 2 | 3 | interface Theme { 4 | id: string 5 | name: string 6 | css: string 7 | client_version: string 8 | creator: any 9 | } 10 | 11 | const schema = new Schema({ 12 | id: {type: String, required: true}, 13 | name: {type: String, required: true}, 14 | css: {type: String, required: true}, 15 | client_version: {type: String}, 16 | creator: { type: Schema.Types.ObjectId, ref: 'users' }, 17 | }) 18 | 19 | 20 | 21 | export const Themes = model('themes', schema); -------------------------------------------------------------------------------- /src/policies/RolesPolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require("express-validator"); 2 | const policyHandler = require("./policyHandler"); 3 | 4 | const policies = { 5 | updateRole: [ 6 | check("name") 7 | .isString() 8 | .withMessage("Invalid Format.") 9 | .isLength({ min: 0, max: 30 }) 10 | .withMessage("Name must be shorter than 30 characters") 11 | .optional({ checkFalsy: true }), 12 | check("permissions") 13 | .isNumeric() 14 | .withMessage("Invalid Format.") 15 | .isLength({ min: 0, max: 30 }) 16 | .withMessage("permissions must be shorter than 30 characters") 17 | .optional({ checkFalsy: false }), 18 | check("color") 19 | .isString() 20 | .withMessage("Invalid Format.") 21 | .isLength({ min: 0, max: 15 }) 22 | .withMessage("color must be shorter than 15 characters") 23 | .optional({ checkFalsy: true }), 24 | check("hideRole") 25 | .isBoolean() 26 | .withMessage("Invalid Format.") 27 | .optional({ checkNull: true }), 28 | policyHandler 29 | ] 30 | }; 31 | 32 | module.exports = policies; 33 | -------------------------------------------------------------------------------- /src/policies/ServerPolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require("express-validator"); 2 | const policyHandler = require("./policyHandler"); 3 | 4 | const policies = { 5 | updateChannel: [ 6 | check("name") 7 | .isString().withMessage("Invalid Format.") 8 | .isLength({ min: 0, max: 30 }).withMessage("Name must be shorter than 30 characters"), 9 | check("icon") 10 | .isString().withMessage("Invalid Format.") 11 | .optional({ nullable: true }) 12 | .isLength({ min: 0, max: 60 }).withMessage("icon must be shorter than 60 characters"), 13 | policyHandler 14 | ], 15 | createChannel: [ 16 | check("name") 17 | .exists() 18 | .withMessage("Name field is empty.") 19 | .isString() 20 | .withMessage("Invalid Format.") 21 | .isLength({ min: 0, max: 30 }) 22 | .withMessage("Name must be shorter than 30 characters"), 23 | check("type") 24 | .isInt({min: 1, max: 2}) 25 | .withMessage("type must be 1 or 2.") 26 | .optional() 27 | , 28 | policyHandler 29 | ], 30 | createServer: [ 31 | check("name") 32 | .isString() 33 | .withMessage("Invalid Format.") 34 | .isLength({ min: 0, max: 30 }) 35 | .withMessage("Name must be shorter than 30 characters"), 36 | policyHandler 37 | ], 38 | updateServer: [ 39 | check("name") 40 | .isString() 41 | .withMessage("Invalid Format.") 42 | .isLength({ min: 0, max: 30 }) 43 | .withMessage("Name must be shorter than 30 characters") 44 | .optional({ checkFalsy: true }), 45 | check("default_channel_id") 46 | .isString() 47 | .withMessage("Invalid Format.") 48 | .optional({ checkFalsy: true }), 49 | check("avatar") 50 | .isString() 51 | .withMessage("Invalid Format.") 52 | .optional({ checkFalsy: true }), 53 | check("banner") 54 | .isString() 55 | .withMessage("Invalid Format.") 56 | .optional({ checkFalsy: true }), 57 | policyHandler 58 | ] 59 | }; 60 | 61 | // name: "", 62 | // gender: null, 63 | // age: null, 64 | // continent: null, 65 | // country: null, 66 | // about_me: "" 67 | module.exports = policies; 68 | -------------------------------------------------------------------------------- /src/policies/ThemePolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const policyHandler = require('./policyHandler'); 3 | 4 | const policies = { 5 | save: [ 6 | check('name').isString().withMessage('Invalid Format.').isLength({min: 3, max: 30}).withMessage("name must be between 3 - 30 chars long."), 7 | check('client_version').isString().withMessage('Invalid Format.').isLength({min: 3, max: 100}).withMessage("client_version must be between 3 - 100 chars long.").optional(true), 8 | check('css').isString().withMessage('Invalid Format.').isLength({min: 5, max: 30000}).withMessage('css must be between 5 to 30000 chars long.'), 9 | policyHandler 10 | ], 11 | 12 | } 13 | 14 | module.exports = policies -------------------------------------------------------------------------------- /src/policies/authenticationPolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const policyHandler = require('./policyHandler'); 3 | 4 | const policies = { 5 | register: [ 6 | check('email') 7 | .not().isEmpty().withMessage("Email is required.") 8 | .isEmail().withMessage("Valid email is required."), 9 | check('username') 10 | .not().isEmpty().withMessage("Username is required.") 11 | .isLength({min: 3, max: 30}).withMessage("Username must be 3 - 30 chars long.") 12 | .not().contains(':').withMessage("username cannot contain :") 13 | .not().contains('@').withMessage("username cannot contain @"), 14 | check('password') 15 | .not().isEmpty().withMessage("Password is required.") 16 | .isLength({min: 3, max: 200}).withMessage("Password must be at least 3 chars long and less than 200 chars."), 17 | policyHandler 18 | ], 19 | login: [ 20 | check('email').not().isEmpty().withMessage('Email is required.'), 21 | check('password').not().isEmpty().withMessage('Password is required.'), 22 | policyHandler 23 | ], 24 | resetRequest: [ 25 | check('email').not().isEmpty().withMessage('Email is required.'), 26 | policyHandler 27 | ], 28 | reset: [ 29 | check('password') 30 | .not().isEmpty().withMessage("Password is required.") 31 | .isLength({min: 3, max: 200}).withMessage("Password must be at least 3 chars long and less than 200 chars."), 32 | policyHandler 33 | ], 34 | confirm: [ 35 | check('email').not().isEmpty().withMessage('Email is required.'), 36 | check('code').not().isEmpty().withMessage('code is required.'), 37 | policyHandler 38 | ] 39 | } 40 | 41 | 42 | module.exports = policies -------------------------------------------------------------------------------- /src/policies/errorReportPolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const policyHandler = require('./policyHandler'); 3 | 4 | const policies = { 5 | post: [ 6 | check('message').isLength({ min: 0, max:300 }).withMessage("Maximum length exceeded (300 characters)").optional(), 7 | check('name').isLength({ min: 0, max:150 }).withMessage("Maximum length exceeded (150 characters)").optional(), 8 | check('stack').isLength({ min: 0, max:2000 }).withMessage("Maximum length exceeded (2000 characters)").optional(), 9 | check('user_message').isLength({ min: 0, max:500 }).withMessage("Maximum length exceeded (500 characters)").optional(), 10 | check('url').isLength({ min: 0, max:150 }).withMessage("Maximum length exceeded (150 characters)").optional(), 11 | policyHandler 12 | ], 13 | 14 | } 15 | 16 | module.exports = policies -------------------------------------------------------------------------------- /src/policies/forceCaptcha.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | 3 | module.exports = (req, res, next) => { 4 | if (process.env.DEV_MODE === "true") { 5 | next(); 6 | return; 7 | } 8 | req.rateLimited = true; 9 | next(); 10 | } 11 | -------------------------------------------------------------------------------- /src/policies/messagePolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const { sanitizeBody } = require('express-validator'); 3 | 4 | const policyHandler = require('./policyHandler'); 5 | 6 | const policies = { 7 | post: [ 8 | check('message').isString().withMessage('message must be a string!').optional(), 9 | check('socketID').optional(), 10 | check('tempID').optional(), 11 | check('buttons').isArray().withMessage('buttons must be an array!').optional(), 12 | check('htmlEmbed').optional(), 13 | policyHandler 14 | ], 15 | update: [ 16 | check('message').isString().withMessage('message must be a string!').optional({checkFalsy: true}), 17 | check('color').optional({checkFalsy: true}), 18 | policyHandler 19 | ] 20 | } 21 | 22 | 23 | module.exports = policies -------------------------------------------------------------------------------- /src/policies/policyHandler.js: -------------------------------------------------------------------------------- 1 | const { check, validationResult } = require('express-validator'); 2 | 3 | module.exports = (req, res, next) => { 4 | const errors = validationResult(req); 5 | if (!errors.isEmpty()) { 6 | return res.status(422).json({ status: false, errors: errors.array() }); 7 | } 8 | next(); 9 | } -------------------------------------------------------------------------------- /src/policies/publicThemePolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const policyHandler = require('./policyHandler'); 3 | 4 | const policies = { 5 | submit: [ 6 | check('screenshot').not().isEmpty().withMessage('Screenshot is required.'), 7 | check('description').not().isEmpty().withMessage('Description is required.').isLength({min: 5, max: 150}).withMessage('Description must be between 5 to 150 chars long.'), 8 | policyHandler 9 | ], 10 | update: [ 11 | check('screenshot').optional({ checkFalsy: true }), 12 | check('description').isLength({min: 5, max: 150}).withMessage('Description must be between 5 to 150 chars long.').optional({ checkFalsy: true }), 13 | policyHandler 14 | ], 15 | 16 | } 17 | 18 | module.exports = policies -------------------------------------------------------------------------------- /src/policies/reCaptchaPolicie.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | module.exports = (req, res, next) => { 3 | 4 | 5 | // decide if the captcha is required 6 | if (process.env.DEV_MODE === "true") { 7 | next(); 8 | return; 9 | } 10 | 11 | if (!req.rateLimited) { 12 | next(); 13 | return; 14 | } 15 | 16 | 17 | const { token } = req.body; 18 | if ( 19 | token === undefined || 20 | token === null || 21 | token === '' 22 | ) { 23 | return res.status(403).json({ status: false, errors: [{ msg: "Captcha is required", param: "reCaptcha", code: 1 }] }); 24 | } 25 | 26 | const verifyUrl = "https://hcaptcha.com/siteverify" 27 | const secret = process.env.CAPTCHA_KEY; 28 | const siteKey = process.env.CAPTCHA_SITE_KEY; 29 | 30 | 31 | fetch(`${verifyUrl}?secret=${secret}&response=${token}&sitekey=${siteKey}&remoteip=${req.userIP}`, { 32 | method: "post", 33 | }) 34 | .then(res => res.json()) 35 | .then(json => { 36 | if (json.success === false) { 37 | throw Error("Invalid Token") 38 | } 39 | next(); 40 | }) 41 | .catch(() => { 42 | res.status(403).json({ status: false, errors: [{ msg: "Invalid Captcha ", param: "reCaptcha" }] }); 43 | }) 44 | 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/policies/relationshipPolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const policyHandler = require('./policyHandler'); 3 | 4 | const policies = { 5 | post: [ 6 | check('username').not().isEmpty().withMessage('username is required.'), 7 | check('tag') 8 | .not().isEmpty().withMessage('tag is required.') 9 | .isLength({ min: 4, max:4 }).withMessage('tag must be 4 digits long.'), 10 | policyHandler 11 | ], 12 | put: [ 13 | check('id').not().isEmpty().withMessage('id is required.'), 14 | policyHandler 15 | ], 16 | delete: [ 17 | check('id').not().isEmpty().withMessage('id is required.'), 18 | policyHandler 19 | ] 20 | 21 | } 22 | 23 | 24 | module.exports = policies -------------------------------------------------------------------------------- /src/policies/settingsPolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const policyHandler = require('./policyHandler'); 3 | 4 | const policies = { 5 | status: [ 6 | check('status') 7 | .not().isEmpty().withMessage('status is required.') 8 | .isNumeric().withMessage('status must be a number') 9 | .isInt({min:0, max:4}).withMessage('status must be between 0 - 4') 10 | .isLength({max: 1}).withMessage('status must be 1 digit long.'), 11 | policyHandler 12 | ] 13 | 14 | } 15 | 16 | 17 | module.exports = policies -------------------------------------------------------------------------------- /src/policies/surveyPolicies.js: -------------------------------------------------------------------------------- 1 | const { check } = require('express-validator'); 2 | const policyHandler = require('./policyHandler'); 3 | 4 | const policies = { 5 | put: [ 6 | check('name').isLength({ min: 0, max:100 }).withMessage("Maximum length exceeded (100 characters)").optional({checkFalsy: true}), 7 | check('gender').isLength({ min: 0, max:15 }).withMessage("Maximum length exceeded (100 characters)").optional({checkFalsy: true}), 8 | check('age').isLength({ min: 0, max:15 }).withMessage("Maximum length exceeded (100 characters)").optional({checkFalsy: true}), 9 | check('continent').isLength({ min: 0, max:100 }).withMessage("Maximum length exceeded (100 characters)").optional({checkFalsy: true}), 10 | check('country').isLength({ min: 0, max:100 }).withMessage("Maximum length exceeded (100 characters)").optional({checkFalsy: true}), 11 | check('about_me').isLength({ min: 0, max:500 }).withMessage("Maximum length exceeded (500 characters)").optional({checkFalsy: true}), 12 | policyHandler 13 | ], 14 | 15 | } 16 | 17 | // name: "", 18 | // gender: null, 19 | // age: null, 20 | // continent: null, 21 | // country: null, 22 | // about_me: "" 23 | module.exports = policies -------------------------------------------------------------------------------- /src/redis.js: -------------------------------------------------------------------------------- 1 | import {getRedisInstance} from './redis/instance'; 2 | 3 | module.exports = { 4 | 5 | 6 | 7 | addServerMember: (userID, serverID, data) => { 8 | return wrapper('hset', `serverMembers:${serverID}`, userID, data || "{}"); 9 | }, 10 | getServerMember: (userID, serverID) => { 11 | return wrapper('hget', `serverMembers:${serverID}`, userID); 12 | }, 13 | remServerMember: (userID, serverID) => { 14 | return wrapper('hdel', `serverMembers:${serverID}`, userID); 15 | }, 16 | delAllServerMembers: (serverID) => { 17 | return wrapper('del', `serverMembers:${serverID}`); 18 | }, 19 | removeRateLimit: (key) => { 20 | return wrapper('del', `rateLimit:${key}`); 21 | }, 22 | //member 23 | 24 | rateLimitSetExpire: async (key, expire, currentTTL) => { 25 | if (currentTTL === 1 || currentTTL === -1){ 26 | const expiryMs = Math.round(1000 * expire); 27 | await wrapper('pexpire', `rateLimit:${key}`, expiryMs); 28 | } 29 | return; 30 | }, 31 | rateLimitIncr: async (key) => { 32 | const response = await multiWrapper( 33 | getRedisInstance().multi() 34 | .incr(`rateLimit:${key}`) 35 | .pttl(`rateLimit:${key}`) 36 | ); 37 | if (!response.ok) return null; 38 | return response.result; 39 | } 40 | } 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | function multiWrapper(multi) { 56 | return new Promise(resolve => { 57 | multi.exec((error, result) => { 58 | if (error) { 59 | return resolve({ok: false, error}); 60 | } 61 | return resolve({ok: true, result}); 62 | }); 63 | }) 64 | } 65 | 66 | function wrapper(method, ...args) { 67 | return new Promise(resolve => { 68 | getRedisInstance()[method](args, (error, result)=> { 69 | if (error) { 70 | return resolve({ok: false, error}); 71 | } 72 | return resolve({ok: true, result}); 73 | }) 74 | }); 75 | } -------------------------------------------------------------------------------- /src/redis/instance.ts: -------------------------------------------------------------------------------- 1 | import { RedisClient, ClientOpts } from 'redis'; 2 | 3 | export interface Redis { 4 | host: string, 5 | port: number, 6 | password: string, 7 | } 8 | 9 | let REDIS_INSTANCE: RedisClient | undefined = undefined; 10 | 11 | export function getRedisInstance(details?: Redis) { 12 | if (REDIS_INSTANCE) { 13 | return REDIS_INSTANCE; 14 | } else { 15 | if (!details) return; 16 | REDIS_INSTANCE = new RedisClient({ 17 | host: details.host, 18 | port: details.port, 19 | auth_pass: details.password 20 | }); 21 | return REDIS_INSTANCE; 22 | } 23 | } 24 | 25 | export function redisInstanceExists() { 26 | return REDIS_INSTANCE !== undefined; 27 | } -------------------------------------------------------------------------------- /src/routes/admin/Stats.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import { Users } from "../../models/Users"; 3 | import {Servers} from "../../models/Servers"; 4 | import {Messages} from '../../models/Messages' 5 | 6 | module.exports = async (_req: Request, res: Response, _next: NextFunction) => { 7 | const userCount = await Users.estimatedDocumentCount() 8 | const serverCount = await Servers.estimatedDocumentCount() 9 | const messageCount = await Messages.estimatedDocumentCount() 10 | res.json({userCount, serverCount, messageCount}); 11 | }; 12 | -------------------------------------------------------------------------------- /src/routes/admin/approveTheme.js: -------------------------------------------------------------------------------- 1 | import {PublicThemes} from '../../models/PublicThemes' 2 | 3 | module.exports = async (req, res, next) => { 4 | const { id } = req.params; 5 | const theme = await PublicThemes.findOne({id: id}).select('id description screenshot theme approved css updatedCss').populate('theme', ' -_id name id'); 6 | 7 | let query = {} 8 | 9 | try { 10 | if (!theme.approved) { 11 | query.approved = true; 12 | } else if (theme.updatedCss) { 13 | query.$unset = { 14 | updatedCss: 1 15 | }, 16 | query.css = theme.updatedCss 17 | } else { 18 | res.status(403).json({message: 'Theme is already up to date.'}); 19 | return; 20 | } 21 | 22 | await PublicThemes.updateOne({id}, query, {upsert: true}) 23 | res.status(200).end(); 24 | } catch(e) { 25 | console.log(e) 26 | res.status(403).json({messsage: "Something wen't wrong. Try again later."}); 27 | } 28 | 29 | 30 | 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /src/routes/admin/deleteServer.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | const bcrypt = require("bcryptjs"); 3 | const { default: deleteServer } = require("../../utils/deleteServer"); 4 | 5 | module.exports = async (req, res, next) => { 6 | const server_id = req.params.server_id; 7 | const adminPassword = req.body.password; 8 | 9 | if (!adminPassword) return res.status(403).json({ message: "Invalid password" }); 10 | 11 | // check admin password 12 | const admin = await Users.findById(req.user._id).select("password"); 13 | const verify = await bcrypt.compare(adminPassword, admin.password); 14 | if (!verify) return res.status(403).json({ message: "Invalid password" }); 15 | 16 | 17 | deleteServer(req.io, server_id, null, (err, status) => { 18 | if (err) return res.status(403).json({message: err.message}); 19 | if (!status) return res.status(403).json({message: "Something went wrong. Try again later."}); 20 | res.json("Server Deleted!"); 21 | }) 22 | }; 23 | -------------------------------------------------------------------------------- /src/routes/admin/getTheme.js: -------------------------------------------------------------------------------- 1 | import {PublicThemes} from '../../models/PublicThemes' 2 | 3 | 4 | module.exports = async (req, res, next) => { 5 | const { id } = req.params; 6 | const themes = await PublicThemes.findOne({id: id}).select('id description screenshot theme approved css updatedCss').populate('theme', ' -_id name id'); 7 | res.json(themes); 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /src/routes/admin/onlineUsers.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | const { getConnectedUserIds } = require("../../newRedisWrapper"); 3 | const redis = require('../../redis'); 4 | 5 | module.exports = async (req, res, next) => { 6 | 7 | 8 | const [userIds, error] = await getConnectedUserIds(); 9 | 10 | if (error || !userIds) { 11 | return res.status(403).json({message: 'Something went wrong. (Redis failed.)'}) 12 | } 13 | const onlineIds = userIds.map(i => i.split(':')[1]); 14 | const users = await Users.find({id:{ $in: onlineIds}}, {_id: 0}).select('avatar id username tag created status ip email bot').sort({_id: -1}).limit(30).lean() 15 | res.json(users); 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /src/routes/admin/recentAdminActions.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import {AdminActions} from "../../models/AdminActions"; 3 | 4 | module.exports = async (_req: Request, res: Response, _next: NextFunction) => { 5 | const adminActions = await AdminActions.find({}, { _id: 0 }).populate("admin", "username id").populate("user", "username id") 6 | .sort({ _id: -1 }) 7 | .limit(30) 8 | .lean(); 9 | res.json(adminActions); 10 | }; 11 | -------------------------------------------------------------------------------- /src/routes/admin/recentServers.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import {Servers} from "../../models/Servers"; 3 | 4 | module.exports = async (_req: Request, res: Response, _next: NextFunction) => { 5 | const servers = await Servers.find({}, { _id: 0 }) 6 | .select("avatar server_id created name creator") 7 | .populate("creator", "username id") 8 | .sort({ _id: -1 }) 9 | .limit(30) 10 | .lean(); 11 | res.json(servers); 12 | }; 13 | -------------------------------------------------------------------------------- /src/routes/admin/recentUsers.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import { Users } from "../../models/Users"; 3 | 4 | module.exports = async (_req: Request, res: Response, _next: NextFunction) => { 5 | const users = await Users.find({}, { _id: 0 }) 6 | .select("avatar id email username tag ip created banned bot banner") 7 | .sort({ _id: -1 }) 8 | .limit(30) 9 | .lean(); 10 | res.json(users); 11 | }; 12 | -------------------------------------------------------------------------------- /src/routes/admin/sameIPUsers.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | 3 | module.exports = async (req, res, next) => { 4 | const user_id = req.params.user_id; 5 | 6 | const user = await Users.findOne({id: user_id}).select("ip"); 7 | if (!user) { 8 | return res.status(403).json({ message: "User not found." }); 9 | } 10 | if (!user.ip) { 11 | return res.json([]); 12 | } 13 | 14 | 15 | const users = await Users.find({ip: user.ip}, {_id: 0}).select('avatar email id ip username tag created banned bot banner').sort({_id: -1}).limit(30).lean() 16 | res.json(users) 17 | 18 | }; -------------------------------------------------------------------------------- /src/routes/admin/searchServers.js: -------------------------------------------------------------------------------- 1 | import {Servers} from "../../models/Servers"; 2 | 3 | module.exports = async (req, res, next) => { 4 | const value = req.params.value; 5 | const servers = await Servers.find({ 6 | $or: [ 7 | {name: { '$regex' : value, '$options' : 'i' }}, 8 | {server_id: value}, 9 | ] 10 | }, {_id: 0}).select('avatar server_id created name creator').populate("creator", "username id").sort({_id: -1}).limit(30).lean() 11 | res.json(servers) 12 | 13 | }; -------------------------------------------------------------------------------- /src/routes/admin/searchUsers.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | 3 | module.exports = async (req, res, next) => { 4 | const value = req.params.value; 5 | const users = await Users.find({ 6 | $or: [ 7 | {username: { '$regex' : value, '$options' : 'i' }}, 8 | {email: { '$regex' : value, '$options' : 'i' }}, 9 | {ip: { '$regex' : value, '$options' : 'i' }}, 10 | {tag: value}, 11 | {id: value}, 12 | ] 13 | }, {_id: 0}).select('avatar email id ip username tag created banned bot banner').sort({_id: -1}).limit(30).lean() 14 | res.json(users) 15 | 16 | }; -------------------------------------------------------------------------------- /src/routes/admin/unsuspendUser.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | import {BannedIPs} from "../../models/BannedIPs"; 3 | 4 | const bcrypt = require("bcryptjs"); 5 | 6 | import {AdminActions} from "../../models/AdminActions"; 7 | 8 | module.exports = async (req, res, next) => { 9 | const user_id = req.params.id; 10 | const removeIPBan = req.params.removeIPBan; 11 | const adminPassword = req.body.password; 12 | 13 | if (!adminPassword) return res.status(403).json({ message: "Invalid password" }); 14 | 15 | // check admin password 16 | const admin = await Users.findById(req.user._id).select("password"); 17 | const verify = await bcrypt.compare(adminPassword, admin.password); 18 | if (!verify) return res.status(403).json({ message: "Invalid password" }); 19 | 20 | const userToUnsuspend = await Users.findOne({ id: user_id }).select( 21 | "ip banned type" 22 | ); 23 | if (!userToUnsuspend) { 24 | return res.status(404).json({ message: "user not found" }); 25 | } 26 | if (!userToUnsuspend.banned) { 27 | return res.status(403).json({ message: "User is not suspended." }); 28 | } 29 | 30 | 31 | await Users.updateOne( 32 | { _id: userToUnsuspend._id }, 33 | {$unset: {banned: 1, about_me: {"Suspend Reason": 1}}} 34 | ); 35 | 36 | await AdminActions.create({ 37 | action: "UNSUSPEND_USER", 38 | admin: req.user._id, 39 | user: userToUnsuspend._id, 40 | date: Date.now() 41 | }) 42 | await AdminActions.create({ 43 | action: "UNBAN_IP", 44 | admin: req.user._id, 45 | bannedIP: userToUnsuspend.ip, 46 | date: Date.now() 47 | }) 48 | 49 | if (removeIPBan && userToUnsuspend.ip) { 50 | await BannedIPs.deleteOne({ip: userToUnsuspend.ip}) 51 | } 52 | res.json("Account Suspended!"); 53 | }; -------------------------------------------------------------------------------- /src/routes/admin/waitingThemes.js: -------------------------------------------------------------------------------- 1 | import {PublicThemes} from '../../models/PublicThemes' 2 | 3 | module.exports = async (req, res, next) => { 4 | const themes = await PublicThemes.find({$or: [{approved: false}, {updatedCss: {$exists: true}}] }, {_id: 0}).select('id description screenshot theme approved').populate('theme', ' -_id name id'); 5 | res.json(themes); 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /src/routes/api.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const router = express.Router(); 4 | 5 | const loadMedia = require('../middlewares/loadMedia'); 6 | 7 | 8 | import { getAndRemoveAllRequests, ipRequestIncrement } from '../newRedisWrapper'; 9 | 10 | 11 | 12 | 13 | 14 | setInterval(async () => { 15 | const [requests, err] = await getAndRemoveAllRequests(); 16 | if (requests) { 17 | for (const ip in requests) { 18 | const count = requests[ip]; 19 | if (count >= 100) { 20 | console.log(`IP: ${ip} is sending a lot of requests (${count} in 60 seconds)`) 21 | } 22 | } 23 | } 24 | }, 60000); 25 | 26 | 27 | 28 | router.use('/*', async (req, res, next) => { 29 | const [count, err] = await ipRequestIncrement(req.userIP); 30 | if (count >= 150) { 31 | res.status(403).json({message: "You have been rate limited!"}) 32 | return; 33 | } 34 | next(); 35 | }) 36 | 37 | 38 | router.use('/error_report', require('./errorReport')); 39 | router.use('/user', require('./users')); 40 | router.use('/devices', require('./devices')); 41 | router.use('/servers', require('./servers')); 42 | router.use('/channels', require('./channels')); 43 | router.use('/themes', require('./themes').ThemeRouter) 44 | router.use('/bots', require('./bots')) 45 | router.use('/voice', require('./voice').VoiceRouter) 46 | 47 | router.use('/explore', require('./explore')) 48 | 49 | router.use('/messages', require('./messages')); 50 | 51 | router.use('/settings', require('./settings')); 52 | 53 | router.use('/files/*', require('./files')); 54 | router.use('/media/*', loadMedia.default); 55 | 56 | router.use('/admin', require('./admin')); 57 | router.use('/tenor', require('./tenor').TenorRouter); 58 | 59 | module.exports = router; -------------------------------------------------------------------------------- /src/routes/bots/botJoin.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { Users } from "../../models/Users"; 3 | import {Servers} from '../../models/Servers'; 4 | import { ServerRoles } from "../../models/ServerRoles"; 5 | import joinServer from "../../utils/joinServer"; 6 | 7 | import flake from '../../utils/genFlakeId' 8 | import { SERVER_ROLE_CREATED } from "../../ServerEventNames"; 9 | 10 | 11 | export default async function createBot(req: Request, res: Response) { 12 | const { bot_id, server_id } = req.params; 13 | const permissions = parseInt(req.body.permissions) || 0; 14 | 15 | const bot: any = await Users.findOne({ id: bot_id, bot: true }) 16 | .select("avatar tag id username admin _id") 17 | .lean(); 18 | 19 | if (!bot) { 20 | res.status(404).json({ message: "Bot not found." }) 21 | return; 22 | } 23 | 24 | //check if banned 25 | const isBanned = await Servers.exists({ 26 | _id: req.server._id, 27 | "user_bans.user": bot._id 28 | }); 29 | if (isBanned) { 30 | res.status(403).json({ message: "Bot is banned from the server." }) 31 | return; 32 | } 33 | 34 | const joined = await Users.exists({ 35 | _id: bot._id, 36 | servers: req.server._id as any 37 | }); 38 | if (joined) { 39 | res.status(403).json({ message: "Bot is already in that server." }) 40 | return; 41 | } 42 | 43 | 44 | 45 | // create role for bot 46 | const roleId = flake.gen(); 47 | await ServerRoles.updateOne({server: req.server._id, default: true}, {$inc: {order: 2}}) 48 | const doc = { 49 | name: bot.username, 50 | id: roleId, 51 | permissions: permissions, 52 | server: req.server._id, 53 | deletable: false, 54 | bot: bot._id, 55 | server_id: req.server.server_id, 56 | order: 0, 57 | hideRole: true 58 | }; 59 | await ServerRoles.create(doc); 60 | 61 | const data = { 62 | name: doc.name, 63 | permissions: doc.permissions, 64 | deletable: false, 65 | botRole: true, 66 | hideRole: true, 67 | id: roleId, 68 | server_id: server_id, 69 | order: 0, 70 | }; 71 | const io = req.io; 72 | io.in("server:" + req.server.server_id).emit(SERVER_ROLE_CREATED, data); 73 | 74 | 75 | // ready to perform join action 76 | 77 | joinServer(req.server, bot, undefined, req, res, roleId, "BOT"); 78 | 79 | } -------------------------------------------------------------------------------- /src/routes/bots/createBot.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { Users } from "../../models/Users"; 3 | 4 | export default async function createBot(req: Request, res: Response) { 5 | const botUsername = `${req.user.username}'s Bot`; 6 | 7 | //await Users.deleteMany({createdBy: req.user._id}); 8 | 9 | const botCount = await Users.countDocuments({createdBy: req.user._id}); 10 | if (botCount >= 5) { 11 | res.status(403).json({message: "You can only create 5 bots."}) 12 | return; 13 | } 14 | 15 | 16 | const newBot = await Users.create({ username: botUsername, bot: true, createdBy: req.user._id, ip: req.userIP }) 17 | res.send(newBot) 18 | } -------------------------------------------------------------------------------- /src/routes/bots/deleteBot.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | const redis = require("../../redis"); 3 | import {Users} from "../../models/Users"; 4 | import SocketIO from 'socket.io' 5 | import { deleteSession } from "../../newRedisWrapper"; 6 | import { AUTHENTICATION_ERROR } from "../../ServerEventNames"; 7 | 8 | export default async function deleteBot(req: Request, res: Response) { 9 | const { bot_id } = req.params; 10 | try { 11 | const bot: any = await Users.exists({ 12 | id: bot_id, 13 | bot: true, 14 | createdBy: req.user._id, 15 | }); 16 | if (!bot) return res.status(404).json({ message: "Bot not found." }); 17 | 18 | let error = false; 19 | await Users.updateOne( 20 | { id: bot_id }, 21 | { 22 | $set: { 23 | username: "Deleted Bot " + (Math.floor(Math.random() * 100000) + 1), 24 | created: 0 25 | }, 26 | $unset: { 27 | createdBy: 1, 28 | botCommands: 1, 29 | badges: 1, 30 | about_me: 1, 31 | custom_status: 1, 32 | avatar: 1, 33 | }, 34 | $inc: {passwordVersion: 1} 35 | } 36 | ).catch((err: any) => { 37 | console.log(err) 38 | error = true; 39 | }); 40 | if (error) { 41 | res 42 | .status(403) 43 | .json({ message: "Something went wrong while storing to database." }); 44 | return; 45 | } 46 | kickBot(req.io, bot_id); 47 | 48 | res.json({ message: "success" }); 49 | } catch (err) { 50 | res.json({ message: err }); 51 | } 52 | } 53 | 54 | 55 | async function kickBot(io: SocketIO.Server, bot_id: string) { 56 | await deleteSession(bot_id); 57 | 58 | io.in(bot_id).emit(AUTHENTICATION_ERROR, "Token outdated."); 59 | io.in(bot_id).disconnectSockets(true); 60 | } 61 | -------------------------------------------------------------------------------- /src/routes/bots/getBot.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { Users } from "../../models/Users"; 3 | import {Servers} from '../../models/Servers'; 4 | import { sign } from "jsonwebtoken"; 5 | import {ServerMembers} from '../../models/ServerMembers'; 6 | import { ServerRoles } from "../../models/ServerRoles"; 7 | import { roles } from '../../utils/rolePermConstants' 8 | 9 | export default async function createBot(req: Request, res: Response) { 10 | const { bot_id } = req.params; 11 | const { token, myservers } = req.query; 12 | 13 | let servers: any[] | undefined; 14 | const bot: any = await Users.findOne({ id: bot_id, bot: true }, { _id: 0 }) 15 | .select("avatar tag id username createdBy passwordVersion botPrefix botCommands") 16 | .populate("createdBy", "username tag avatar id") 17 | .lean(); 18 | 19 | if (!bot || !bot.createdBy) { 20 | res.status(404).json({ message: "Bot not found." }) 21 | return; 22 | } 23 | 24 | if (token && req.user && bot.createdBy._id.toString() === req.user._id) { 25 | bot.token = sign(bot.id + (bot.passwordVersion !== undefined ? `-${bot.passwordVersion}` : ''), process.env.JWT_SECRET) 26 | .split(".") 27 | .splice(1) 28 | .join("."); 29 | } 30 | delete bot.createdBy._id; 31 | 32 | if (myservers && req.user) { 33 | const myServers = await Servers.find({ creator: req.user._id }).select("name server_id avatar").lean(); 34 | const myServer_ids = myServers.map((ms: any) => ms._id); 35 | 36 | const sm = await ServerMembers.find({ member: req.user._id, roles: { $exists: true, $not: { $size: 0 } } }, { _id: 0 }).select("roles").lean(); 37 | servers = [...myServers, ...(await ServerRoles 38 | .find({ servers: { $nin: myServer_ids }, permissions: { $bitsAllSet: roles.ADMIN }, id: { $in: (sm.map((s: any) => s.roles) as any).flat() } }, { _id: 0 }) 39 | .select("server").populate("server", "name server_id") 40 | .lean()).map((s: any) => s.server)] 41 | 42 | // filter duplicates 43 | servers = servers.filter((val, i) => 44 | servers?.findIndex(v => v.server_id === val.server_id) === i 45 | ) 46 | } 47 | 48 | 49 | 50 | res.json(servers ? { bot, servers } : bot); 51 | 52 | } -------------------------------------------------------------------------------- /src/routes/bots/getCommands.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import {Users} from '../../models/Users'; 3 | 4 | export default async function updateBot(req: Request, res: Response) { 5 | const bot_ids = Object.values(req.query) as string[] 6 | if (!Array.isArray(bot_ids)) { 7 | res.status(403).send({message: "Invalid type"}) 8 | return 9 | } 10 | if (bot_ids.length >= 100) { 11 | res.status(403).send({message: "Array length must be less than 100."}) 12 | return; 13 | } 14 | 15 | const bot = await Users.find({id: {$in: bot_ids}}, {_id: 0}).select("id botCommands").lean(); 16 | res.json(bot); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/routes/bots/index.js: -------------------------------------------------------------------------------- 1 | const botsRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../middlewares/authenticate"); 5 | const UserPresentVerification = require("../../middlewares/UserPresentVerification"); 6 | const checkRolePerms = require('../../middlewares/checkRolePermissions'); 7 | const { roles: {ADMIN} } = require("../../utils/rolePermConstants"); 8 | const rateLimit = require('../../middlewares/rateLimit'); 9 | const UserPolicies = require('../../policies/UserPolicies'); 10 | 11 | // routes 12 | import createBot from './createBot'; 13 | import myBots from './myBots'; 14 | import getBot from './getBot'; 15 | import botJoin from './botJoin'; 16 | import updateBot from './updateBot'; 17 | import deleteBot from './deleteBot'; 18 | import getCommands from './getCommands'; 19 | import resetBotToken from './resetBotToken'; 20 | 21 | // create a bot 22 | botsRouter.route("/").post( 23 | authenticate(), 24 | rateLimit({name: 'create_bot', expire: 60, requestsLimit: 2 }), 25 | createBot 26 | ); 27 | 28 | // get bots created by user 29 | botsRouter.route("/").get( 30 | authenticate(), 31 | myBots 32 | ); 33 | 34 | // get commands 35 | botsRouter.route("/commands").get( 36 | authenticate(), 37 | getCommands 38 | ); 39 | 40 | 41 | // update my bot. 42 | botsRouter.route("/:bot_id").post( 43 | authenticate(), 44 | UserPolicies.updateBot, 45 | updateBot 46 | ); 47 | 48 | 49 | // delete my bot 50 | botsRouter.route("/:bot_id").delete( 51 | authenticate(), 52 | deleteBot 53 | ); 54 | 55 | 56 | // get bot. token only visable for creator. (SAFE TO USE FOR OTHER USERS.) 57 | botsRouter.route("/:bot_id").get( 58 | authenticate(false, true), 59 | getBot 60 | ); 61 | 62 | // join bot to a server 63 | botsRouter.route("/:bot_id/servers/:server_id").put( 64 | authenticate(), 65 | rateLimit({name: 'bot_join', expire: 60, requestsLimit: 5 }), 66 | UserPresentVerification, 67 | checkRolePerms('Admin', ADMIN), 68 | botJoin 69 | ); 70 | 71 | // reset token /bots/6665254446718521344/reset-token 72 | botsRouter.route("/:bot_id/reset-token").post( 73 | authenticate(), 74 | rateLimit({name: 'reset_bot_token', expire: 60, requestsLimit: 5 }), 75 | resetBotToken 76 | ); 77 | 78 | 79 | module.exports = botsRouter; 80 | -------------------------------------------------------------------------------- /src/routes/bots/myBots.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { Users } from "../../models/Users"; 3 | 4 | export default async function createBot(req: Request, res: Response) { 5 | 6 | const bots = await Users.find({createdBy: req.user._id}, {_id: 0}).select("avatar bot created tag id username").lean(); 7 | 8 | res.json(bots); 9 | 10 | } -------------------------------------------------------------------------------- /src/routes/bots/resetBotToken.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import {Users} from '../../models/Users'; 3 | const redis = require("../../redis"); 4 | import SocketIO from 'socket.io' 5 | import JWT from 'jsonwebtoken' 6 | import { deleteSession } from "../../newRedisWrapper"; 7 | import { AUTHENTICATION_ERROR } from "../../ServerEventNames"; 8 | 9 | export default async function resetBotToken(req: Request, res: Response) { 10 | const { bot_id } = req.params; 11 | 12 | const bot: any = await Users.findOne({createdBy: req.user._id, id: bot_id}).select("passwordVersion").lean(); 13 | if (!bot) { 14 | res.status(403).json({message: "Could not find bot."}) 15 | return; 16 | } 17 | 18 | await Users.updateOne({_id: bot._id}, {$inc: { passwordVersion: 1 }}); 19 | 20 | res.json({token: JWT.sign(`${bot_id}-${bot.passwordVersion ? bot.passwordVersion + 1 : 1 }`, process.env.JWT_SECRET).split(".").splice(1).join(".")}) 21 | 22 | kickBot(req.io, bot_id); 23 | } 24 | 25 | 26 | 27 | async function kickBot(io: SocketIO.Server, bot_id: string) { 28 | await deleteSession(bot_id); 29 | 30 | io.in(bot_id).emit(AUTHENTICATION_ERROR, "Token outdated."); 31 | io.in(bot_id).disconnectSockets(true); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/routes/channels/deleteChannel.js: -------------------------------------------------------------------------------- 1 | import {Channels} from '../../models/Channels'; 2 | import { CHANNEL_DELETED } from '../../ServerEventNames'; 3 | module.exports = async (req, res, next) => { 4 | const { channel_id } = req.params; 5 | 6 | 7 | // check if channel exists 8 | let channel = await Channels 9 | .findOne({ channelId: channel_id, creator: req.user._id, server_id: { $exists: false } }) 10 | if (!channel) { 11 | return res 12 | .status(404) 13 | .json({ message: "Invalid channel ID" }); 14 | } 15 | 16 | 17 | await Channels.updateOne({ channelId: channel_id, creator: req.user._id }, {hide: true}); 18 | 19 | 20 | 21 | res.json({ status: true, channelId: channel_id }); 22 | req.io.in(req.user.id).emit(CHANNEL_DELETED, { channelId: channel_id }); 23 | }; 24 | -------------------------------------------------------------------------------- /src/routes/channels/getChannel.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = async (req, res, next) => { 3 | if (req.channel.server) { 4 | res.json({ 5 | name: req.channel.name, 6 | channelId: req.channel.channelId, 7 | server_id: req.channel.server_id, 8 | }); 9 | } else { 10 | res.json({ 11 | recipients: req.channel.recipients, 12 | channelId: req.channel.channelId, 13 | }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/routes/channels/index.js: -------------------------------------------------------------------------------- 1 | const MainChannelRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../middlewares/authenticate"); 5 | const channelVerification = require("../../middlewares/ChannelVerification"); 6 | const rateLimit = require("../../middlewares/rateLimit"); 7 | 8 | // open channel 9 | MainChannelRouter.route("/:recipient_id").post( 10 | authenticate(true), 11 | require("./openChannel") 12 | ); 13 | 14 | // get channel 15 | MainChannelRouter.route("/:channelId").get( 16 | authenticate(true), 17 | channelVerification, 18 | require("./getChannel") 19 | ); 20 | 21 | //close channel 22 | MainChannelRouter.route("/:channel_id").delete( 23 | authenticate(true), 24 | require("./deleteChannel") 25 | ); 26 | 27 | // click message button 28 | //channels/${channelId}/messages/${messageID}/button/${buttonID} 29 | MainChannelRouter.route("/:channelId/messages/:messageID/button/:buttonID").post( 30 | authenticate(true), 31 | channelVerification, 32 | rateLimit({name: 'click_message_button', expire: 60, requestsLimit: 300 }), 33 | require("../messages/messageButtonClick") 34 | ) 35 | 36 | // click message button callback (only used by message creator) 37 | //channels/${channelId}/messages/${messageID}/button/${buttonID} 38 | MainChannelRouter.route("/:channelId/messages/:messageID/button/:buttonID").patch( 39 | authenticate(true), 40 | channelVerification, 41 | rateLimit({name: 'click_message_button_callback', expire: 60, requestsLimit: 300 }), 42 | require("../messages/messageButtonCallback") 43 | ) 44 | 45 | 46 | 47 | 48 | 49 | module.exports = MainChannelRouter; 50 | -------------------------------------------------------------------------------- /src/routes/channels/openChannel.js: -------------------------------------------------------------------------------- 1 | import {Users} from '../../models/Users'; 2 | import { Channels, ChannelType } from "../../models/Channels"; 3 | import { CHANNEL_CREATED } from "../../ServerEventNames"; 4 | 5 | const flake = require('../../utils/genFlakeId').default; 6 | 7 | module.exports = async (req, res, next) => { 8 | const { recipient_id } = req.params; 9 | 10 | // Check if recipient_id is valid 11 | const recipient = await Users.findOne({ id: recipient_id }); 12 | if (!recipient) { 13 | return res 14 | .status(403) 15 | .json({ status: false, message: "recipient_id is invalid." }); 16 | } 17 | 18 | // check if channel exists 19 | let channel = await Channels 20 | .findOne({ recipients: recipient._id, creator: req.user._id }) 21 | .populate({ 22 | path: "recipients", 23 | select: 24 | "-_id -password -__v -email -friends -status -created -lastSeen" 25 | }); 26 | if (channel) { 27 | await Channels.updateOne({ recipients: recipient._id, creator: req.user._id }, {hide: false}); 28 | req.io.in(req.user.id).emit(CHANNEL_CREATED, { channel }); 29 | return res.json({ status: true, channel }); 30 | } 31 | 32 | // check if channel exists 33 | channel = await Channels 34 | .findOne({ recipients: req.user._id, creator: recipient._id }) 35 | .populate({ 36 | path: "recipients", 37 | select: 38 | "-_id -password -__v -email -friends -status -created -lastSeen" 39 | }); 40 | 41 | // create channel because it doesnt exist. 42 | let channelId; 43 | 44 | if (channel) { 45 | channelId = channel.channelId; 46 | } else { 47 | channelId = flake.gen(); 48 | } 49 | 50 | let newChannel = await Channels.create({ 51 | channelId, 52 | type: ChannelType.DM_CHANNEL, 53 | creator: req.user._id, 54 | recipients: [recipient._id], 55 | lastMessaged: Date.now() 56 | }); 57 | newChannel = await Channels.findOne(newChannel).populate({ 58 | path: "recipients", 59 | select: "-_id -password -__v -email -friends -status -created -lastSeen" 60 | }); 61 | 62 | res.json({ status: true, channel: newChannel }); 63 | // sends the open channel to other clients. 64 | req.io.in(req.user.id).emit(CHANNEL_CREATED, { channel: newChannel }); 65 | }; 66 | -------------------------------------------------------------------------------- /src/routes/chat.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router() 3 | const history = require('connect-history-api-fallback'); 4 | 5 | router.use("/", express.static('public/chat')) 6 | router.use("/", history({ 7 | disableDotRule: true, 8 | // verbose: true - disable logging 9 | })); 10 | 11 | router.use("/", express.static('public/chat')) 12 | 13 | 14 | module.exports = router; -------------------------------------------------------------------------------- /src/routes/chatBeta.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router() 3 | const history = require('connect-history-api-fallback'); 4 | 5 | router.use("/", express.static('public/chatBeta')) 6 | router.use("/", history({ 7 | disableDotRule: true, 8 | // verbose: true - disable logging 9 | })); 10 | 11 | router.use("/", express.static('public/chatBeta')) 12 | 13 | 14 | module.exports = router; -------------------------------------------------------------------------------- /src/routes/devices/index.js: -------------------------------------------------------------------------------- 1 | const MainDevicesRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../middlewares/authenticate"); 5 | 6 | 7 | // register device 8 | MainDevicesRouter.route("/").post( 9 | authenticate(), 10 | require("./registerDevice") 11 | ); 12 | 13 | 14 | 15 | 16 | 17 | module.exports = MainDevicesRouter; 18 | -------------------------------------------------------------------------------- /src/routes/devices/registerDevice.js: -------------------------------------------------------------------------------- 1 | import {Devices} from '../../models/Devices'; 2 | import {Servers} from "../../models/Servers"; 3 | import { Users } from "../../models/Users"; 4 | 5 | module.exports = async (req, res, next) => { 6 | const { token } = req.body; 7 | if (!token || !token.trim()) { 8 | return res.status(403).json({ message: "Token not provided." }); 9 | } 10 | 11 | try { 12 | const saveDevice = await Devices.create({ 13 | user: req.user._id, 14 | userId: req.user.id, 15 | platform: "android", 16 | token 17 | }); 18 | 19 | 20 | // // add device to servers; 21 | const user = await Users.findById(req.user._id).select("servers"); 22 | if (user.servers.length){ 23 | await Servers.updateMany({_id: {$in: user.servers}}, {$addToSet: {FCM_devices: saveDevice._id}}) 24 | } 25 | 26 | res.json({ message: "Done" }); 27 | } catch (e) { 28 | return res.status(403).json({ message: "token already saved." }); 29 | } 30 | }; -------------------------------------------------------------------------------- /src/routes/errorReport/index.js: -------------------------------------------------------------------------------- 1 | const MainErrorReportRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const rateLimit = require("../../middlewares/rateLimit"); 5 | const policy = require("../../policies/errorReportPolicies"); 6 | 7 | 8 | // report error 9 | MainErrorReportRouter.route("/").post( 10 | rateLimit({name: 'error_report', expire: 600, requestsLimit: 10, useIP: true}), 11 | policy.post, 12 | require("./reportError") 13 | ); 14 | 15 | 16 | 17 | 18 | 19 | module.exports = MainErrorReportRouter; 20 | -------------------------------------------------------------------------------- /src/routes/errorReport/reportError.js: -------------------------------------------------------------------------------- 1 | import {Messages} from '../../models/Messages' 2 | import { Users } from "../../models/Users"; 3 | import {Channels} from "../../models/Channels"; 4 | import { MESSAGE_CREATED } from '../../ServerEventNames'; 5 | const sendMessageNotification = require('../../utils/SendMessageNotification') 6 | // create a bot in nertivia, create a server and copy the channel iDID 7 | const bot_id = "6768469612037148672" 8 | const channel_id = "6768469298621976576" 9 | const server_id = "6768469298621976577" 10 | 11 | module.exports = async (req, res, next) => { 12 | const { message, name, stack, user_message, url } = req.body; 13 | 14 | const bot = await Users.findOne({id: bot_id}); 15 | if (!bot) { 16 | res.status(403).json({message: "Bot could not be found."}); 17 | return; 18 | } 19 | 20 | const message_string = `**Name**: ${name}\n**Message:** ${message}\n**User Message:** ${user_message}\n**URL:** ${url}\n\n**Stack:**` + "```+ " +stack+ "```" 21 | 22 | const messageCreated = await Messages.create({ 23 | channel_id, 24 | message: message_string, 25 | creator: bot._id, 26 | channelId: channel_id, 27 | messageID: "placeholder", 28 | }) 29 | await Channels.updateOne({ channelId: channel_id }, { $set: { 30 | lastMessaged: Date.now() 31 | }}) 32 | 33 | res.json({message: "Done!"}); 34 | 35 | const io = req.io; 36 | 37 | io.to("server:" + server_id).emit(MESSAGE_CREATED, { 38 | message: messageCreated 39 | }) 40 | 41 | //send notification 42 | await sendMessageNotification({ 43 | message: messageCreated, 44 | channelId: channel_id, 45 | server_id: server_id, 46 | sender: bot, 47 | }) 48 | }; -------------------------------------------------------------------------------- /src/routes/explore/index.js: -------------------------------------------------------------------------------- 1 | const MainExploreRouter = require("express").Router(); 2 | 3 | 4 | // servers 5 | MainExploreRouter.use('/servers', require('./servers')); 6 | 7 | // themes 8 | MainExploreRouter.use('/themes', require('./themes')); 9 | 10 | 11 | 12 | 13 | module.exports = MainExploreRouter; 14 | -------------------------------------------------------------------------------- /src/routes/explore/servers/addPublicServersList.js: -------------------------------------------------------------------------------- 1 | const flake = require('../../../utils/genFlakeId').default; 2 | import {Servers} from '../../../models/Servers'; 3 | import {PublicServers} from '../../../models/PublicServers'; 4 | 5 | module.exports = async (req, res, next) => { 6 | const {server_id, description} = req.body; 7 | 8 | if (description.length > 150) return res.status(403).json({message: 'description must be shorter than 150 characters.'}); 9 | 10 | if (!server_id) return res.status(403).json({message: 'server_id missing.'}); 11 | 12 | // get server by id 13 | const server = await Servers.findOne({server_id}).select('name server_id creator').lean(); 14 | // if servers exists 15 | if (!server) return res.status(404).json({message: 'server does not exist.'}); 16 | // if server creator is by request 17 | if (server.creator.toString() != req.user._id) return res.status(404).json({message: 'This server is not yours.'}); 18 | 19 | 20 | // check if already public-ed 21 | const publicList = await PublicServers.findOne({server: server._id}); 22 | if (publicList) return res.status(404).json({message: 'server is already in the public list.'}); 23 | 24 | // check if user added other servers 25 | const lastTwoCreated = await PublicServers.find({creator: req.user._id}, {_id: 0}).select('created').sort({_id: -1}).limit(6); 26 | if (lastTwoCreated.length >= 5) { 27 | return res.status(403).json({message: 'You can only add up to 5 public Servers.'}); 28 | } 29 | if (lastTwoCreated.length >= 2) { 30 | let first = lastTwoCreated[0].created; 31 | let second = lastTwoCreated[1].created; 32 | 33 | // if the user already added server 2 times in an hour: 34 | if (inHour(first) && inHour(second)) 35 | return res.status(403).json({message: 'Wait an hour before adding another server.'}); 36 | 37 | } 38 | // update server 39 | const update = await Servers.updateOne({_id: server._id}, {$set: { 40 | public: true 41 | }}) 42 | 43 | // add server 44 | const add = await PublicServers.create({ 45 | id: flake.gen(), 46 | server: server._id, 47 | creator: req.user._id, 48 | description, 49 | }) 50 | 51 | 52 | res.end(); 53 | }; 54 | 55 | 56 | const inHour = (time) => ((new Date) - time) < 60 * 60 * 1000 -------------------------------------------------------------------------------- /src/routes/explore/servers/deletePublicServersList.js: -------------------------------------------------------------------------------- 1 | import {Servers} from '../../../models/Servers'; 2 | import {PublicServers} from '../../../models/PublicServers'; 3 | 4 | 5 | module.exports = async (req, res, next) => { 6 | const {server_id} = req.params; 7 | 8 | if (!server_id) return res.status(403).json({message: 'server_id missing.'}); 9 | 10 | // get server by id 11 | const server = await Servers.findOne({server_id}).select('name server_id creator').lean(); 12 | // if Servers exists 13 | if (!server) return res.status(404).json({message: 'server does not exist.'}); 14 | // if server creator is by request 15 | if (server.creator.toString() != req.user._id) return res.status(404).json({message: 'This server is not yours.'}); 16 | 17 | 18 | // check if exists 19 | const publicList = await PublicServers.findOne({server: server._id}); 20 | if (!publicList) return res.status(404).json({message: 'Server does not exist in the public list.'}); 21 | 22 | 23 | // update server 24 | const update = await Servers.updateOne({_id: server._id}, {$set: { 25 | public: false 26 | }}) 27 | 28 | // remove Server 29 | const deleteServer = await PublicServers.deleteOne({ 30 | server: server._id, 31 | }) 32 | 33 | 34 | res.end(); 35 | }; 36 | -------------------------------------------------------------------------------- /src/routes/explore/servers/getPublicServersList.js: -------------------------------------------------------------------------------- 1 | import {PublicServers} from '../../../models/PublicServers'; 2 | 3 | module.exports = async (req, res, next) => { 4 | const { verified, alphabetical, most_users, date_added } = req.query; 5 | 6 | const match = {}; 7 | let sort = null; 8 | 9 | if (verified && verified == "false") { 10 | match.$or = [{ verified: false }, { verified: { $exists: false } }]; 11 | } else if (verified && verified == "true") { 12 | match["server.verified"] = true 13 | } 14 | 15 | if (alphabetical && alphabetical == "true") { 16 | sort = { "server.name": 1 }; 17 | } else if (most_users && most_users == "false") { 18 | sort = { total_members: 1 }; 19 | } else if (date_added && date_added == "true") { 20 | sort = { created: -1 }; 21 | } 22 | 23 | const serversList = await PublicServers.aggregate([ 24 | { 25 | $lookup: { 26 | from: "servers", 27 | localField: "server", 28 | foreignField: "_id", 29 | as: "server" 30 | } 31 | }, 32 | { $unwind: "$server" }, 33 | 34 | { 35 | $lookup: { 36 | from: "users", 37 | localField: "creator", 38 | foreignField: "_id", 39 | as: "creator" 40 | } 41 | }, 42 | { $unwind: "$creator" }, 43 | 44 | { 45 | $lookup: { 46 | from: "server_members", 47 | localField: "server._id", 48 | foreignField: "server", 49 | as: "serverMembers" 50 | } 51 | }, 52 | { 53 | $project: { 54 | id: 1, 55 | server: 1, 56 | server_id: 1, 57 | invite_code: 1, 58 | description: 1, 59 | verified: 1, 60 | creator: {username: 1, id: 1,tag: 1}, 61 | created: 1, 62 | server: { avatar: 1, banner: 1, name: 1, server_id: 1, public: 1, verified: 1 }, 63 | total_members: { 64 | $size: { 65 | $filter: { 66 | input: "$serverMembers", 67 | "cond": { "$ne": [ "$$this.type", "BOT" ] } 68 | } 69 | } 70 | }, 71 | _id: 0 72 | } 73 | }, 74 | { $match: match }, 75 | { $sort: sort || { "total_members": -1 } } 76 | // { $limit: 10 } //TODO: add lazy loading (im lazy to add it yet -_-) 77 | ]); 78 | 79 | res.json(serversList); 80 | }; 81 | -------------------------------------------------------------------------------- /src/routes/explore/servers/getServer.js: -------------------------------------------------------------------------------- 1 | import {PublicServers} from '../../../models/PublicServers'; 2 | 3 | import {Servers} from '../../../models/Servers'; 4 | module.exports = async (req, res, next) => { 5 | const {server_id} = req.params; 6 | 7 | const server = await Servers.findOne({server_id}).select('_id'); 8 | if (!server) return res.status(404).json({message: 'server does not exist.'}); 9 | 10 | 11 | const serversList = await PublicServers.findOne({server: server._id}, {_id: 0}) 12 | .select('description id created') 13 | .populate({path: 'server', select: 'name server_id avatar -_id'}) 14 | if (!serversList) return res.status(404).json({message: 'does not exist.'}); 15 | 16 | 17 | 18 | res.json(serversList); 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /src/routes/explore/servers/index.js: -------------------------------------------------------------------------------- 1 | const MainServersRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../../middlewares/authenticate"); 5 | 6 | 7 | // get public servers list 8 | MainServersRouter.route('/').get( 9 | authenticate(), 10 | require("./getPublicServersList") 11 | ); 12 | 13 | // get a server 14 | MainServersRouter.route('/:server_id').get( 15 | authenticate(), 16 | require("./getServer") 17 | ); 18 | 19 | // update public server 20 | MainServersRouter.route('/:server_id').patch( 21 | authenticate(), 22 | require("./updatePublicServersList") 23 | ); 24 | 25 | // delete public server 26 | MainServersRouter.route('/:server_id').delete( 27 | authenticate(), 28 | require("./deletePublicServersList") 29 | ); 30 | 31 | // add to public servers list 32 | MainServersRouter.route('/').post( 33 | authenticate(), 34 | require("./addPublicServersList") 35 | ); 36 | 37 | 38 | 39 | 40 | module.exports = MainServersRouter; 41 | -------------------------------------------------------------------------------- /src/routes/explore/servers/updatePublicServersList.js: -------------------------------------------------------------------------------- 1 | import {Servers} from '../../../models/Servers'; 2 | import {PublicServers} from '../../../models/PublicServers'; 3 | 4 | 5 | module.exports = async (req, res, next) => { 6 | const {server_id} = req.params; 7 | const {description} = req.body; 8 | 9 | if (description.length > 150) return res.status(403).json({message: 'description must be shorter than 150 characters.'}); 10 | 11 | if (!server_id) return res.status(403).json({message: 'server_id missing.'}); 12 | 13 | // get server by id 14 | const server = await Servers.findOne({server_id}).select('name server_id creator').lean(); 15 | // if servers exists 16 | if (!server) return res.status(404).json({message: 'server does not exist.'}); 17 | // if server creator is by request 18 | if (server.creator.toString() != req.user._id) return res.status(404).json({message: 'This server is not yours.'}); 19 | 20 | // check if exists 21 | const publicList = await PublicServers.findOne({server: server._id}); 22 | if (!publicList) return res.status(404).json({message: 'Server does not exist in the public list.'}); 23 | 24 | 25 | 26 | // update server 27 | const add = await PublicServers.updateOne({ 28 | server: server._id, 29 | }, 30 | { 31 | $set: {description} 32 | }); 33 | 34 | 35 | res.end(); 36 | }; 37 | -------------------------------------------------------------------------------- /src/routes/explore/themes/AddLike.js: -------------------------------------------------------------------------------- 1 | const Express = require("express"); 2 | import {PublicThemes} from '../../../models/PublicThemes' 3 | 4 | 5 | 6 | /** @type {Express.RequestHandler} */ 7 | module.exports = async (req, res, next) => { 8 | const PublicThemeID = req.params.id; 9 | 10 | const publicTheme = await PublicThemes.updateOne({id: PublicThemeID}, { 11 | $addToSet: { 12 | likes: req.user._id 13 | } 14 | }) 15 | 16 | 17 | res.json({message: "Liked."}) 18 | }; -------------------------------------------------------------------------------- /src/routes/explore/themes/applyPublicTheme.js: -------------------------------------------------------------------------------- 1 | const Express = require("express"); 2 | import {PublicThemes} from '../../../models/PublicThemes' 3 | 4 | 5 | /** @type {Express.RequestHandler} */ 6 | module.exports = async (req, res, next) => { 7 | const PublicID = req.params.id; 8 | 9 | 10 | const publicTheme = await PublicThemes.findOne({id: PublicID}, {_id: 0}).select('id description screenshot css theme').populate('theme', ' -_id name id').lean(); 11 | if (!publicTheme) { 12 | return res.status(404).json({message: 'Invalid theme id.'}); 13 | } 14 | 15 | res.json(publicTheme) 16 | }; -------------------------------------------------------------------------------- /src/routes/explore/themes/getAllThemes.js: -------------------------------------------------------------------------------- 1 | const Express = require("express"); 2 | import {PublicThemes} from '../../../models/PublicThemes' 3 | 4 | import {ObjectId} from "mongodb" 5 | 6 | /** @type {Express.RequestHandler} */ 7 | module.exports = async (req, res, next) => { 8 | const { version } = req.query; 9 | 10 | let filters = {}; 11 | if (version) { 12 | filters.compatible_client_version = version; 13 | } 14 | 15 | // const themes = await PublicThemes.find({ approved: true, ...filters }, { _id: 0 }) 16 | // .select("id description screenshot theme creator compatible_client_version") 17 | // .populate([ 18 | // { path: "theme", select: "-_id name id" }, 19 | // { path: "creator", select: "-_id username tag id" } 20 | // ]) 21 | 22 | 23 | 24 | 25 | const themes = await PublicThemes.aggregate([ 26 | { $match: { approved: true, ...filters } }, 27 | // populate 28 | { $lookup: { from: 'users', localField: 'creator', foreignField: '_id', as: 'creator' } }, 29 | { $lookup: { from: 'themes', localField: 'theme', foreignField: '_id', as: 'theme' } }, 30 | // change [populate] to populate 31 | {$unwind: {path: '$creator'}}, 32 | {$unwind: {path: '$theme'}}, 33 | { 34 | $project: { 35 | theme: {name: 1, id: 1}, 36 | creator: {username: 1, tag: 1, id: 1}, 37 | id: 1, 38 | description: 1, 39 | screenshot: 1, 40 | compatible_client_version: 1, 41 | likes: { 42 | $size: { $ifNull: ["$likes", []] } 43 | }, 44 | liked: { 45 | $in:[new ObjectId(req.user._id), {$ifNull:["$likes", []]}] 46 | } 47 | } 48 | }, 49 | { "$sort": { "likes": -1 } }, 50 | ]); 51 | 52 | res.json(themes); 53 | }; 54 | -------------------------------------------------------------------------------- /src/routes/explore/themes/getPublicTheme.js: -------------------------------------------------------------------------------- 1 | const Express = require("express"); 2 | import {Themes} from '../../../models/Themes'; 3 | 4 | import {PublicThemes} from '../../../models/PublicThemes' 5 | 6 | 7 | /** @type {Express.RequestHandler} */ 8 | module.exports = async (req, res, next) => { 9 | const getCSS = req.query.css; 10 | 11 | const themeID = req.params.id; 12 | 13 | const theme = await Themes.findOne({id: themeID}); 14 | if (!theme) { 15 | return res.status(404).json({message: 'Invalid theme id.'}); 16 | } 17 | 18 | let select = 'id description screenshot compatible_client_version approved css updatedCss'; 19 | if (getCSS === "false") { 20 | select = 'id description screenshot compatible_client_version approved updatedCss' 21 | } 22 | 23 | const publicTheme = await PublicThemes.findOne({theme: theme._id}, {_id: 0}).select(select).lean(); 24 | if (!publicTheme) { 25 | return res.status(404).json({message: 'Invalid theme id.'}); 26 | } 27 | if (!publicTheme.approved && theme.creator.toString() !== req.user._id) { 28 | return res.status(404).json({message: 'Theme is not approved.'}); 29 | } 30 | 31 | publicTheme.updatedCss = !!publicTheme.updatedCss; 32 | 33 | res.json({...publicTheme, name: theme.name}) 34 | }; -------------------------------------------------------------------------------- /src/routes/explore/themes/index.js: -------------------------------------------------------------------------------- 1 | const MainThemesRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../../middlewares/authenticate"); 5 | const policies = require('../../../policies/publicThemePolicies'); 6 | const rateLimit = require('../../../middlewares/rateLimit'); 7 | 8 | 9 | // add theme 10 | MainThemesRouter.route('/:id').post( 11 | authenticate(), 12 | policies.submit, 13 | require("./addTheme") 14 | ); 15 | 16 | // update theme 17 | MainThemesRouter.route('/:id').patch( 18 | authenticate(), 19 | policies.update, 20 | require("./updateTheme") 21 | ); 22 | 23 | 24 | // apply a theme 25 | MainThemesRouter.route('/:id/apply').get( 26 | authenticate(), 27 | rateLimit({name: 'public_theme_apply', expire: 60, requestsLimit: 120 }), 28 | require("./applyPublicTheme") 29 | ); 30 | 31 | // like a theme 32 | MainThemesRouter.route('/:id/like').post( 33 | authenticate(), 34 | rateLimit({name: 'public_theme_like', expire: 60, requestsLimit: 120 }), 35 | require("./AddLike") 36 | ); 37 | // unlike a theme 38 | MainThemesRouter.route('/:id/like').delete( 39 | authenticate(), 40 | rateLimit({name: 'public_theme_like', expire: 60, requestsLimit: 120 }), 41 | require("./removeLike") 42 | ); 43 | 44 | 45 | // get a theme 46 | MainThemesRouter.route('/:id').get( 47 | authenticate(), 48 | require("./getPublicTheme") 49 | ); 50 | 51 | // get all themes 52 | MainThemesRouter.route('/').get( 53 | authenticate(), 54 | require("./getAllThemes") 55 | ); 56 | 57 | 58 | 59 | 60 | 61 | module.exports = MainThemesRouter; 62 | -------------------------------------------------------------------------------- /src/routes/explore/themes/removeLike.js: -------------------------------------------------------------------------------- 1 | const Express = require("express"); 2 | import {PublicThemes} from '../../../models/PublicThemes' 3 | 4 | 5 | /** @type {Express.RequestHandler} */ 6 | module.exports = async (req, res, next) => { 7 | const PublicThemeID = req.params.id; 8 | 9 | const publicTheme = await PublicThemes.updateOne({id: PublicThemeID}, { 10 | $pull: { 11 | likes: req.user._id 12 | } 13 | }) 14 | 15 | 16 | res.json({message: "Unliked."}) 17 | }; -------------------------------------------------------------------------------- /src/routes/files.js: -------------------------------------------------------------------------------- 1 | module.exports = async (req, res, next) => { 2 | const id = req.params["0"].split("/")[0]; 3 | 4 | res.redirect("https://drive.google.com/uc?export=view&id=" + id) 5 | 6 | }; 7 | -------------------------------------------------------------------------------- /src/routes/messages/addReaction.js: -------------------------------------------------------------------------------- 1 | import {MessageReactions} from '../../models/MessageReactions'; 2 | import {Messages} from '../../models/Messages' 3 | import { MESSAGE_REACTION_UPDATED } from '../../ServerEventNames'; 4 | 5 | 6 | module.exports = async (req, res, next) => { 7 | const { channelId, messageID } = req.params; 8 | const { emojiID, gif, unicode } = req.body; 9 | 10 | const message = await Messages.findOne({ channelId, messageID }); 11 | if (!message) { 12 | return res.status(404).json({ message: "Message was not found." }); 13 | } 14 | 15 | 16 | if (!emojiID && !unicode) { 17 | return res.status(403).json({ message: "Missing emojiID or unicode." }); 18 | } 19 | 20 | let filter = {messageID}; 21 | if (emojiID) { 22 | filter.emojiID = emojiID 23 | } else { 24 | filter.unicode = unicode 25 | } 26 | 27 | // check if reaction exists 28 | const reactionExists = await MessageReactions.exists({...filter}); 29 | if (!reactionExists) { 30 | const count = await MessageReactions.countDocuments({messageID}); 31 | if (count > 10) { 32 | return res.status(403).json({ message: "Maximum reaction limit reached!" }); 33 | 34 | } 35 | } 36 | 37 | 38 | // check if already reacted 39 | const alreadyReacted = await MessageReactions.exists({...filter, reactedBy: req.user._id}); 40 | if (alreadyReacted) { 41 | return res.status(403).json({ message: "Already reacted." }); 42 | 43 | } 44 | 45 | await MessageReactions.updateOne(filter, { 46 | $addToSet: { 47 | reactedBy: req.user._id 48 | }, 49 | $set: { 50 | gif: gif === true 51 | } 52 | }, {upsert: true}) 53 | 54 | const doc = await MessageReactions.findOne({...filter, reactedBy: req.user._id}); 55 | 56 | const response = { 57 | channelId, 58 | messageID, 59 | reactedByUserID: req.user.id, 60 | reaction: { 61 | emojiID: doc.emojiID, 62 | unicode: doc.unicode, 63 | gif: doc.gif, 64 | count: doc.reactedBy.length, 65 | } 66 | } 67 | 68 | if (req.channel.server) { 69 | req.io.in("server:" + req.channel.server.server_id).emit(MESSAGE_REACTION_UPDATED, response) 70 | } else { 71 | req.io.in(req.user.id).emit(MESSAGE_REACTION_UPDATED, response); 72 | req.io.in(req.channel.recipients[0].id).emit(MESSAGE_REACTION_UPDATED, response); 73 | } 74 | 75 | res.json(response); 76 | 77 | 78 | 79 | } -------------------------------------------------------------------------------- /src/routes/messages/deleteMessage.js: -------------------------------------------------------------------------------- 1 | import {Messages} from '../../models/Messages' 2 | 3 | import {MessageQuotes} from '../../models/MessageQuotes' 4 | import { MESSAGE_DELETED } from '../../ServerEventNames'; 5 | const nertiviaCDN = require("../../utils/uploadCDN/nertiviaCDN"); 6 | 7 | module.exports = async (req, res, next) => { 8 | const { channelId, messageID } = req.params; 9 | 10 | const message = await Messages.findOne({ channelId, messageID }); 11 | const channel = req.channel; 12 | const server = channel.server; 13 | const user = req.user; 14 | if (!message) { 15 | return res.status(404).json({ message: "Message was not found." }); 16 | } 17 | 18 | 19 | if (server && req.permErrorMessage) { 20 | if (message.creator.toString() !== user._id) { 21 | return res.status(403).json(req.permErrorMessage) 22 | } 23 | } 24 | 25 | if (!server && message.creator.toString() !== req.user._id) { 26 | return res.status(403).json({ message: "Can't delete this message." }); 27 | } 28 | 29 | try { 30 | await message.remove(); 31 | if (message.quotes && message.quotes.length){ 32 | await MessageQuotes.deleteMany({ 33 | _id: { 34 | $in: message.quotes 35 | } 36 | }) 37 | } 38 | const resObj = { channelId, messageID }; 39 | res.json(resObj); 40 | const io = req.io; 41 | if (server) { 42 | io.in("server:" + server.server_id).emit(MESSAGE_DELETED, resObj); 43 | } else { 44 | io.in(user.id).emit(MESSAGE_DELETED, resObj); 45 | io.in(channel.recipients[0].id).emit(MESSAGE_DELETED, resObj); 46 | } 47 | 48 | // delete image if exists 49 | const filesExist = message.files && message.files.length; 50 | const isImage = filesExist && message.files[0].dimensions; 51 | const isNertiviaCDN = filesExist && message.files[0].url.startsWith("https://") 52 | if (filesExist && isImage && isNertiviaCDN) { 53 | const path = (new URL(message.files[0].url)).pathname; 54 | nertiviaCDN.deletePath(path).catch(err => {console.log("Error deleting from CDN", err)}) 55 | } 56 | } catch (error) { 57 | console.error(error); 58 | res 59 | .status(403) 60 | .json({ message: "Something went wrong. Please try again later." }); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /src/routes/messages/deleteMessageBulk.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '../../models/Messages'; 2 | import { NextFunction, Request, Response } from "express"; 3 | import { Document, FilterQuery, LeanDocument } from "mongoose"; 4 | import { Messages } from "../../models/Messages"; 5 | import { MESSAGE_DELETED_BULK } from '../../ServerEventNames'; 6 | 7 | 8 | module.exports = async (req: Request, res: Response, next: NextFunction) => { 9 | const { channelId } = req.params; 10 | const { ids } = req.body; 11 | const channel = req.channel; 12 | const server = channel.server; 13 | const io = req.io; 14 | 15 | if (!Array.isArray(ids)) { 16 | return res.status(403).json({ message: "Please provide an array of ids." }); 17 | } 18 | 19 | if (ids.length < 2 || ids.length > 200) { 20 | return res 21 | .status(403) 22 | .json({ message: "Please provide more than 1 and less than 200 ids." }); 23 | } 24 | 25 | let messageIds: string[] = []; 26 | 27 | // if im in dms or not admin in servers, im only allowed to delete my own messages. 28 | if (!server || req.permErrorMessage) { 29 | messageIds = await findMessages({ 30 | messageID: { $in: ids }, 31 | creator: req.user._id, 32 | channelId, 33 | }); 34 | } 35 | 36 | // if im in a server and im the admin, delete anyones message 37 | if (server && !req.permErrorMessage) { 38 | messageIds = await findMessages({ 39 | messageID: { $in: ids }, 40 | channelId, 41 | }); 42 | } 43 | 44 | await Messages.deleteMany({messageID: { $in: messageIds}}) 45 | 46 | const response = { 47 | channelId: channelId, 48 | messageIds, 49 | } 50 | 51 | 52 | res.json(response) 53 | 54 | if (!messageIds.length) return; 55 | 56 | if (!server) { 57 | io.in(req.user.id).emit(MESSAGE_DELETED_BULK, response); 58 | io.in(channel.recipients[0].id).emit(MESSAGE_DELETED_BULK, response); 59 | } 60 | if (server) { 61 | io.in("server:" + server.server_id).emit(MESSAGE_DELETED_BULK, response); 62 | } 63 | }; 64 | 65 | async function findMessages(filter: FilterQuery) { 66 | return messagesToIds(await Messages.find(filter, {_id: 0}).limit(200).select("messageID").lean()); 67 | } 68 | 69 | function messagesToIds(messages: LeanDocument>[]) { 70 | return messages.map((message) => message.messageID) 71 | } 72 | -------------------------------------------------------------------------------- /src/routes/messages/getMessage.js: -------------------------------------------------------------------------------- 1 | import {Messages} from '../../models/Messages' 2 | 3 | module.exports = async (req, res, next) => { 4 | const { channelId, messageID } = req.params; 5 | 6 | const populate = [{ 7 | path: "creator", 8 | select: "avatar username id tag admin -_id bot" 9 | }, { 10 | path: "mentions", 11 | select: "avatar username id tag admin -_id" 12 | }, { 13 | path: "quotes", 14 | select: "creator message -_id", 15 | populate: { 16 | path: "creator", 17 | select: "avatar username id tag admin -_id", 18 | model: "users" 19 | } 20 | } 21 | ] 22 | 23 | // Get message 24 | let message = await Messages.findOne( 25 | { 26 | channelId, 27 | messageID 28 | }, 29 | "-__v -_id" 30 | ) 31 | .populate(populate) 32 | .lean(); 33 | 34 | if (!message) { 35 | return res.status(404).json({ 36 | message: "Invalid channelId or messageID" 37 | }); 38 | } 39 | 40 | return res.json(message); 41 | }; 42 | -------------------------------------------------------------------------------- /src/routes/messages/getReactedUsers.js: -------------------------------------------------------------------------------- 1 | import {MessageReactions} from '../../models/MessageReactions'; 2 | 3 | module.exports = async (req, res, next) => { 4 | const {channelId, messageID} = req.params; 5 | 6 | const {emojiID, unicode} = req.query; 7 | 8 | let limit = parseInt(req.query.limit || "100"); 9 | let skip = parseInt(req.query.skip || "0"); 10 | 11 | if (limit < 1 || limit > 100) { 12 | limit = 100; 13 | } 14 | 15 | let filter = {messageID}; 16 | if (emojiID) { 17 | filter.emojiID = emojiID 18 | } else { 19 | filter.unicode = unicode 20 | } 21 | 22 | const reaction = await MessageReactions.findOne(filter, {reactedBy:{$slice:[skip, limit]}}).populate("reactedBy", "username tag id avatar") 23 | if (!reaction) { 24 | return res.status(404).json({ message: "Reaction not found" }); 25 | } 26 | res.json(reaction.reactedBy); 27 | 28 | 29 | } -------------------------------------------------------------------------------- /src/routes/messages/messageButtonCallback.js: -------------------------------------------------------------------------------- 1 | import {Messages} from '../../models/Messages' 2 | import { MESSAGE_BUTTON_CALLBACK } from '../../ServerEventNames'; 3 | 4 | module.exports = async (req, res, next) => { 5 | const { channelId, messageID, buttonID } = req.params; 6 | const { message, clickedByID } = req.body; 7 | 8 | 9 | const messageDB = await Messages.findOne({ channelId, messageID, "buttons.id": buttonID }).select("creator"); 10 | const channel = req.channel; 11 | const server = channel.server; 12 | const user = req.user; 13 | 14 | if (!messageDB) { 15 | return res.status(404).json({ message: "Message was not found." }); 16 | } 17 | 18 | if (user._id !== messageDB.creator._id.toString()) { 19 | return res.status(403).json({ message: "You are not the message creator." }); 20 | } 21 | if (message && message.length >= 60) { 22 | return res.status(403).json({ message: "Message can only contain less than 60 characters." }); 23 | } 24 | if (!clickedByID) { 25 | return res.status(403).json({ message: "clickedByID is required." }); 26 | } 27 | const io = req.io; 28 | const resObj = { 29 | id: buttonID, 30 | channelId, 31 | messageID, 32 | message 33 | } 34 | if (server) { 35 | resObj.serverID = server.server_id 36 | } 37 | 38 | io.in(clickedByID).emit(MESSAGE_BUTTON_CALLBACK, resObj) 39 | 40 | res.status(200).json({message: "Response Sent!"}); 41 | }; -------------------------------------------------------------------------------- /src/routes/messages/messageButtonClick.js: -------------------------------------------------------------------------------- 1 | import {Messages} from '../../models/Messages' 2 | import { MESSAGE_BUTTON_CLICKED } from '../../ServerEventNames'; 3 | 4 | module.exports = async (req, res, next) => { 5 | const { channelId, messageID, buttonID } = req.params; 6 | 7 | 8 | const message = await Messages.findOne({ channelId, messageID, "buttons.id": buttonID }).select("creator").populate("creator", "id"); 9 | const channel = req.channel; 10 | const server = channel.server; 11 | const user = req.user; 12 | 13 | if (!message) { 14 | return res.status(404).json({ message: "Message was not found." }); 15 | } 16 | 17 | const io = req.io; 18 | const resObj = { 19 | id: buttonID, 20 | channelId, 21 | messageID, 22 | clickedByID: user.id 23 | } 24 | if (server) { 25 | resObj.serverID = server.server_id 26 | } 27 | 28 | io.in(message.creator.id).emit(MESSAGE_BUTTON_CLICKED, resObj) 29 | res.status(200).json({message: "Waiting for bot response..."}); 30 | }; -------------------------------------------------------------------------------- /src/routes/messages/removeReaction.js: -------------------------------------------------------------------------------- 1 | import {MessageReactions} from '../../models/MessageReactions'; 2 | import {Messages} from '../../models/Messages' 3 | import { MESSAGE_REACTION_UPDATED } from '../../ServerEventNames'; 4 | 5 | module.exports = async (req, res, next) => { 6 | const { channelId, messageID } = req.params; 7 | const { emojiID, unicode } = req.body; 8 | 9 | 10 | const message = await Messages.findOne({ channelId, messageID }); 11 | if (!message) { 12 | return res.status(404).json({ message: "Message was not found." }); 13 | } 14 | if (!emojiID && !unicode) { 15 | return res.status(403).json({ message: "Missing emojiID or unicode." }); 16 | } 17 | 18 | let filter = {messageID}; 19 | if (emojiID) { 20 | filter.emojiID = emojiID 21 | } else { 22 | filter.unicode = unicode 23 | } 24 | 25 | const reaction = await MessageReactions.findOne(filter); 26 | if (!reaction) { 27 | return res.status(403).json({ message: "Reaction not found" }); 28 | } 29 | 30 | 31 | // check if already reacted 32 | const didReact = reaction.reactedBy.find(_id => _id.toString() === req.user._id.toString()) 33 | if (!didReact) { 34 | return res.status(403).json({ message: "You did not react." }); 35 | } 36 | const count = reaction.reactedBy.length; 37 | 38 | if (count === 1) { 39 | await MessageReactions.deleteOne(filter) 40 | } else { 41 | await MessageReactions.updateOne(filter, {$pull: {reactedBy: req.user._id}}) 42 | } 43 | 44 | 45 | const response = { 46 | channelId, 47 | messageID, 48 | unReactedByUserID: req.user.id, 49 | reaction: { 50 | emojiID: reaction.emojiID, 51 | unicode: reaction.unicode, 52 | gif: reaction.gif, 53 | count: count - 1, 54 | } 55 | } 56 | 57 | 58 | if (req.channel.server) { 59 | req.io.in("server:" + req.channel.server.server_id).emit(MESSAGE_REACTION_UPDATED, response) 60 | } else { 61 | req.io.in(req.user.id).emit(MESSAGE_REACTION_UPDATED, response); 62 | req.io.in(req.channel.recipients[0].id).emit(MESSAGE_REACTION_UPDATED, response); 63 | } 64 | 65 | res.json(response); 66 | 67 | 68 | } -------------------------------------------------------------------------------- /src/routes/messages/sendTypingIndicator.js: -------------------------------------------------------------------------------- 1 | const { USER_TYPING } = require("../../ServerEventNames"); 2 | 3 | module.exports = async (req, res, next) => { 4 | const { channelId } = req.params; 5 | res.status(204).end(); 6 | 7 | // emit to users 8 | const io = req.io; 9 | 10 | if (req.channel && req.channel.server) { 11 | io.in("server:" + req.channel.server.server_id).emit(USER_TYPING, { 12 | channel_id: channelId, 13 | user: { id: req.user.id, username: req.user.username } 14 | }); 15 | return; 16 | } 17 | 18 | if (req.channel && req.channel.recipients) { 19 | for (let recipients of req.channel.recipients) { 20 | io.in(recipients.id).emit(USER_TYPING, { 21 | channel_id: channelId, 22 | user: { id: req.user.id, username: req.user.username } 23 | }); 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/routes/servers/bannedMembers.js: -------------------------------------------------------------------------------- 1 | 2 | import {Servers} from "../../models/Servers"; 3 | module.exports = async (req, res, next) => { 4 | const server = req.server; 5 | 6 | // get banned list 7 | const serversList = await Servers.findById(server._id, {_id: 0}).select('user_bans').populate({ 8 | path: 'user_bans.user', 9 | select: 'username tag id avatar -_id' 10 | }).lean() 11 | 12 | res.json(serversList.user_bans); 13 | 14 | }; 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/routes/servers/channels/channelPositions.js: -------------------------------------------------------------------------------- 1 | import { Channels, ChannelType } from '../../../models/Channels'; 2 | import {Servers} from '../../../models/Servers'; 3 | const { SERVER_CHANNEL_POSITION_UPDATED } = require('../../../ServerEventNames'); 4 | module.exports = async (req, res, next) => { 5 | 6 | 7 | const io = req.io; 8 | const { channel_position, category } = req.body; 9 | 10 | // check if there are more than 200 entries 11 | if (channel_position.length >= 200) { 12 | return res.status(403).json({ 13 | message: 'Limit reached (max: 200)', 14 | }) 15 | } 16 | 17 | for (let index = 0; index < channel_position.length; index++) { 18 | const element = channel_position[index]; 19 | if (element.length >= 50 || typeof element !== "string") { 20 | return res.status(403).json({ 21 | message: 'Invalid channelId format.', 22 | }) 23 | } 24 | } 25 | 26 | if (category) { 27 | const categoryId = category.id; 28 | const channelId = category.channelId; 29 | 30 | const channel = await Channels.findOne({channelId: channelId, server_id: req.server.server_id, type: ChannelType.SERVER_CHANNEL}).select("channelId"); 31 | if (!channel) { 32 | return res.status(404).json({ 33 | message: 'Channel not found.', 34 | }) 35 | } 36 | 37 | if (categoryId) { 38 | const categoryChannel = await Channels.findOne({channelId: categoryId, server_id: req.server.server_id, type: ChannelType.SERVER_CATEGORY}).select("channelId"); 39 | 40 | if (!categoryChannel) { 41 | return res.status(404).json({ 42 | message: 'Category not found.', 43 | }) 44 | } 45 | await channel.updateOne({$set: {categoryId}}); 46 | 47 | } 48 | if (!categoryId) { 49 | await channel.updateOne({$unset: {categoryId: 1}}); 50 | } 51 | 52 | } 53 | 54 | try { 55 | const update = await Servers.updateOne( 56 | { _id: req.server._id }, 57 | {channel_position: channel_position}, 58 | {upsert: true}, 59 | ); 60 | res.json({ 61 | channel_position 62 | }); 63 | io.in('server:' + req.server.server_id).emit(SERVER_CHANNEL_POSITION_UPDATED, {serverID: req.server.server_id, channel_position, category} ); 64 | return; 65 | } catch(e) { 66 | return res.status(403).json({ 67 | message: 'Something went wrong, try again later.', 68 | }); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/routes/servers/channels/createServerChannel.js: -------------------------------------------------------------------------------- 1 | const flake = require('../../../utils/genFlakeId').default; 2 | import {Channels, ChannelType} from "../../../models/Channels"; 3 | import { SERVER_CHANNEL_CREATED } from "../../../ServerEventNames"; 4 | 5 | module.exports = async (req, res, next) => { 6 | 7 | const { name, type } = req.body; 8 | // check if channels exceeded limit 9 | 10 | const channels = await Channels.find({ server: req.server._id }); 11 | if (channels.length >= 50) { 12 | return res 13 | .status(403) 14 | .json({ message: "Channel limit reached (50 channels)" }); 15 | } 16 | 17 | 18 | const channel = await createChannel(req.server, name, type) 19 | 20 | 21 | const io = req.io; 22 | io.in("server:" + req.server.server_id).emit(SERVER_CHANNEL_CREATED, { 23 | channel 24 | }); 25 | 26 | res.json({ channel }); 27 | }; 28 | 29 | async function createChannel(server, name, type) { 30 | if (type === undefined || type === ChannelType.SERVER_CHANNEL) { 31 | return createTextChannel(server, name) 32 | } 33 | if (type === ChannelType.SERVER_CATEGORY) { 34 | return createCategoryChannel(server, name) 35 | } 36 | } 37 | 38 | async function createTextChannel(server, name) { 39 | const createChannel = await Channels.create({ 40 | name: name, 41 | type: ChannelType.SERVER_CHANNEL, 42 | channelId: flake.gen(), 43 | server: server._id, 44 | server_id: server.server_id, 45 | lastMessaged: Date.now() 46 | }); 47 | 48 | const channelObj = { 49 | channelId: createChannel.channelId, 50 | type: createChannel.type, 51 | lastMessaged: createChannel.lastMessaged, 52 | name: createChannel.name, 53 | server_id: server.server_id, 54 | }; 55 | return channelObj; 56 | } 57 | async function createCategoryChannel(server, name) { 58 | const createChannel = await Channels.create({ 59 | name: name, 60 | type: ChannelType.SERVER_CATEGORY, 61 | channelId: flake.gen(), 62 | server: server._id, 63 | server_id: server.server_id, 64 | }); 65 | 66 | const channelObj = { 67 | channelId: createChannel.channelId, 68 | type: createChannel.type, 69 | name: createChannel.name, 70 | server_id: server.server_id, 71 | }; 72 | return channelObj; 73 | } -------------------------------------------------------------------------------- /src/routes/servers/channels/deleteServerChannel.js: -------------------------------------------------------------------------------- 1 | import {Channels, ChannelType} from "../../../models/Channels"; 2 | import {Messages} from '../../../models/Messages' 3 | 4 | import {MessageQuotes} from '../../../models/MessageQuotes' 5 | import { deleteServerChannel } from '../../../newRedisWrapper'; 6 | import { Notifications } from '../../../models/Notifications'; 7 | import { SERVER_CHANNEL_DELETED } from "../../../ServerEventNames"; 8 | const redis = require("../../../redis"); 9 | 10 | module.exports = async (req, res, next) => { 11 | 12 | const server = req.server; 13 | const channelId = req.params.channel_id; 14 | // check if its default channel 15 | if (req.server.default_channel_id.toString() === channelId.toString()) { 16 | return res.status(403).json({ message: "Cannot delete default channel." }); 17 | } 18 | try { 19 | await MessageQuotes.deleteMany({quotedChannel: req.channel._id}) 20 | await Notifications.deleteMany({ channelId }); 21 | await Channels.deleteOne({ channelId }); 22 | if (req.channel.type === ChannelType.SERVER_CATEGORY) { 23 | await Channels.updateMany({server_id: server.server_id, categoryId: channelId}, {$unset: {categoryId: 1}}) 24 | } 25 | await Messages.deleteMany({ channelId }); 26 | await deleteServerChannel(channelId); 27 | const io = req.io; 28 | io.in("server:" + req.server.server_id).emit(SERVER_CHANNEL_DELETED, { 29 | channelId, 30 | server_id: server.server_id 31 | }); 32 | res.json({ channelId, server_id: server.server_id }); 33 | } catch (e) { 34 | return res 35 | .status(403) 36 | .json({ message: "Something went wrong. Try again later." }); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/routes/servers/channels/getServerChannels.js: -------------------------------------------------------------------------------- 1 | import {Channels} from "../../../models/Channels"; 2 | 3 | module.exports = async (req, res, next) => { 4 | // find all channels 5 | const channels = await Channels.find({ server: req.server._id }); 6 | res.json(channels); 7 | }; 8 | -------------------------------------------------------------------------------- /src/routes/servers/channels/index.js: -------------------------------------------------------------------------------- 1 | const MainChannelRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../../middlewares/authenticate"); 5 | const UserPresentVerification = require ('../../../middlewares/UserPresentVerification') 6 | const serverPolicy = require("../../../policies/ServerPolicies"); 7 | const checkRolePerms = require('../../../middlewares/checkRolePermissions'); 8 | const { roles: {MANAGE_CHANNELS}} = require("../../../utils/rolePermConstants"); 9 | // Channels 10 | MainChannelRouter.route('/:server_id/channels').get( 11 | authenticate(), 12 | UserPresentVerification, 13 | require("./getServerChannels") 14 | ); 15 | 16 | // Create 17 | MainChannelRouter.route('/:server_id/channels').put( 18 | authenticate(true), 19 | UserPresentVerification, 20 | checkRolePerms('Channels', MANAGE_CHANNELS), 21 | serverPolicy.createChannel, 22 | require("./createServerChannel") 23 | ); 24 | 25 | // Update 26 | MainChannelRouter.route('/:server_id/channels/:channel_id').patch( 27 | authenticate(true), 28 | UserPresentVerification, 29 | checkRolePerms('Channels', MANAGE_CHANNELS), 30 | serverPolicy.updateChannel, 31 | require("./updateServerChannel") 32 | ); 33 | 34 | // Delete 35 | MainChannelRouter.route('/:server_id/channels/:channel_id').delete( 36 | authenticate(true), 37 | UserPresentVerification, 38 | checkRolePerms('Channels', MANAGE_CHANNELS), 39 | require("./deleteServerChannel") 40 | ); 41 | 42 | // mute server channel 43 | MainChannelRouter.route('/:server_id/channels/:channel_id/mute').put( 44 | authenticate(), 45 | UserPresentVerification, 46 | require("./muteServerChannel") 47 | ); 48 | 49 | // unmute server channel 50 | MainChannelRouter.route('/:server_id/channels/:channel_id/mute').delete( 51 | authenticate(), 52 | UserPresentVerification, 53 | require("./unmuteServerChannel") 54 | ); 55 | 56 | // position 57 | MainChannelRouter.route('/:server_id/channels/position').put( 58 | authenticate(), 59 | UserPresentVerification, 60 | checkRolePerms('Channels', MANAGE_CHANNELS), 61 | require("./channelPositions") 62 | ); 63 | 64 | module.exports = MainChannelRouter; 65 | -------------------------------------------------------------------------------- /src/routes/servers/channels/muteServerChannel.js: -------------------------------------------------------------------------------- 1 | import { Notifications } from "../../../models/Notifications"; 2 | import {ServerMembers} from "../../../models/ServerMembers"; 3 | import { CHANNEL_MUTED } from "../../../ServerEventNames"; 4 | 5 | module.exports = async (req, res, next) => { 6 | const { channel_id, server_id } = req.params; 7 | 8 | // check if channel exists in server. 9 | if (req.channel.server_id !== server_id) { 10 | return res.status(404).json({ message: "Channel not found." }); 11 | } 12 | 13 | // check if already muted 14 | const isMuted = await ServerMembers.exists({ member: req.user._id, server_id: req.channel.server_id, muted_channels: channel_id }); 15 | 16 | if (isMuted) { 17 | return res.status(403).json({ message: "Channel is already muted!" }); 18 | } 19 | 20 | await ServerMembers.updateOne( 21 | { member: req.user._id, server_id: req.channel.server_id }, 22 | { $addToSet: { muted_channels: channel_id } } 23 | ); 24 | await Notifications.deleteMany({ 25 | channelId: channel_id, 26 | recipient: req.user.id 27 | }); 28 | 29 | res.json({ message: "Channel muted." }); 30 | 31 | const io = req.io; 32 | io.in(req.user.id).emit(CHANNEL_MUTED, {channelId: channel_id}); 33 | }; 34 | -------------------------------------------------------------------------------- /src/routes/servers/channels/unmuteServerChannel.js: -------------------------------------------------------------------------------- 1 | import {ServerMembers} from "../../../models/ServerMembers"; 2 | import { CHANNEL_UNMUTED } from "../../../ServerEventNames"; 3 | 4 | module.exports = async (req, res, next) => { 5 | const { channel_id, server_id } = req.params; 6 | 7 | // check if channel exists in server. 8 | if (req.channel.server_id !== server_id) { 9 | return res.status(404).json({ message: "Channel not found." }); 10 | } 11 | 12 | // check if already not muted 13 | const isMuted = await ServerMembers.exists({ member: req.user._id, server_id: req.channel.server_id, muted_channels: channel_id }); 14 | 15 | if (!isMuted) { 16 | return res.status(403).json({ message: "Channel is already unmuted!" }); 17 | } 18 | 19 | await ServerMembers.updateOne( 20 | { member: req.user._id, server_id: req.channel.server_id }, 21 | {$pull: { muted_channels: channel_id } } 22 | ); 23 | 24 | res.json({ message: "Channel unmuted." }); 25 | 26 | const io = req.io; 27 | io.in(req.user.id).emit(CHANNEL_UNMUTED, {channelId: channel_id}); 28 | }; 29 | -------------------------------------------------------------------------------- /src/routes/servers/deleteServer.js: -------------------------------------------------------------------------------- 1 | import deleteServer from "../../utils/deleteServer"; 2 | module.exports = async (req, res, next) => { 3 | // check if its the creator and delete the server. 4 | if (req.server.creator !== req.user._id) { 5 | return res.status(403).json({message: "Only the creator of the servers can delete servers."}); 6 | } 7 | deleteServer(req.io, req.server.server_id, req.server, (err, status) => { 8 | if (err) return res.status(403).json({message: err.message}); 9 | if (!status) return res.status(403).json({message: "Something went wrong. Try again later."}); 10 | res.json({ status: "Done!" }); 11 | }) 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /src/routes/servers/getServer.js: -------------------------------------------------------------------------------- 1 | module.exports = async (req, res, next) => { 2 | res.json({ 3 | name: req.server.name, 4 | avatar: req.server.avatar, 5 | default_channel_id: req.server.default_channel_id, 6 | server_id: req.server.server_id, 7 | created: req.server.created, 8 | banner: req.server.banner, 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/routes/servers/invites/createCustomInvite.js: -------------------------------------------------------------------------------- 1 | import {ServerInvites} from '../../../models/ServerInvites' 2 | 3 | module.exports = async (req, res, next) => { 4 | 5 | if (req.server.creator !== req.user._id) { 6 | return res.status(403).send({ 7 | message: "Only server creators can create custom links." 8 | }) 9 | } 10 | 11 | if (!req.server.verified) { 12 | return res.status(403).send({ 13 | message: "Only verified servers can create custom links." 14 | }) 15 | } 16 | 17 | if (!req.body.customCode) { 18 | await ServerInvites.deleteOne({ server: req.server._id, custom: true }); 19 | res.json({ message: "Deleted." }); 20 | return; 21 | } 22 | 23 | const customCodeTrimmed = req.body.customCode.trim(); 24 | 25 | if (customCodeTrimmed.length >= 20) { 26 | return res.status(403).send({ 27 | message: "Custom invite must be less than 20 characters." 28 | }) 29 | } 30 | 31 | const condition = { server: req.server._id, custom: true}; 32 | 33 | const action = { 34 | $set: { 35 | server: req.server._id, 36 | creator: req.user._id, 37 | custom: true, 38 | invite_code: customCodeTrimmed, 39 | } 40 | } 41 | 42 | await ServerInvites.updateOne(condition, action, {upsert: true}) 43 | 44 | res.json({ message: "Added Custom Link!"}); 45 | }; 46 | -------------------------------------------------------------------------------- /src/routes/servers/invites/createInvite.js: -------------------------------------------------------------------------------- 1 | import {ServerInvites} from '../../../models/ServerInvites' 2 | 3 | module.exports = async (req, res, next) => { 4 | //check if invite limit reached. 5 | const invites = await ServerInvites.find({ 6 | server: req.server._id, 7 | creator: req.user._id 8 | }); 9 | if (invites.length >= 30) { 10 | return res.status(403).json({ 11 | message: "You have reached the maximum limit of invites for this server." 12 | }); 13 | } 14 | 15 | const inviteCode = generateString(6); 16 | 17 | const create = await ServerInvites.create({ 18 | server: req.server._id, 19 | creator: req.user._id, 20 | invite_code: inviteCode 21 | }); 22 | 23 | res.json({ invite_code: inviteCode }); 24 | }; 25 | 26 | 27 | function generateString(n) { 28 | var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; 29 | var string_length = n; 30 | var randomstring = ""; 31 | for (var i = 0; i < string_length; i++) { 32 | var rnum = Math.floor(Math.random() * chars.length); 33 | randomstring += chars.substring(rnum, rnum + 1); 34 | } 35 | return randomstring; 36 | } -------------------------------------------------------------------------------- /src/routes/servers/invites/deleteInvite.js: -------------------------------------------------------------------------------- 1 | import {ServerInvites} from '../../../models/ServerInvites' 2 | 3 | 4 | module.exports = async (req, res, next) => { 5 | const inviteCode = req.params.invite_code; 6 | 7 | // Find invite 8 | const invite = await ServerInvites.findOne({ invite_code: inviteCode }) 9 | .select("creator server") 10 | .populate("server", "creator") 11 | .lean(); 12 | 13 | if (!invite) { 14 | return res.status(404).json({ message: "Invalid invite." }); 15 | } 16 | 17 | const isInviteCreator = invite.creator.toString() === req.user._id; 18 | const isServerCreator = invite.server.creator.toString() === req.user._id; 19 | 20 | if (!isInviteCreator && !isServerCreator) { 21 | return res.status(403).json({ message: "You do not have permission to delete this invite." }); 22 | } 23 | 24 | await ServerInvites.deleteOne({ _id: invite._id }); 25 | 26 | res.json({ message: "Deleted!" }); 27 | }; 28 | -------------------------------------------------------------------------------- /src/routes/servers/invites/getInvites.js: -------------------------------------------------------------------------------- 1 | import {ServerInvites} from '../../../models/ServerInvites' 2 | 3 | 4 | module.exports = async (req, res, next) => { 5 | 6 | const doc = { 7 | server: req.server._id, 8 | } 9 | 10 | if (req.server.creator !== req.user._id) { 11 | doc.creator = req.user._id 12 | } 13 | 14 | const invites = await ServerInvites.find(doc, {_id: 0}).select("creator invite_code uses custom").populate("creator", "username avatar tag id"); 15 | res.json(invites); 16 | }; 17 | -------------------------------------------------------------------------------- /src/routes/servers/invites/index.js: -------------------------------------------------------------------------------- 1 | const MainInviteRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../../middlewares/authenticate"); 5 | const UserPresentVerification = require("../../../middlewares/UserPresentVerification"); 6 | const rateLimit = require("../../../middlewares/rateLimit"); 7 | 8 | const reCaptchaPolicy = require("../../../policies/reCaptchaPolicie"); 9 | const forceCaptcha = require("../../../policies/forceCaptcha"); 10 | 11 | // Invites 12 | MainInviteRouter.route("/:server_id/invites").get( 13 | authenticate(), 14 | UserPresentVerification, 15 | require("./getInvites") 16 | ); 17 | 18 | // Invite details 19 | MainInviteRouter.route("/invite/:invite_code").get(require("./inviteDetails")); 20 | 21 | 22 | // Delete invite 23 | MainInviteRouter.route("/invite/:invite_code").delete( 24 | authenticate(), 25 | require("./deleteInvite") 26 | ); 27 | 28 | 29 | // Create Custom Invite 30 | MainInviteRouter.route("/:server_id/invites/custom").post( 31 | authenticate(), 32 | UserPresentVerification, 33 | require("./createCustomInvite") 34 | ); 35 | 36 | // Create Invite 37 | MainInviteRouter.route("/:server_id/invite").post( 38 | authenticate(), 39 | UserPresentVerification, 40 | require("./createInvite") 41 | ); 42 | 43 | // Join by invite 44 | MainInviteRouter.route("/invite/:invite_code").post( 45 | authenticate(), 46 | rateLimit({name: 'server_join', expire: 60, requestsLimit: 10 }), 47 | // force captcha 48 | forceCaptcha, 49 | reCaptchaPolicy, 50 | require("./joinServer") 51 | ); 52 | // Join by server_id 53 | MainInviteRouter.route("/invite/servers/:server_id").post( 54 | authenticate(), 55 | rateLimit({name: 'server_join', expire: 60, requestsLimit: 10 }), 56 | // force captcha 57 | forceCaptcha, 58 | reCaptchaPolicy, 59 | require("./joinServer") 60 | ); 61 | 62 | 63 | module.exports = MainInviteRouter; 64 | -------------------------------------------------------------------------------- /src/routes/servers/invites/inviteDetails.js: -------------------------------------------------------------------------------- 1 | import {ServerInvites} from '../../../models/ServerInvites' 2 | 3 | 4 | module.exports = async (req, res, next) => { 5 | const inviteCode = req.params.invite_code; 6 | 7 | // Find invite 8 | const invite = await ServerInvites.findOne({ invite_code: inviteCode }) 9 | .populate("server", "+verified") 10 | .lean(); 11 | 12 | if (!invite) { 13 | return res.status(404).json({ message: "Invalid invite." }); 14 | } 15 | 16 | res.json(invite.server); 17 | }; 18 | -------------------------------------------------------------------------------- /src/routes/servers/invites/joinServer.js: -------------------------------------------------------------------------------- 1 | import {ServerInvites} from '../../../models/ServerInvites' 2 | import {PublicServers} from '../../../models/PublicServers'; 3 | 4 | import {Servers} from "../../../models/Servers"; 5 | 6 | 7 | import joinServer from '../../../utils/joinServer'; 8 | 9 | module.exports = async (req, res, next) => { 10 | const { invite_code, server_id } = req.params; 11 | const { socketID } = req.body; 12 | 13 | let invite, server; 14 | 15 | if (invite_code) { 16 | // Find invite 17 | invite = await ServerInvites.findOne({ invite_code }) 18 | .populate({ path: "server", populate: [{ path: "creator" }] }) 19 | .lean(); 20 | // check if banned 21 | if (!invite) { 22 | return res.status(404).json({ message: "Invalid invite code." }); 23 | } 24 | const checkBanned = await Servers.findOne({ 25 | _id: invite.server._id, 26 | "user_bans.user": { 27 | $ne: req.user._id 28 | } 29 | }); 30 | if (!checkBanned) invite = undefined; 31 | } else if (server_id) { 32 | server = await Servers.findOne({ 33 | server_id: server_id, 34 | "user_bans.user": { 35 | $ne: req.user._id 36 | } 37 | }) 38 | .populate("creator") 39 | .lean(); 40 | // check if server is in public list 41 | if (server) { 42 | const checkPublicList = await PublicServers.findOne({ 43 | server: server._id 44 | }); 45 | if (!checkPublicList) { 46 | return res.status(403).json({ message: "Server is not public." }); 47 | } 48 | } 49 | } 50 | 51 | if (!invite && !server) 52 | return res.status(404).json({ message: "Invalid server." }); 53 | joinServer(server || invite, req.user, socketID, req, res) 54 | }; -------------------------------------------------------------------------------- /src/routes/servers/muteServer.js: -------------------------------------------------------------------------------- 1 | import {ServerMembers} from "../../models/ServerMembers"; 2 | import { Notifications } from "../../models/Notifications"; 3 | import {Channels} from "../../models/Channels"; 4 | import { SERVER_MUTED } from "../../ServerEventNames"; 5 | 6 | 7 | 8 | module.exports = async (req, res, next) => { 9 | let {type} = req.body; 10 | 11 | if (!type) type = 0; 12 | 13 | 14 | if (typeof type !== "number") { 15 | res.status(401).json({message: "type must be number."}) 16 | return 17 | } 18 | 19 | if (type > 2 || type < 0) { 20 | res.status(401).json({message: "type must be between 0 and 2"}) 21 | return 22 | } 23 | 24 | await ServerMembers.updateOne( 25 | { member: req.user._id, server_id: req.server.server_id }, 26 | { $set: { muted: type } } 27 | ); 28 | 29 | if (type === 2) { 30 | const serverChannels = (await Channels.find({server_id: req.server.server_id}).select("channelId").lean()).map(c => c.channelId); 31 | 32 | await Notifications.deleteOne({ 33 | channelId: {$in: serverChannels}, 34 | recipient: req.user.id 35 | }); 36 | } 37 | 38 | res.json({ message: "Done" }); 39 | 40 | const io = req.io; 41 | io.in(req.user.id).emit(SERVER_MUTED, {server_id: req.server.server_id, muted: type}); 42 | 43 | 44 | }; 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/routes/servers/roles/applyRoleToMember.js: -------------------------------------------------------------------------------- 1 | 2 | import { ServerRoles } from '../../../models/ServerRoles'; 3 | import { ServerMembers } from '../../../models/ServerMembers'; 4 | import { Users } from "../../../models/Users"; 5 | import { SERVER_ROLE_ADDED_TO_MEMBER } from '../../../ServerEventNames'; 6 | const redis = require("../../../redis"); 7 | 8 | // /:server_id/members/:member_id/roles/:role_id 9 | module.exports = async (req, res, next) => { 10 | const { server_id, member_id, role_id } = req.params; 11 | 12 | const user = await Users.findOne({id: member_id}); 13 | 14 | if (!user) { 15 | return res 16 | .status(404) 17 | .json({ message: "User does not exist." }); 18 | } 19 | 20 | // check if role exists in that server 21 | const role = await ServerRoles.findOne({id: role_id, server_id: server_id}).select("bot order default"); 22 | 23 | if (role.default === true) { 24 | return res 25 | .status(404) 26 | .json({ message: "Role does not exist." }); 27 | } 28 | 29 | if (role.bot) { 30 | return res 31 | .status(403) 32 | .json({ message: "This is a bot role." }); 33 | } 34 | 35 | 36 | if (!role) { 37 | return res 38 | .status(404) 39 | .json({ message: "Role does not exist." }); 40 | } 41 | 42 | // check if member exists in the server. 43 | 44 | const serverMember = await ServerMembers.findOne({server_id: server_id, member: user._id}); 45 | 46 | if (!serverMember) { 47 | return res 48 | .status(404) 49 | .json({ message: "Member does not exist." }); 50 | } 51 | 52 | 53 | 54 | // higher role should have higher priority 55 | const isCreator = req.server.creator === req.user._id 56 | if (!isCreator) { 57 | if (req.highestRolePosition >= role.order) { 58 | return res 59 | .status(403) 60 | .json({ message: "Your Role priority is too low to perfom this action." }); 61 | } 62 | } 63 | 64 | 65 | await ServerMembers.updateOne({_id: serverMember._id}, {$addToSet: { roles: role_id } }); 66 | 67 | redis.remServerMember(member_id, req.server.server_id); 68 | 69 | const io = req.io; 70 | io.in("server:" + req.server.server_id).emit(SERVER_ROLE_ADDED_TO_MEMBER, { 71 | role_id: role_id, 72 | id: member_id, 73 | server_id: server_id, 74 | }); 75 | 76 | res.json({success: true}); 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /src/routes/servers/roles/createRole.js: -------------------------------------------------------------------------------- 1 | const flake = require('../../../utils/genFlakeId').default; 2 | import { ServerRoles } from '../../../models/ServerRoles'; 3 | import { SERVER_ROLE_CREATED } from '../../../ServerEventNames'; 4 | const { matchedData } = require('express-validator'); 5 | const rolePerms = require("../../../utils/rolePermConstants"); 6 | 7 | module.exports = async (req, res, next) => { 8 | 9 | // check if roles limit reached 10 | const rolesCount = await ServerRoles.countDocuments({ server: req.server._id }); 11 | const dataMatched = matchedData(req); 12 | 13 | if (rolesCount >= 30) { 14 | return res.status(403).json({ message: "Role limit reached! (>= 30)" }); 15 | } 16 | 17 | const id = flake.gen(); 18 | const doc = { 19 | name: dataMatched.name || "New Role", 20 | id: id, 21 | permissions: dataMatched.permissions || rolePerms.roles.SEND_MESSAGES, 22 | server: req.server._id, 23 | server_id: req.server.server_id, 24 | order: rolesCount 25 | }; 26 | if (dataMatched.color) { 27 | doc.color = dataMatched.color; 28 | } 29 | const create = await ServerRoles.create(doc); 30 | await ServerRoles.updateOne({server: req.server._id, default: true}, {$inc: {order: 2}}) 31 | 32 | const data = { 33 | name: doc.name, 34 | permissions: doc.permissions, 35 | deletable: true, 36 | id: id, 37 | server_id: doc.server_id, 38 | order: rolesCount 39 | }; 40 | if (doc.color) { 41 | data.color = doc.color; 42 | } 43 | const io = req.io; 44 | io.in("server:" + req.server.server_id).emit(SERVER_ROLE_CREATED, data); 45 | 46 | res.json(data); 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /src/routes/servers/roles/deleteRole.js: -------------------------------------------------------------------------------- 1 | 2 | import {ServerRoles} from '../../../models/ServerRoles'; 3 | import { SERVER_ROLE_DELETED } from '../../../ServerEventNames'; 4 | 5 | 6 | 7 | module.exports = async (req, res, next) => { 8 | const roleID = req.params.role_id; 9 | 10 | 11 | // check if role exists. 12 | const role = await ServerRoles.findOne({id: roleID, server: req.server._id}).select("order default id"); 13 | 14 | // check if default 15 | if (role.default) { 16 | return res 17 | .status(403) 18 | .json({ message: "Default role cannot be deleted." }); 19 | } 20 | 21 | if (!role) { 22 | return res 23 | .status(404) 24 | .json({ message: "Role does not exist in that server." }); 25 | } 26 | 27 | 28 | // higher role should have higher priority 29 | const isCreator = req.server.creator === req.user._id 30 | if (!isCreator) { 31 | if (req.highestRolePosition >= role.order) { 32 | return res 33 | .status(403) 34 | .json({ message: "Your Role priority is too low to perfom this action." }); 35 | } 36 | } 37 | 38 | 39 | 40 | await ServerRoles.deleteOne({_id: role._id}); 41 | 42 | const io = req.io; 43 | io.in("server:" + req.server.server_id).emit(SERVER_ROLE_DELETED, {role_id: role.id, server_id: req.server.server_id}); 44 | 45 | res.json({role_id: role.id, server_id: req.server.server_id}); 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /src/routes/servers/roles/index.js: -------------------------------------------------------------------------------- 1 | const MainRolesRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../../middlewares/authenticate"); 5 | const UserPresentVerification = require("../../../middlewares/UserPresentVerification"); 6 | const checkRolePerms = require('../../../middlewares/checkRolePermissions'); 7 | 8 | const rolePolicies = require('../../../policies/RolesPolicies'); 9 | const {roles: {MANAGE_ROLES}} = require("../../../utils/rolePermConstants"); 10 | 11 | // create role 12 | MainRolesRouter.route("/:server_id/roles").post( 13 | authenticate(true), 14 | UserPresentVerification, 15 | rolePolicies.updateRole, 16 | // redis and UserPresentVerification needs work in order for this to work. 17 | checkRolePerms('Roles', MANAGE_ROLES), 18 | require("./createRole") 19 | ); 20 | 21 | // update role position 22 | MainRolesRouter.route("/:server_id/roles").patch( 23 | authenticate(true), 24 | UserPresentVerification, 25 | checkRolePerms('Roles', MANAGE_ROLES), 26 | require("./updateRolePosition") 27 | ); 28 | 29 | // update role 30 | MainRolesRouter.route("/:server_id/roles/:role_id").patch( 31 | authenticate(true), 32 | UserPresentVerification, 33 | rolePolicies.updateRole, 34 | checkRolePerms('Roles', MANAGE_ROLES), 35 | require("./updateRole") 36 | ); 37 | 38 | // delete role 39 | MainRolesRouter.route("/:server_id/roles/:role_id").delete( 40 | authenticate(true), 41 | UserPresentVerification, 42 | checkRolePerms('Roles', MANAGE_ROLES), 43 | require("./deleteRole") 44 | ); 45 | 46 | 47 | // applyRoleToMember 48 | MainRolesRouter.route("/:server_id/members/:member_id/roles/:role_id").patch( 49 | authenticate(true), 50 | UserPresentVerification, 51 | checkRolePerms('Roles', MANAGE_ROLES), 52 | require("./applyRoleToMember") 53 | ); 54 | // removeRoleFromMember 55 | MainRolesRouter.route("/:server_id/members/:member_id/roles/:role_id").delete( 56 | authenticate(true), 57 | UserPresentVerification, 58 | checkRolePerms('Roles', MANAGE_ROLES), 59 | require("./removeRoleFromMember") 60 | ); 61 | 62 | module.exports = MainRolesRouter; 63 | -------------------------------------------------------------------------------- /src/routes/servers/roles/removeRoleFromMember.js: -------------------------------------------------------------------------------- 1 | 2 | import { ServerRoles } from '../../../models/ServerRoles'; 3 | import { ServerMembers } from '../../../models/ServerMembers'; 4 | import { Users } from "../../../models/Users"; 5 | import { SERVER_ROLE_REMOVED_FROM_MEMBER } from '../../../ServerEventNames'; 6 | const redis = require("../../../redis"); 7 | 8 | 9 | // /:server_id/members/:member_id/roles/:role_id 10 | module.exports = async (req, res, next) => { 11 | const { server_id, member_id, role_id } = req.params; 12 | 13 | const user = await Users.findOne({id: member_id}); 14 | 15 | if (!user) { 16 | return res 17 | .status(404) 18 | .json({ message: "User does not exist." }); 19 | } 20 | 21 | // check if role exists in that server 22 | const role = await ServerRoles.findOne({id: role_id, server_id: server_id}).select("bot order default"); 23 | 24 | if (role.default === true) { 25 | return res 26 | .status(404) 27 | .json({ message: "Role does not exist." }); 28 | } 29 | 30 | 31 | if (!role) { 32 | return res 33 | .status(404) 34 | .json({ message: "Role does not exist." }); 35 | } 36 | 37 | if (role.bot){ 38 | return res 39 | .status(403) 40 | .json({ message: "This is a bot role." }); 41 | } 42 | 43 | // check if member exists in the server. 44 | 45 | const serverMember = await ServerMembers.findOne({server_id: server_id, member: user._id}); 46 | 47 | if (!serverMember) { 48 | return res 49 | .status(404) 50 | .json({ message: "Member does not exist." }); 51 | } 52 | 53 | 54 | // higher role should have higher priority 55 | const isCreator = req.server.creator === req.user._id 56 | if (!isCreator) { 57 | if (req.highestRolePosition >= role.order) { 58 | return res 59 | .status(403) 60 | .json({ message: "Your Role priority is too low to perfom this action." }); 61 | } 62 | } 63 | 64 | 65 | 66 | await ServerMembers.updateOne({_id: serverMember._id}, {$pull: { roles: role_id } }); 67 | redis.remServerMember(member_id, req.server.server_id); 68 | 69 | const io = req.io; 70 | io.in("server:" + req.server.server_id).emit(SERVER_ROLE_REMOVED_FROM_MEMBER, { 71 | role_id: role_id, 72 | id: member_id, 73 | server_id: server_id, 74 | }); 75 | 76 | res.json({success: true}); 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /src/routes/servers/roles/updateRole.js: -------------------------------------------------------------------------------- 1 | import { ServerRoles } from "../../../models/ServerRoles"; 2 | import { SERVER_ROLE_UPDATED } from "../../../ServerEventNames"; 3 | const { matchedData } = require("express-validator"); 4 | const redis = require("../../../redis"); 5 | const rolePermConstants = require('../../../utils/rolePermConstants'); 6 | const { connection } = require('mongoose'); 7 | module.exports = async (req, res, next) => { 8 | const roleID = req.params.role_id; 9 | 10 | let dataMatched = matchedData(req); 11 | 12 | if (!dataMatched.name || !dataMatched.name.trim()) { 13 | delete dataMatched.name; 14 | } 15 | 16 | // check if role exists. 17 | const role = await ServerRoles.findOne({id: roleID, server: req.server._id}).select("order permissions"); 18 | 19 | if (!role) { 20 | return res 21 | .status(403) 22 | .json({ message: "Role does not exist in that server." }); 23 | } 24 | 25 | // higher role should have higher priority 26 | const isCreator = req.server.creator === req.user._id 27 | if (!isCreator) { 28 | if (req.highestRolePosition >= role.order) { 29 | return res 30 | .status(403) 31 | .json({ message: "Your Role priority is too low to perfom this action." }); 32 | } 33 | // only allowed to edit permissions you have. 34 | const requesterPermissions = req.permissions; 35 | const isAdmin = rolePermConstants.containsPerm(requesterPermissions, rolePermConstants.roles.ADMIN); 36 | if (!isAdmin) { 37 | const oldPermissions = role.permissions; 38 | const permissionToModify = dataMatched.permissions; 39 | const permChanged = rolePermConstants.changedPermissions(oldPermissions, permissionToModify); 40 | for (let name in permChanged) { 41 | const perm = permChanged[name]; 42 | const hasPerm = rolePermConstants.containsPerm(requesterPermissions, perm); 43 | if (!hasPerm) { 44 | res 45 | .status(403) 46 | .json({ message: "Cannot modify this permission as you dont have it." }); 47 | return; 48 | } 49 | } 50 | } 51 | } 52 | 53 | await ServerRoles.updateOne({_id: role._id}, dataMatched); 54 | 55 | redis.delAllServerMembers(req.server.server_id); 56 | 57 | 58 | 59 | const data = Object.assign({}, dataMatched, {id: roleID, server_id: req.server.server_id}); 60 | const io = req.io; 61 | io.in("server:" + req.server.server_id).emit(SERVER_ROLE_UPDATED, data); 62 | 63 | res.json(data); 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /src/routes/servers/roles/updateRolePosition.js: -------------------------------------------------------------------------------- 1 | 2 | import {ServerRoles} from '../../../models/ServerRoles' 3 | import { SERVER_ROLES_UPDATED } from '../../../ServerEventNames'; 4 | module.exports = async (req, res, next) => { 5 | 6 | const {roleID, order} = req.body 7 | 8 | 9 | const isCreator = req.server.creator === req.user._id 10 | // less = higher priority 11 | // higher = less priority 12 | if (!isCreator) { 13 | if (order <= req.highestRolePosition) { 14 | return res 15 | .status(403) 16 | .json({ message: "Your Role priority is too low to perfom this action." }); 17 | } 18 | } 19 | 20 | 21 | 22 | const roles = await ServerRoles.find({ server: req.server._id }).select("name id color permissions server_id deletable order default hideRole bot").lean(); 23 | 24 | // order roles 25 | let ordered = roles.sort((a, b) => a.order - b.order); 26 | 27 | if (order > roles.length && order < 0) { 28 | return res.json(ordered); 29 | } 30 | 31 | // find index of role 32 | let index = roles.findIndex(r => r.id == roleID); 33 | 34 | if (index < 0) { 35 | return res.json(ordered); 36 | } 37 | 38 | ordered.splice(order, 0, ordered.splice(index, 1)[0]); 39 | 40 | 41 | const defaultRole = roles.find(r => r.default); 42 | ordered = ordered.filter(o => !o.default) 43 | 44 | let itemsToUpdate = []; 45 | ordered = ordered.map((v, i) => { 46 | itemsToUpdate.push({_id: v._id, order: i}) 47 | return {...v, ...{order: i}} 48 | }) 49 | defaultRole.order = ordered.length; 50 | ordered.push(defaultRole); 51 | 52 | await ServerRoles.bulkWrite( 53 | itemsToUpdate.map(item => ({ 54 | updateOne: { 55 | filter: {_id: item._id}, 56 | update: { $set: item } 57 | } 58 | })) 59 | ) 60 | 61 | const io = req.io; 62 | io.in("server:" + req.server.server_id).emit(SERVER_ROLES_UPDATED, {server_id: req.server.server_id, roles: ordered}); 63 | 64 | res.json(ordered); 65 | }; -------------------------------------------------------------------------------- /src/routes/servers/unBanMember.js: -------------------------------------------------------------------------------- 1 | 2 | import {Servers} from "../../models/Servers"; 3 | import { Users } from "../../models/Users"; 4 | 5 | 6 | 7 | module.exports = async (req, res, next) => { 8 | const {server_id, id} = req.params; 9 | 10 | 11 | const server = req.server; 12 | 13 | 14 | const userToBeUnbanned = await Users.findOne({id: id}).select('username tag avatar id'); 15 | 16 | if (!userToBeUnbanned) { 17 | return res.status(404).json({message: "User doesn't exist."}) 18 | } 19 | 20 | // check if user is banned 21 | const checkBanned = await Servers.findOne({ 22 | _id: server._id, 23 | "user_bans.user": userToBeUnbanned._id 24 | }) 25 | 26 | if (!checkBanned) { 27 | return res.status(404).json({message: "User is not banned."}) 28 | } 29 | 30 | await Servers.updateOne( 31 | {_id: server._id}, 32 | {$pull: {user_bans: {user: userToBeUnbanned._id}}} 33 | ); 34 | 35 | 36 | res.json({ status: "Done!" }); 37 | 38 | 39 | 40 | 41 | 42 | }; 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/routes/settings/changeAppearance.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | 3 | module.exports = async (req, res, next) => { 4 | const setting = req.body; 5 | 6 | const settingName = Object.keys(setting)[0]; 7 | const appearancePath = "settings.apperance." + settingName; 8 | const settingsValue = setting[settingName]; 9 | 10 | Users.findOneAndUpdate( 11 | { _id: req.user._id }, 12 | { [appearancePath]: settingsValue } 13 | ).exec(function(err, item) { 14 | if (err) { 15 | return res.status(403).json({ 16 | message: "Could not be updated." 17 | }); 18 | } 19 | if (!item) { 20 | return res.status(404).json({ 21 | message: "Users not found" 22 | }); 23 | } 24 | res.json({ 25 | changed: { [settingName]: settingsValue } 26 | }); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /src/routes/settings/changeCustomStatus.js: -------------------------------------------------------------------------------- 1 | const redis = require("../../redis"); 2 | import { Users } from "../../models/Users"; 3 | import { SELF_CUSTOM_STATUS_CHANGE } from "../../ServerEventNames"; 4 | const emitAll = require("../../socketController/emitToAll"); 5 | const { changeCustomStatusByUserId } = require("../../newRedisWrapper"); 6 | 7 | module.exports = async (req, res, next) => { 8 | const io = req.io; 9 | const { custom_status } = req.body; 10 | 11 | let customStatus = custom_status && custom_status.trim() !== "" ? custom_status : null; 12 | 13 | if (customStatus) { 14 | if (customStatus.length >= 100) { 15 | return res.status(401).json("String too long.") 16 | } 17 | customStatus = customStatus.replace(/\n/g, " ") 18 | } 19 | 20 | const obj = {}; 21 | if (!customStatus) { 22 | obj.$unset = { 23 | custom_status: 1 24 | } 25 | } else { 26 | obj.$set = { 27 | custom_status: customStatus 28 | } 29 | } 30 | await Users.updateOne({_id: req.user._id}, obj) 31 | 32 | // change the status in redis. 33 | await changeCustomStatusByUserId(req.user.id, customStatus); 34 | 35 | // emit status to users. 36 | emitAll("member:custom_status_change", req.user._id, {user_id: req.user.id, custom_status: customStatus}, io) 37 | 38 | io.in(req.user.id).emit(SELF_CUSTOM_STATUS_CHANGE, { custom_status: customStatus}); 39 | 40 | 41 | res.json({ 42 | custom_status: customStatus 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /src/routes/settings/changeStatus.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { Users } from "../../models/Users"; 4 | const redis = require("../../redis"); 5 | const emitStatus = require("../../socketController/emitUserStatus"); 6 | const { getCustomStatusByUserId, changeStatusByUserId } = require("../../newRedisWrapper"); 7 | 8 | module.exports = async (req, res, next) => { 9 | const io = req.io; 10 | const { status } = req.body; 11 | const beforeStatus = req.user.status; 12 | 13 | 14 | await Users.updateOne({ _id: req.user._id }, 15 | { $set: { "status": status } }) 16 | // change the status in redis. 17 | await changeStatusByUserId(req.user.id, status); 18 | 19 | // emit status to users. 20 | if (beforeStatus === 0) { 21 | const [customStatus] = await getCustomStatusByUserId(req.user.id) 22 | emitStatus(req.user.id, req.user._id, status, io, false, customStatus?.[1], true) 23 | 24 | } else { 25 | emitStatus(req.user.id, req.user._id, status, io); 26 | } 27 | res.json({ 28 | status: true, 29 | set: status 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/routes/settings/deleteCustomEmoji.js: -------------------------------------------------------------------------------- 1 | import {CustomEmojis} from '../../models/CustomEmojis'; 2 | import { CUSTOM_EMOJI_DELETED } from '../../ServerEventNames'; 3 | 4 | module.exports = async (req, res, next) => { 5 | const { id } = req.body; 6 | const userID = req.user._id; 7 | 8 | CustomEmojis.findOneAndRemove({ user: userID, id }).exec(function( 9 | err, 10 | item 11 | ) { 12 | if (err) { 13 | return res.status(403).json({ 14 | status: false, 15 | message: "Emoji couldn't be removed!" 16 | }); 17 | } 18 | if (!item) { 19 | return res.status(404).json({ 20 | success: false, 21 | message: "Emoji was not found." 22 | }); 23 | } 24 | res.json({ 25 | success: true, 26 | message: "Emoji deleted." 27 | }); 28 | const io = req.io; 29 | // send owns status to every connected device 30 | io.in(req.user.id).emit(CUSTOM_EMOJI_DELETED, { 31 | emoji: item 32 | }); 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /src/routes/settings/index.js: -------------------------------------------------------------------------------- 1 | const MainSettingsRouter = require("express").Router(); 2 | const busboy = require("connect-busboy"); 3 | 4 | // Middleware 5 | const { authenticate } = require("../../middlewares/authenticate"); 6 | const GDriveOauthClient = require("../../middlewares/GDriveOauthClient"); 7 | 8 | // Policies 9 | const settingsPolicy = require("../../policies/settingsPolicies"); 10 | const rateLimit = require("../../middlewares/rateLimit"); 11 | 12 | // Change Status 13 | MainSettingsRouter.route("/status").post( 14 | authenticate(true), 15 | rateLimit({name: 'messages_load', expire: 60, requestsLimit: 50 }), 16 | settingsPolicy.status, 17 | require("./changeStatus") 18 | ); 19 | 20 | // Change Custom Status 21 | MainSettingsRouter.route("/custom-status").post( 22 | authenticate(true), 23 | rateLimit({name: 'messages_load', expire: 60, requestsLimit: 50 }), 24 | require("./changeCustomStatus") 25 | ); 26 | 27 | // Change appearance 28 | MainSettingsRouter.route("/apperance").put( 29 | //TODO: fix typo in database and client and server. 30 | authenticate(), 31 | require("./changeAppearance") 32 | ); 33 | 34 | // Emoji 35 | MainSettingsRouter.route("/emoji") 36 | .post(authenticate(), require("./addCustomEmoji")) 37 | .put(authenticate(), require("./renameCustomEmoji")) 38 | .delete(authenticate(), require("./deleteCustomEmoji")); 39 | 40 | // Server Position 41 | MainSettingsRouter.route("/server_position") 42 | .put(authenticate(), require("./serverPosition")) 43 | 44 | // Link Google Drive 45 | MainSettingsRouter.use( 46 | "/drive", 47 | GDriveOauthClient, 48 | require("./linkGoogleDrive") 49 | ); 50 | 51 | 52 | module.exports = MainSettingsRouter; 53 | -------------------------------------------------------------------------------- /src/routes/settings/linkGoogleDrive/auth.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../../models/Users"; 2 | import { GOOGLE_DRIVE_LINKED } from "../../../ServerEventNames"; 3 | 4 | const jwt = require('jsonwebtoken') 5 | 6 | const DriveAPI = require('../../../API/GDrive'); 7 | 8 | module.exports = async (req, res, next) => { 9 | const oauth2Client = req.oauth2Client; 10 | const {code, token} = req.body; 11 | try { 12 | // jwt token 13 | let decryptedToken = await jwt.verify(process.env.JWT_HEADER + token, process.env.JWT_SECRET); 14 | decryptedToken = decryptedToken.split("-")[0]; 15 | 16 | const {tokens} = await oauth2Client.getToken (code); 17 | const refresh_token = tokens.refresh_token; 18 | 19 | 20 | 21 | const addToken = await Users.updateOne ({ id: decryptedToken }, { 22 | $set: { 23 | GDriveRefreshToken: refresh_token 24 | } 25 | }) 26 | oauth2Client.setCredentials({refresh_token}) 27 | 28 | //create a folder. 29 | const {ok, error, result} = await DriveAPI.createFolder( oauth2Client ); 30 | if ( ok ) { 31 | const io = req.io 32 | io.in(decryptedToken).emit(GOOGLE_DRIVE_LINKED); 33 | return res.json ({ success: true }) 34 | } else { 35 | return res.json ({ success: false }) 36 | } 37 | 38 | } catch (e) { 39 | return res.status(403).json ({ success: false }) 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/routes/settings/linkGoogleDrive/index.js: -------------------------------------------------------------------------------- 1 | const MainGoogleDriveLinkRouter = require("express").Router(); 2 | 3 | const { authenticate } = require("../../../middlewares/authenticate"); 4 | 5 | //send consent url to client. 6 | MainGoogleDriveLinkRouter.route("/url").get(authenticate(), require("./url")); 7 | 8 | MainGoogleDriveLinkRouter.route("/auth").post(require("./auth")); 9 | 10 | module.exports = MainGoogleDriveLinkRouter; 11 | -------------------------------------------------------------------------------- /src/routes/settings/linkGoogleDrive/url.js: -------------------------------------------------------------------------------- 1 | module.exports = async (req, res, next) => { 2 | const oauth2Client = req.oauth2Client; 3 | const scopes = ["https://www.googleapis.com/auth/drive.file"]; 4 | const url = oauth2Client.generateAuthUrl({ 5 | access_type: "offline", 6 | scope: scopes, 7 | prompt: "consent", 8 | state: req.headers.authorization 9 | }); 10 | res.json({ url: url }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/routes/settings/serverPosition.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | import { SERVER_POSITION_UPDATED } from "../../ServerEventNames"; 3 | module.exports = async (req, res, next) => { 4 | 5 | const io = req.io; 6 | const { server_position } = req.body; 7 | 8 | // check if there are more than 200 entries 9 | if (server_position.length >= 200) { 10 | return res.status(403).json({ 11 | message: 'Limit reached (max: 200)', 12 | }) 13 | } 14 | 15 | for (let index = 0; index < server_position.length; index++) { 16 | const element = server_position[index]; 17 | if (element.length >= 50 || typeof element !== "string") { 18 | return res.status(403).json({ 19 | message: 'Invalid server_id format.', 20 | }) 21 | } 22 | } 23 | 24 | try { 25 | const update = await Users.updateOne( 26 | { _id: req.user._id }, 27 | {'settings.server_position': server_position}, 28 | {upsert: true}, 29 | ); 30 | res.json({ 31 | server_position 32 | }); 33 | io.in(req.user.id).emit(SERVER_POSITION_UPDATED, {server_position} ); 34 | return; 35 | } catch(e) { 36 | return res.status(403).json({ 37 | message: 'Something went wrong, try again later.', 38 | }); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/routes/tenor/getCategories.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | 3 | import {getTenorCategories} from '../../utils/tenor' 4 | 5 | export default async function getCategories(req: Request, res: Response, next: NextFunction) { 6 | res.json(await getTenorCategories().catch(() => {})) 7 | } -------------------------------------------------------------------------------- /src/routes/tenor/getSearches.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | 3 | import {getTenorSearch} from '../../utils/tenor' 4 | 5 | export default async function getSearches(req: Request, res: Response, next: NextFunction) { 6 | res.json(await getTenorSearch(req.params.value).catch(() => {})) 7 | } -------------------------------------------------------------------------------- /src/routes/tenor/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | const { authenticate } = require("../../middlewares/authenticate"); 3 | 4 | 5 | const TenorRouter = Router(); 6 | 7 | import GetCategories from './getCategories' 8 | import getSearches from './getSearches' 9 | 10 | // Get Categories 11 | TenorRouter.route("/categories").get( 12 | authenticate(), 13 | GetCategories 14 | ); 15 | 16 | // Get search 17 | TenorRouter.route("/search/:value").get( 18 | authenticate(), 19 | getSearches 20 | ); 21 | 22 | 23 | 24 | 25 | 26 | export {TenorRouter}; -------------------------------------------------------------------------------- /src/routes/themes/deleteTheme.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import * as Themes from '../../services/Themes'; 3 | 4 | export const deleteTheme = async (req: Request, res: Response) => { 5 | const { id } = req.params; 6 | 7 | const deleted = await Themes.deleteTheme(id, req.user._id).catch(err => { 8 | res.status(err.statusCode).json({message: err.message}); 9 | }) 10 | if (!deleted) return; 11 | 12 | res.json({message: 'deleted'}); 13 | }; 14 | -------------------------------------------------------------------------------- /src/routes/themes/getTheme.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import * as Themes from '../../services/Themes'; 3 | 4 | export const getTheme = async (req: Request, res: Response) => { 5 | const { id } = req.params; 6 | 7 | const themes = await Themes.getTheme(id); 8 | if (!themes) { 9 | return res.status(404).json({message: "Theme doesn't exist!"}) 10 | } 11 | res.json(themes); 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /src/routes/themes/getThemes.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import {Themes} from '../../models/Themes'; 3 | import { getThemesByCreatorId } from '../../services/Themes'; 4 | 5 | export const getThemes = async (req: Request, res: Response) => { 6 | const themes = await getThemesByCreatorId(req.user._id); 7 | res.json(themes); 8 | }; 9 | -------------------------------------------------------------------------------- /src/routes/themes/index.ts: -------------------------------------------------------------------------------- 1 | import themePolicy from '../../policies/ThemePolicies'; 2 | 3 | 4 | import { getTheme } from './getTheme'; 5 | import { deleteTheme } from './deleteTheme'; 6 | import { getThemes } from './getThemes'; 7 | import { saveTheme } from './saveTheme'; 8 | import { updateTheme } from './updateTheme'; 9 | 10 | // Middleware 11 | import { authenticate } from "../../middlewares/authenticate"; 12 | import { Router } from 'express'; 13 | 14 | 15 | const ThemeRouter = Router(); 16 | 17 | // get theme 18 | ThemeRouter.route("/:id").get( 19 | authenticate(), 20 | getTheme 21 | ); 22 | 23 | // delete theme 24 | ThemeRouter.route("/:id").delete( 25 | authenticate(), 26 | deleteTheme 27 | ); 28 | 29 | // get themes 30 | ThemeRouter.route("/").get( 31 | authenticate(), 32 | getThemes 33 | ); 34 | 35 | // save theme 36 | ThemeRouter.route("/").post( 37 | authenticate(), 38 | themePolicy.save, 39 | saveTheme 40 | ); 41 | 42 | // update theme 43 | ThemeRouter.route("/:id").patch( 44 | authenticate(), 45 | themePolicy.save, 46 | updateTheme 47 | ); 48 | 49 | 50 | 51 | 52 | export {ThemeRouter}; 53 | -------------------------------------------------------------------------------- /src/routes/themes/saveTheme.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { createTheme } from '../../services/Themes'; 3 | 4 | 5 | export const saveTheme = async (req: Request, res: Response) => { 6 | const { name, css, client_version } = req.body; 7 | 8 | const theme = await createTheme({ 9 | creator: req.user._id, 10 | name, 11 | css, 12 | client_version, 13 | }) 14 | .catch(err => { 15 | res.status(err.statusCode).json({message: err.message}); 16 | }) 17 | 18 | if (!theme) return; 19 | 20 | res.json(theme); 21 | }; 22 | -------------------------------------------------------------------------------- /src/routes/themes/updateTheme.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import * as Themes from '../../services/Themes'; 3 | 4 | 5 | export const updateTheme = async (req: Request, res: Response) => { 6 | const { name, css, client_version } = req.body; 7 | const { id } = req.params; 8 | 9 | const theme = await Themes.updateTheme(id, req.user._id, { 10 | name, css, client_version 11 | }) .catch(err => { 12 | res.status(err.statusCode).json({message: err.message}); 13 | }) 14 | 15 | if (!theme) return; 16 | 17 | res.json(theme); 18 | }; 19 | -------------------------------------------------------------------------------- /src/routes/users/agreeingPolicies.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | 3 | module.exports = async (req, res, next) => { 4 | req.session.destroy(); 5 | await Users.updateOne({id: req.user.id}, {$set: {readTerms: true}}); 6 | res.json({success: true}) 7 | 8 | 9 | } -------------------------------------------------------------------------------- /src/routes/users/confirmEmail.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | import {BannedIPs} from "../../models/BannedIPs"; 3 | const JWT = require("jsonwebtoken"); 4 | 5 | function signToken(user_id, pwdVer) { 6 | if (pwdVer !== undefined) { 7 | return JWT.sign(`${user_id}-${pwdVer}`, process.env.JWT_SECRET); 8 | } else { 9 | return JWT.sign(user_id, process.env.JWT_SECRET); 10 | } 11 | } 12 | 13 | 14 | module.exports = async (req, res, next) => { 15 | const { code, email } = req.body; 16 | 17 | // check if ip is banned 18 | const ipBanned = await BannedIPs.exists({ ip: req.userIP }); 19 | if (ipBanned) { 20 | return res.status(401).json({ 21 | error: "IP is banned." 22 | }); 23 | } 24 | 25 | // Check if there is a user with the same email 26 | const foundUser = await Users.findOne({ email: email.toLowerCase() }).select( 27 | "id email_confirm_code passwordVersion" 28 | ); 29 | if (!foundUser) { 30 | return res.status(404).json({ 31 | error:"Invalid email." 32 | }); 33 | } 34 | if (!foundUser.email_confirm_code) { 35 | return res.status(401).json({ 36 | error: "Email already confirmed." 37 | }); 38 | } 39 | 40 | if (code !== foundUser.email_confirm_code) { 41 | return res.status(401).json({ 42 | error:"Invalid code." 43 | }); 44 | } 45 | 46 | await Users.updateOne({_id: foundUser._id}, {$unset: {email_confirm_code: 1}}) 47 | 48 | // Generate the token without header information 49 | const token = signToken(foundUser.id, foundUser.passwordVersion) 50 | .split(".") 51 | .splice(1) 52 | .join("."); 53 | 54 | // Respond with user 55 | res.send({ 56 | token 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /src/routes/users/deleteAccount.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | const nertiviaCDN = require("../../utils/uploadCDN/nertiviaCDN"); 3 | 4 | const redis = require("../../redis"); 5 | const { deleteAllUserFCM } = require("../../utils/sendPushNotification"); 6 | const { kickUser } = require("../../utils/kickUser"); 7 | 8 | 9 | 10 | module.exports = async (req, res, next) => { 11 | const { password } = req.body; 12 | 13 | 14 | // validate password 15 | const user = await Users.findById(req.user._id).select("password"); 16 | if (!user){ 17 | return res 18 | .status(404) 19 | .json({error: "User could not be found."}) 20 | } 21 | 22 | const passwordMatch = await user.isValidPassword(password); 23 | if (!passwordMatch) { 24 | return res 25 | .status(403) 26 | .json({error: "Password does not match."}) 27 | } 28 | 29 | 30 | // CHECK: 31 | // Users MUST 32 | // leave/delete all servers 33 | // delete all bots 34 | 35 | const botExists = await Users.exists({createdBy: req.user._id}); 36 | if (botExists) { 37 | return res 38 | .status(403) 39 | .json({error: "You must delete all of your bots before deleting your account."}) 40 | } 41 | 42 | const _user = await Users.findById(req.user._id).select("servers"); 43 | if (_user && _user.servers && _user.servers.length) { 44 | return res 45 | .status(403) 46 | .json({error: "You must leave / Delete all of your servers before deleting your account."}) 47 | } 48 | 49 | await deleteAllUserFCM(req.user.id); 50 | 51 | 52 | 53 | await Users.updateOne({_id: req.user._id}, { 54 | $set: { 55 | username: "Deleted User " + (Math.floor(Math.random() * 100000) + 1), 56 | created: 0 57 | }, 58 | $inc: {passwordVersion: 1}, 59 | $unset: { 60 | ip: 1, 61 | about_me: 1, 62 | show_welcome: 1, 63 | GDriveRefreshToken: 1, 64 | banned: 1, 65 | custom_status: 1, 66 | email: 1, 67 | settings: 1, 68 | avatar: 1, 69 | password: 1, 70 | readTerms: 1, 71 | servers: 1, 72 | lastSeen: 1, 73 | status: 1, 74 | type: 1, 75 | badges: 1, 76 | } 77 | }) 78 | 79 | 80 | // delete files from cdn 81 | nertiviaCDN.deletePath("/" + req.user.id).catch(err => {console.log("Error deleting from CDN", err)}); 82 | 83 | kickUser(req.user.id, "Token outdated."); 84 | req.session.destroy(); 85 | 86 | res 87 | .status(200) 88 | .json({status: "Account Deleted!"}) 89 | 90 | 91 | 92 | }; -------------------------------------------------------------------------------- /src/routes/users/htmlProfile/addHtmlProfile.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express-serve-static-core"; 2 | 3 | import {checkHTML} from 'html-safe-checker' 4 | import {Users} from "../../../models/Users"; 5 | import { zip } from "../../../utils/zip"; 6 | 7 | export const addHtmlProfile = async (req: Request, res: Response, next: NextFunction) => { 8 | const {html} = req.body; 9 | 10 | if (!html) { 11 | return res.status(403).json({error: "html key is missing from the body."}) 12 | } 13 | if (typeof html !== "string") { 14 | return res.status(403).json({error: "html value must be type string."}) 15 | } 16 | if (html.length > 5000) { 17 | return res.status(403).json({error: "html value length must be less than 5000 characters long."}) 18 | } 19 | let jsonHtml: any; 20 | try { 21 | jsonHtml = checkHTML(html); 22 | } catch(err: any) { 23 | return res.status(403).json({error: err.message}) 24 | } 25 | if (!jsonHtml) return; 26 | 27 | 28 | const zippedJson = zip(JSON.stringify(jsonHtml)); 29 | await Users.updateOne({_id: req.user._id}, {$set: {htmlProfile: zippedJson}}) 30 | res.status(201).json({message: "created!"}) 31 | }; 32 | -------------------------------------------------------------------------------- /src/routes/users/htmlProfile/deleteHtmlProfile.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express-serve-static-core"; 2 | 3 | import {Users} from "../../../models/Users"; 4 | 5 | export const deleteHtmlProfile = async (req: Request, res: Response, next: NextFunction) => { 6 | await Users.updateOne({_id: req.user._id}, {$unset: {htmlProfile: 1}}) 7 | res.status(200).json({status: "done"}) 8 | }; 9 | -------------------------------------------------------------------------------- /src/routes/users/htmlProfile/getHtmlProfile.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express-serve-static-core"; 2 | 3 | import {Users} from "../../../models/Users"; 4 | 5 | export const getHtmlProfile = async (req: Request, res: Response, next: NextFunction) => { 6 | const user = await Users.findOne({_id: req.user._id}).select("htmlProfile") 7 | if (!user) return res.status(404).json("User not found") 8 | if (!user.htmlProfile) return res.status(404).json("Html profile does not exist.") 9 | res.status(200).json(user.htmlProfile) 10 | }; 11 | -------------------------------------------------------------------------------- /src/routes/users/htmlProfile/index.ts: -------------------------------------------------------------------------------- 1 | const htmlProfileRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../../middlewares/authenticate"); 5 | 6 | import {addHtmlProfile} from './addHtmlProfile'; 7 | import {getHtmlProfile} from './getHtmlProfile'; 8 | import {deleteHtmlProfile} from './deleteHtmlProfile'; 9 | 10 | htmlProfileRouter.route('/') 11 | .post(authenticate(true), addHtmlProfile); 12 | 13 | htmlProfileRouter.route('/') 14 | .get(authenticate(), getHtmlProfile); 15 | 16 | htmlProfileRouter.route('/') 17 | .delete(authenticate(), deleteHtmlProfile); 18 | 19 | 20 | 21 | export {htmlProfileRouter}; 22 | -------------------------------------------------------------------------------- /src/routes/users/logout.js: -------------------------------------------------------------------------------- 1 | module.exports = async (req, res, next) => { 2 | req.session.destroy(); 3 | 4 | res.status(204).end(); 5 | }; 6 | -------------------------------------------------------------------------------- /src/routes/users/relationship/acceptFriend.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../../models/Users"; 2 | import {Friends} from '../../../models/Friends'; 3 | import { RELATIONSHIP_ACCEPTED } from "../../../ServerEventNames"; 4 | 5 | module.exports = async (req, res, next) => { 6 | const recipientUserID = req.body.id; 7 | 8 | // check if the recipient exists 9 | const recipient = await Users.findOne({id: recipientUserID}); 10 | if (!recipient) return res.status(403) 11 | .json({ status: false, errors: [{param: "all", msg: "Users not found."}] }); 12 | // get accepter and check if the user exists. 13 | const accepter = await Users.findOne({id: req.user.id}) 14 | if (!accepter) return res.status(403) 15 | .json({ status: false, errors: [{param: "all", msg: "Something went wrong."}] }); 16 | 17 | const request = await Friends.findOne({ requester: accepter, recipient: recipient }); 18 | if (!request) return res.status(403) 19 | .json({ status: false, errors: [{param: "all", msg: "Request doesn't exist."}] }); 20 | // if the requester is accepting the invite 21 | 22 | if (request.status == 0) return res.status(403) 23 | .json({ status: false, errors: [{param: "all", msg: "Your request it still pending."}] }); 24 | // if they are already friends 25 | else if (request.status == 2) return res.status(403) 26 | .json({ status: false, errors: [{param: "all", msg: "You are already friends!"}] }); 27 | 28 | // change status to 2 (friends) 29 | 30 | const docAccepter = await Friends.findOneAndUpdate( 31 | { requester: accepter, recipient: recipient }, 32 | { $set: { status: 2 }} 33 | ).lean() 34 | docAccepter.recipient = recipient 35 | 36 | const docRecipient = await Friends.findOneAndUpdate( 37 | { requester: recipient, recipient: accepter }, 38 | { $set: { status: 2 }} 39 | ).lean() 40 | docRecipient.recipient = accepter 41 | 42 | const io = req.io 43 | io.in(accepter.id).emit(RELATIONSHIP_ACCEPTED, recipient.id); 44 | 45 | io.in(recipient.id).emit(RELATIONSHIP_ACCEPTED, accepter.id); 46 | 47 | return res.json({ status: true, message: `Request accepted` }) 48 | 49 | 50 | } -------------------------------------------------------------------------------- /src/routes/users/relationship/index.js: -------------------------------------------------------------------------------- 1 | const MainRelationshipRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../../middlewares/authenticate"); 5 | 6 | // Policies 7 | const relationshipPolicy = require("../../../policies/relationshipPolicies"); 8 | 9 | 10 | // Add 11 | MainRelationshipRouter.route('/') 12 | .post(authenticate(), relationshipPolicy.post, require('./addFriend')); 13 | 14 | // Accept 15 | MainRelationshipRouter.route('/') 16 | .put(authenticate(), relationshipPolicy.put, require('./acceptFriend')); 17 | 18 | 19 | // Remove 20 | MainRelationshipRouter.route('/') 21 | .delete(authenticate(), relationshipPolicy.delete, require('./removeFriend')); 22 | 23 | 24 | module.exports = MainRelationshipRouter; 25 | -------------------------------------------------------------------------------- /src/routes/users/relationship/removeFriend.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../../models/Users"; 2 | import {Friends} from '../../../models/Friends'; 3 | import { RELATIONSHIP_DELETED } from "../../../ServerEventNames"; 4 | 5 | module.exports = async (req, res, next) => { 6 | const recipientUserID = req.body.id; 7 | 8 | // check if the recipient exists 9 | const recipient = await Users.findOne({id: recipientUserID}); 10 | if (!recipient) return res.status(403) 11 | .json({ status: false, errors: [{param: "all", msg: "Users not found."}] }); 12 | 13 | // check if the decliner exists 14 | const decliner = await Users.findOne({id: req.user.id}) 15 | if (!decliner) return res.status(403) 16 | .json({ status: false, errors: [{param: "all", msg: "Something went wrong."}] }); 17 | 18 | // check if the request exists 19 | const request = await Friends.findOne({ requester: decliner, recipient: recipient }); 20 | if (!request) return res.status(403) 21 | .json({ status: false, errors: [{param: "all", msg: "Request doesnt exist."}] }); 22 | 23 | // remove from database 24 | const docA = await Friends.findOneAndRemove({ requester: decliner, recipient: recipient }); 25 | const docB = await Friends.findOneAndRemove({ requester: recipient, recipient: decliner }); 26 | 27 | const updateUserA = await Users.findOneAndUpdate({ _id: decliner },{ $pull: { friends: docA._id }}); 28 | const updateUserB = await Users.findOneAndUpdate({ _id: recipient },{ $pull: { friends: docB._id }}); 29 | 30 | const io = req.io 31 | io.in(decliner.id).emit(RELATIONSHIP_DELETED, recipient.id); 32 | 33 | io.in(recipient.id).emit(RELATIONSHIP_DELETED, decliner.id); 34 | 35 | return res.json({ status: true, message: `Request deleted` }) 36 | } -------------------------------------------------------------------------------- /src/routes/users/survey/index.js: -------------------------------------------------------------------------------- 1 | const MainSurveyRouter = require("express").Router(); 2 | 3 | // Middleware 4 | const { authenticate } = require("../../../middlewares/authenticate"); 5 | 6 | // Policies 7 | const surveyPolicy = require("../../../policies/surveyPolicies"); 8 | 9 | 10 | // Details 11 | MainSurveyRouter.route('/') 12 | .get(authenticate(true), require('./surveyDetails')); 13 | 14 | // Update 15 | MainSurveyRouter.route('/') 16 | .put(authenticate(true), surveyPolicy.put, require('./surveyUpdate')); 17 | 18 | 19 | 20 | 21 | 22 | module.exports = MainSurveyRouter; 23 | -------------------------------------------------------------------------------- /src/routes/users/survey/surveyDetails.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../../models/Users"; 2 | 3 | module.exports = async (req, res, next) => { 4 | const result = await Users.findById(req.user._id, "about_me").lean(); 5 | 6 | if (!result.about_me) { 7 | return res.status(403).json({ 8 | message: "about_me does not exist." 9 | }); 10 | } 11 | 12 | delete result._id; 13 | delete result.about_me._id; 14 | res.json({ 15 | result: result.about_me 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /src/routes/users/survey/surveyUpdate.js: -------------------------------------------------------------------------------- 1 | 2 | import { Users } from "../../../models/Users"; 3 | const { matchedData } = require('express-validator'); 4 | 5 | module.exports = async (req, res, next) => { 6 | const data = matchedData(req); 7 | Users.findOneAndUpdate({ _id: req.user._id }, { about_me: data }).exec( 8 | async function(err, item) { 9 | if (err) { 10 | return res.status(403).json({ 11 | message: "Could not be updated." 12 | }); 13 | } 14 | if (!item) { 15 | return res.status(404).json({ 16 | message: "Users not found" 17 | }); 18 | } 19 | res.json({ 20 | message: "Saved!" 21 | }); 22 | 23 | } 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/routes/users/unblockUser.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | import { BlockedUsers } from '../../models/BlockedUsers'; 3 | import {Channels} from '../../models/Channels'; 4 | import { USER_UNBLOCKED } from "../../ServerEventNames"; 5 | const { deleteDmChannel } = require('../../newRedisWrapper'); 6 | 7 | module.exports = async (req, res, next) => { 8 | const recipientUserID = req.body.id; 9 | 10 | // check if the recipient exists 11 | const recipient = await Users.findOne({id: recipientUserID}); 12 | if (!recipient) return res.status(403) 13 | .json({ status: false, errors: [{param: "all", msg: "Users not found."}] }); 14 | 15 | // check if the blocker exists 16 | const requester = await Users.findOne({id: req.user.id}) 17 | if (!requester) return res.status(403) 18 | .json({ status: false, errors: [{param: "all", msg: "Something went wrong."}] }); 19 | 20 | 21 | // check if is bit blocked 22 | const isBlocked = await BlockedUsers.exists({ 23 | requester: requester, 24 | recipient: recipient 25 | }) 26 | 27 | if (!isBlocked) { 28 | return res.status(403) 29 | .json({ message:"Users is not blocked." }); 30 | } 31 | 32 | await BlockedUsers.deleteOne({ 33 | requester: requester, 34 | recipient: recipient 35 | }) 36 | 37 | // check if channel is opened 38 | const openedChannel = await Channels.findOne({$or: [ 39 | {creator: requester._id, recipients: recipient._id}, 40 | {creator: recipient._id, recipients: requester._id} 41 | ]}).select("channelId") 42 | 43 | if (openedChannel) { 44 | await deleteDmChannel(requester.id, openedChannel.channelId) 45 | await deleteDmChannel(recipient.id, openedChannel.channelId) 46 | } 47 | 48 | const io = req.io 49 | 50 | io.in(requester.id).emit(USER_UNBLOCKED, recipient.id); 51 | 52 | return res.json({message: "Users unblocked." }) 53 | } -------------------------------------------------------------------------------- /src/routes/users/userDetails.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | import {BlockedUsers} from "../../models/BlockedUsers"; 3 | 4 | import {Servers} from "../../models/Servers"; 5 | import {Friends} from "../../models/Friends"; 6 | 7 | module.exports = async (req, res, next) => { 8 | let userID = req.params.user_id; 9 | 10 | if (!userID) { 11 | userID = req.user.id; 12 | } 13 | 14 | const user = await Users.findOne({ 15 | id: userID 16 | }) 17 | .select("-status -__v -friends +about_me +badges +servers +createdBy +htmlProfile") 18 | .populate('createdBy', 'username tag id -_id') 19 | .lean(); 20 | 21 | if (!user) { 22 | return res.status(404).json({ 23 | message: "User was not found." 24 | }); 25 | } 26 | 27 | 28 | //check if user is blocked. 29 | const isBlocked = await BlockedUsers.exists({ 30 | requester: req.user._id, // blocked by 31 | recipient: user._id // blocked user 32 | }) 33 | 34 | if (userID === req.user.id) { 35 | res.json({ 36 | user: {...user, servers: undefined}, 37 | isBlocked 38 | }); 39 | return; 40 | } 41 | 42 | // get common servers 43 | const requesterServersIDs = ((await Users.findOne({_id: req.user._id}).select("servers").lean()).servers || []).map(s => s.toString()); 44 | const userServerIDs = (user.servers || []).map(s => s.toString()); 45 | 46 | const commonServers_ID = requesterServersIDs.filter(s => { 47 | return userServerIDs.includes(s) 48 | }) 49 | const commonServersID = (await Servers.find({_id: {$in: commonServers_ID}}).select("server_id")).map(s => s.server_id); 50 | 51 | // get common friends 52 | const requesterFriendsArr = (await Friends.find({requester: req.user._id, status: 2})).map(f => f.recipient.toString()); 53 | const userFriendsArr = (await Friends.find({requester: user._id, status: 2})).map(f => f.recipient.toString()); 54 | const commonFriend_idArr = requesterFriendsArr.filter(r => userFriendsArr.includes(r)); 55 | const commonFriendID = (await Users.find({_id: {$in: commonFriend_idArr}}).select("id")).map(u => u.id); 56 | 57 | 58 | 59 | res.json({ 60 | user: {...user, servers: undefined}, 61 | commonServersArr: commonServersID, 62 | commonFriendsArr: commonFriendID, 63 | isBlocked 64 | }); 65 | }; 66 | -------------------------------------------------------------------------------- /src/routes/users/welcomeDone.js: -------------------------------------------------------------------------------- 1 | import { Users } from "../../models/Users"; 2 | 3 | module.exports = async (req, res, next) => { 4 | Users.findOneAndUpdate({ _id: req.user._id }, { $unset: {show_welcome: 1} }).exec( 5 | function(err, item) { 6 | if (err) { 7 | return res.status(403).json({ 8 | message: "Could not be updated." 9 | }); 10 | } 11 | if (!item) { 12 | return res.status(404).json({ 13 | message: "Users not found" 14 | }); 15 | } 16 | 17 | res.json({ 18 | message: "Saved!" 19 | }); 20 | } 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/routes/voice/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | const { authenticate } = require("../../middlewares/authenticate"); 3 | const ChannelVerification = require( "../../middlewares/ChannelVerification"); 4 | 5 | import rateLimit from "../../middlewares/rateLimit"; 6 | 7 | 8 | import {joinCall} from './join' 9 | import {leaveCall} from './leave' 10 | 11 | const VoiceRouter = Router(); 12 | 13 | 14 | // Join Call 15 | VoiceRouter.route("/channels/:channelId").post( 16 | authenticate(true), 17 | rateLimit({name: 'join_voice', expire: 20, requestsLimit: 15 }), 18 | ChannelVerification, 19 | // checkRolePermissions('Send Message', permissions.roles.SEND_MESSAGES), 20 | joinCall 21 | ); 22 | 23 | // leave Call 24 | VoiceRouter.route("/leave").post( 25 | authenticate(true), 26 | rateLimit({name: 'leave_voice', expire: 20, requestsLimit: 15 }), 27 | leaveCall 28 | ); 29 | 30 | 31 | 32 | 33 | export {VoiceRouter}; -------------------------------------------------------------------------------- /src/routes/voice/join.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express-serve-static-core"; 2 | import { addUserToVoice, getConnectedUserBySocketID, getUserInVoiceByUserId } from "../../newRedisWrapper"; 3 | import { USER_CALL_JOINED } from "../../ServerEventNames"; 4 | import { getIOInstance } from "../../socket/instance"; 5 | 6 | export async function joinCall (req: Request, res: Response, next: NextFunction) { 7 | const socketId = req.body.socketId; 8 | // check if socketId matches user id 9 | const [details, err] = await getConnectedUserBySocketID(socketId); 10 | if (!details?.id || details?.id !== req.user.id) { 11 | return res.status(403).send("Invalid Id!") 12 | } 13 | 14 | const [isAlreadyInCall, err1] = await getUserInVoiceByUserId(req.user.id); 15 | if (isAlreadyInCall) { 16 | return res.status(403).send("Already in a call!") 17 | } 18 | 19 | const data: any = {socketId}; 20 | if (req.channel.server) { 21 | data.serverId = req.channel.server.server_id 22 | } 23 | await addUserToVoice(req.channel.channelId, req.user.id, data) 24 | 25 | if (data.serverId) { 26 | getIOInstance().in("server:" + data.serverId).emit(USER_CALL_JOINED, {channelId: req.channel.channelId, userId: req.user.id}) 27 | } 28 | res.json({success: true}) 29 | 30 | 31 | } -------------------------------------------------------------------------------- /src/routes/voice/leave.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express-serve-static-core"; 2 | import {getUserInVoiceByUserId, removeUserFromVoice } from "../../newRedisWrapper"; 3 | import { USER_CALL_LEFT } from "../../ServerEventNames"; 4 | import { getIOInstance } from "../../socket/instance"; 5 | 6 | export async function leaveCall (req: Request, res: Response, next: NextFunction) { 7 | 8 | const [voiceDetails, err1] = await getUserInVoiceByUserId(req.user.id); 9 | if (!voiceDetails) { 10 | return res.status(403).send("You're not in a call!") 11 | } 12 | 13 | await removeUserFromVoice(req.user.id) 14 | 15 | if (voiceDetails.serverId) { 16 | getIOInstance().in("server:" + voiceDetails.serverId).emit(USER_CALL_LEFT, {channelId: voiceDetails.channelId, userId: req.user.id}) 17 | } 18 | res.json({success: true}) 19 | 20 | 21 | } -------------------------------------------------------------------------------- /src/services/Themes.ts: -------------------------------------------------------------------------------- 1 | import { PublicThemes } from "../models/PublicThemes"; 2 | import { Themes } from "../models/Themes"; 3 | import flake from "../utils/genFlakeId"; 4 | 5 | interface Theme { 6 | id?: string; 7 | name: string; 8 | css: string; 9 | client_version: string; 10 | creator: string; 11 | } 12 | 13 | export async function createTheme(data: Theme) { 14 | // check how many themes the user has made. 15 | const count = await Themes.countDocuments({creator: data.creator}); 16 | if (count >= 30) { 17 | throw {statusCode: 403, message: 'Too many themes! (Max: 30)'}; 18 | } 19 | const id = flake.gen(); 20 | const theme = await Themes.create({ 21 | name: data.name, 22 | css: data.css, 23 | id, 24 | client_version: data.client_version, 25 | creator: data.creator, 26 | }); 27 | return theme; 28 | } 29 | 30 | export async function updateTheme(id: string, creatorObjectId: string, data: Partial) { 31 | const theme = await Themes.findOne({ id, creator: creatorObjectId }, { _id: 0 }).select("name id"); 32 | if (!theme) { 33 | throw {statusCode: 404, message: "Theme does not exist!"}; 34 | } 35 | await Themes.updateOne( 36 | { id }, 37 | { 38 | name: data.name, 39 | css: data.css, 40 | client_version: data.client_version 41 | }, 42 | { upsert: true } 43 | ); 44 | return data; 45 | } 46 | 47 | export async function getTheme(id: string) { 48 | const theme = await Themes.findOne({id: id}).select('name id css client_version'); 49 | if (!theme) return null; 50 | return theme; 51 | } 52 | export async function getThemesByCreatorId(id: string) { 53 | const themes = await Themes.find({creator: id}, {_id: 0}).select('name id client_version'); 54 | return themes; 55 | } 56 | 57 | export async function deleteTheme(id: string, creatorObjectId: string) { 58 | const theme = await Themes.findOne({ id, creator: creatorObjectId }).select("name id"); 59 | 60 | if (!theme) { 61 | throw {statusCode: 404, message: "Theme does not exist!"}; 62 | } 63 | 64 | await Themes.deleteOne({id}); 65 | await PublicThemes.deleteOne({theme: theme._id}); 66 | return true; 67 | } -------------------------------------------------------------------------------- /src/socket/instance.ts: -------------------------------------------------------------------------------- 1 | import {Server} from "http"; 2 | import socketIO from "socket.io"; 3 | 4 | import { createAdapter, RedisAdapter } from '@socket.io/redis-adapter'; 5 | import { getRedisInstance } from "../redis/instance"; 6 | 7 | let IO_INSTANCE: socketIO.Server | undefined = undefined; 8 | 9 | export function getIOInstance(server?: Server) { 10 | if (IO_INSTANCE) { 11 | return IO_INSTANCE; 12 | } else { 13 | IO_INSTANCE = new socketIO.Server(server, { 14 | transports: ["websocket"], 15 | cors: { 16 | allowedHeaders: "Content-Type, Authorization", 17 | origin: JSON.parse(process.env.ALLOWED_ORIGINS), 18 | credentials: true, 19 | } 20 | }) 21 | IO_INSTANCE.adapter(createAdapter(getRedisInstance(), getRedisInstance()?.duplicate())) 22 | 23 | return IO_INSTANCE; 24 | } 25 | } 26 | 27 | export function getIOAdapter() { 28 | return getIOInstance().of('/').adapter as RedisAdapter; 29 | } 30 | 31 | export function ioInstanceExists() { 32 | return IO_INSTANCE !== undefined; 33 | } 34 | -------------------------------------------------------------------------------- /src/socketController/emitToAll.js: -------------------------------------------------------------------------------- 1 | import {Friends} from '../models/Friends'; 2 | 3 | import { Users } from '../models/Users'; 4 | const SocketIO = require('socket.io'); 5 | 6 | 7 | 8 | /** 9 | * 10 | * @param {SocketIO.Server} io 11 | */ 12 | module.exports = async (name, _id, data, io, emitToSelf = true) => { 13 | 14 | const friends = await Friends.find({requester: _id}).populate('recipient'); 15 | const user = await Users.findById(_id).populate('servers'); 16 | 17 | 18 | let roomIDArr = []; 19 | 20 | 21 | 22 | 23 | 24 | for (let index = 0; index < user.servers.length; index++) { 25 | const server = user.servers[index]; 26 | roomIDArr.push("server:" + server.server_id); 27 | } 28 | 29 | for (let index = 0; index < friends.length; index++) { 30 | const friend = friends[index]; 31 | if (!friend.recipient?.length) continue; 32 | roomIDArr.push(friend.recipient.id); 33 | } 34 | 35 | 36 | if (emitToSelf) { 37 | roomIDArr.push(user.id); 38 | } else { 39 | roomIDArr.filter(r => r !== user.id) 40 | } 41 | io.to(roomIDArr).emit(name, data) 42 | 43 | 44 | 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/socketController/emitUserStatus.js: -------------------------------------------------------------------------------- 1 | const { USER_STATUS_CHANGED, SELF_STATUS_CHANGE } = require("../ServerEventNames"); 2 | const emitToAll = require("./emitToAll"); 3 | 4 | module.exports = function emitStatus(user_id, id, status, io, emitOffline = true, customStatus, connected = false) { 5 | // dont emit if the status is offline (0) 6 | if (emitOffline || (!emitOffline && status !== 0)) { 7 | let payload = { user_id, status} 8 | if (connected) { 9 | payload.custom_status = customStatus 10 | payload.connected = true 11 | } 12 | emitToAll(USER_STATUS_CHANGED, id, payload, io); 13 | } 14 | 15 | // send owns status to every connected device 16 | io.in(user_id).emit(SELF_STATUS_CHANGE, { status }); 17 | } 18 | -------------------------------------------------------------------------------- /src/socketEvents/index.js: -------------------------------------------------------------------------------- 1 | const notificationDismiss = require('../socketEvents/notificationDismiss'); 2 | 3 | module.exports = { 4 | notificationDismiss 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/socketEvents/notificationDismiss.js: -------------------------------------------------------------------------------- 1 | import { Channels } from "../models/Channels"; 2 | 3 | import {ServerMembers} from "../models/ServerMembers"; 4 | const { getConnectedUserBySocketID } = require("../newRedisWrapper"); 5 | import { Notifications } from "../models/Notifications"; 6 | import { NOTIFICATION_DISMISSED } from "../ServerEventNames"; 7 | const redis = require('../redis'); 8 | module.exports = async (data, client, io) => { 9 | const {channelId} = data; 10 | if (!channelId) return; 11 | 12 | 13 | const [user, error] = await getConnectedUserBySocketID(client.id); 14 | 15 | if (error || !user) return; 16 | 17 | // server channel 18 | const serverChannel = await Channels.findOne({channelId, server: {$exists: true, $ne: null}}).select("server"); 19 | if (serverChannel) { 20 | await ServerMembers.updateOne({server: serverChannel.server, member: user._id}, { 21 | $set: { 22 | [`last_seen_channels.${channelId}`] : Date.now() 23 | } 24 | }) 25 | 26 | } 27 | await Notifications.deleteOne({recipient: user.id, channelId}); 28 | 29 | io.to(user.id).emit(NOTIFICATION_DISMISSED, {channelId, serverNotification: !!serverChannel}); 30 | } -------------------------------------------------------------------------------- /src/utils/compressImage.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | import gm from 'gm'; 5 | const gmInstance = gm.subClass({ imageMagick: true }); 6 | 7 | interface Options { 8 | 9 | } 10 | export default async function compressImage(filename: string, dirPath?: string, opts?: Options): Promise { 11 | 12 | return await new Promise(async (res, rej) => { 13 | if (!dirPath) return rej(undefined); 14 | const currentExt = path.extname(dirPath); 15 | if (currentExt !== ".webp" && currentExt !== ".gif") { 16 | const newDir = path.join(path.dirname(dirPath), path.basename(dirPath, currentExt) + ".webp") 17 | const success = await renameAsync(dirPath, newDir).catch(err => {rej(err)}) 18 | if (!success) return; 19 | dirPath = newDir; 20 | filename = path.basename(filename, currentExt) + ".webp" 21 | } 22 | gmInstance(dirPath) 23 | .resize(1920, 1080, ">") 24 | .quality(90) 25 | .autoOrient() 26 | .write(dirPath, err => { 27 | if (err && dirPath) { 28 | deleteFile(dirPath); 29 | return rej(err); 30 | } 31 | if (!dirPath) return rej(undefined); 32 | res(dirPath); 33 | }) 34 | 35 | }) 36 | 37 | } 38 | 39 | function deleteFile(path: string) { 40 | fs.unlink(path, err => { 41 | if (err) console.error(err) 42 | }); 43 | } 44 | 45 | 46 | function renameAsync(oldDir: string, newDir: string) { 47 | return new Promise((res, rej) => { 48 | fs.rename(oldDir, newDir, err => { 49 | if (err) return rej(err); 50 | res(true); 51 | }) 52 | }) 53 | } -------------------------------------------------------------------------------- /src/utils/cropImage.ts: -------------------------------------------------------------------------------- 1 | import gm from 'gm'; 2 | import sharp from 'sharp'; 3 | 4 | const im = gm.subClass({imageMagick: true}); 5 | 6 | 7 | 8 | export async function cropImage (buffer: Buffer, mimeType: string, size: number): Promise { 9 | if (mimeType === "image/gif") { 10 | return new Promise(resolve => { 11 | im(buffer) 12 | .coalesce() 13 | .resize(size, size, "^") 14 | .gravity("Center") 15 | .crop(size, size) 16 | .repage("+") 17 | .dither(false) 18 | .matte() 19 | .fuzz(10) 20 | .colors(128) 21 | .toBuffer((err, buff) => { 22 | if (err) return resolve(undefined); 23 | resolve(buff); 24 | }); 25 | }); 26 | } 27 | return sharp(buffer) 28 | .resize(size, size) 29 | .toBuffer(); 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /src/utils/genFlakeId.ts: -------------------------------------------------------------------------------- 1 | const FlakeId = require('flakeid'); 2 | 3 | interface IFlakeId { 4 | gen: () => string 5 | } 6 | 7 | const flake: IFlakeId = new FlakeId(); 8 | 9 | export default flake 10 | 11 | -------------------------------------------------------------------------------- /src/utils/getUserDetails.ts: -------------------------------------------------------------------------------- 1 | import { getCustomStatusByUserIds, getPresenceByUserIds, getProgramActivityByUserIds } from "../newRedisWrapper"; 2 | 3 | const redis = require('../redis') 4 | export default async function (userIDArr: string[]){ 5 | 6 | let [result] = await getPresenceByUserIds(userIDArr); 7 | 8 | const memberStatusArr = result.filter((s: string[]) => s[0] !== null && s[1] !== "0"); 9 | 10 | // its ugly, but watever. Most of my code is ugly 🤡 11 | const onlineMemberUserIDArr = memberStatusArr.map((f: string[]) => f[0]); 12 | let [customStatusArr] = await getCustomStatusByUserIds(onlineMemberUserIDArr) 13 | customStatusArr = customStatusArr.filter((s: string[]) => s[0] !== null && s[1] !== null) 14 | 15 | let [programActivities] = await getProgramActivityByUserIds(onlineMemberUserIDArr) 16 | 17 | programActivities = programActivities.map((pa: any, i: number) => { 18 | if (!pa) return undefined; 19 | const user_id = onlineMemberUserIDArr[i]; 20 | const json = JSON.parse(pa); 21 | delete json.socketID; 22 | return {...json, user_id: user_id} 23 | }) 24 | .filter((pa: any) => pa) 25 | return {memberStatusArr, customStatusArr, programActivityArr: programActivities}; 26 | } -------------------------------------------------------------------------------- /src/utils/kickUser.ts: -------------------------------------------------------------------------------- 1 | import { deleteSession } from "../newRedisWrapper"; 2 | import { AUTHENTICATION_ERROR } from "../ServerEventNames"; 3 | import { getIOAdapter, getIOInstance } from "../socket/instance"; 4 | const redis = require("../redis"); 5 | 6 | // excludeSocketID: emit to everyone BUT excludeSocketID 7 | export async function kickUser(userID: string, message: string, excludeSocketID?: string) { 8 | const io = getIOInstance(); 9 | 10 | await deleteSession(userID); 11 | 12 | 13 | io.in(userID).allSockets().then(clients => { 14 | clients.forEach(socket_id => { 15 | if (excludeSocketID === socket_id) return; 16 | io.to(socket_id).emit(AUTHENTICATION_ERROR, message); 17 | getIOAdapter().remoteDisconnect(socket_id, true) 18 | }) 19 | }) 20 | 21 | } -------------------------------------------------------------------------------- /src/utils/rolePermConstants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roles: { 3 | ADMIN: 1, 4 | SEND_MESSAGES: 2, 5 | MANAGE_ROLES: 4, 6 | MANAGE_CHANNELS: 8, 7 | KICK_USER: 16, 8 | BAN_USER: 32, 9 | }, 10 | containsPerm (perms, flag) { 11 | return perms & (flag); 12 | }, 13 | changedPermissions(perms1, perms2) { 14 | const changed = {}; 15 | for (let name in this.roles) { 16 | const perm = this.roles[name]; 17 | const contains1 = !!this.containsPerm(perms1, perm); 18 | const contains2 = !!this.containsPerm(perms2, perm); 19 | if (contains1 !== contains2) { 20 | changed[name] = perm; 21 | } 22 | } 23 | return changed; 24 | } 25 | }; -------------------------------------------------------------------------------- /src/utils/tempSaveImage.ts: -------------------------------------------------------------------------------- 1 | import flake from "./genFlakeId"; 2 | import path from 'path' 3 | import fs from 'fs' 4 | 5 | export default function tempSaveImage(filename: string, file: NodeJS.ReadableStream | Buffer): Promise<{fileid: string, dirPath: string}> { 6 | return new Promise((res) => { 7 | const fileid = flake.gen(); 8 | let dirPath: any = path.join(__dirname, "../", "public", "temp", `${fileid}${path.extname(filename)}`); 9 | 10 | // temporarly store file in server. 11 | if (file instanceof Buffer) { 12 | fs.writeFile(dirPath, file, (err) => { 13 | if (!err) { 14 | res({fileid, dirPath}) 15 | } 16 | }) 17 | return; 18 | } 19 | 20 | const writeStream = fs.createWriteStream(dirPath); 21 | file.pipe(writeStream); 22 | writeStream.on("close", () => { 23 | res({fileid, dirPath}) 24 | writeStream.removeAllListeners(); 25 | writeStream.end(); 26 | }) 27 | }) 28 | } -------------------------------------------------------------------------------- /src/utils/tenor.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | 4 | interface ResponseCategory { 5 | tags: { 6 | searchterm: string, 7 | path: string, 8 | image: string, 9 | name: string 10 | }[] 11 | } 12 | 13 | export async function getTenorCategories() { 14 | const url = `https://g.tenor.com/v1/categories?key=${process.env.TENOR_KEY}&contentfilter=high&type=featured` 15 | return await fetch(url).then(async res => { 16 | const data: ResponseCategory = await res.json(); 17 | return data.tags.map(category => { 18 | return { 19 | name: category.searchterm, 20 | previewUrl: category.image 21 | } 22 | }) 23 | }) 24 | } 25 | 26 | interface ResponseSearch { 27 | results: { 28 | media: any[], 29 | url: string, 30 | }[] 31 | } 32 | 33 | export async function getTenorSearch(value: string) { 34 | const url = `https://g.tenor.com/v1/search?key=${process.env.TENOR_KEY}&contentfilter=high&media_filter=basic&q=${encodeURIComponent(value)}` 35 | return await fetch(url).then(async res => { 36 | const data: ResponseSearch = await res.json(); 37 | return data.results.map(result => { 38 | result.media[0].tinymp4.siteUrl = result.url 39 | return result.media[0].tinymp4; 40 | }) 41 | }) 42 | } -------------------------------------------------------------------------------- /src/utils/uploadBase64Image.js: -------------------------------------------------------------------------------- 1 | const GDriveApi = require("../API/GDrive"); 2 | const stream = require("stream"); 3 | 4 | module.exports = (base64, oauth2Client, maxSize, name) => { 5 | return new Promise(async resolve => { 6 | const buffer = Buffer.from(base64.split(",")[1], "base64"); 7 | 8 | if (buffer.byteLength > maxSize) { 9 | return resolve({ 10 | ok: false, 11 | message: "Image is larger than 2MB." 12 | }); 13 | } 14 | const mimeType = base64MimeType(base64); 15 | if (!checkMimeType(mimeType)) { 16 | return resolve({ ok: false, message: "Invalid image." }); 17 | } 18 | 19 | const readable = new stream.Readable(); 20 | readable._read = () => {}; // _read is required but you can noop it 21 | readable.push(buffer); 22 | readable.push(null); 23 | 24 | // get nertivia_uploads folder id 25 | const requestFolderID = await GDriveApi.findFolder(oauth2Client); 26 | if (!requestFolderID.result) 27 | return resolve({ 28 | ok: false, 29 | message: 30 | "Error occured. Try relinking Google Drive from settings. (Error: Google Drive folder missing.)" 31 | }); 32 | const folderID = requestFolderID.result.id; 33 | 34 | const requestUploadFile = await GDriveApi.uploadFile( 35 | { 36 | fileName: name, 37 | mimeType, 38 | fileStream: readable 39 | }, 40 | folderID, 41 | oauth2Client 42 | ); 43 | if (!requestUploadFile.ok) { 44 | resolve({ 45 | error: requestUploadFile.error, 46 | ok: false, 47 | message: "Something went wrong." 48 | }); 49 | } else { 50 | resolve(requestUploadFile); 51 | } 52 | }); 53 | }; 54 | 55 | 56 | function base64MimeType(encoded) { 57 | var result = null; 58 | 59 | if (typeof encoded !== 'string') { 60 | return result; 61 | } 62 | 63 | var mime = encoded.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/); 64 | 65 | if (mime && mime.length) { 66 | result = mime[1]; 67 | } 68 | 69 | return result; 70 | } 71 | 72 | function checkMimeType(mimeType) { 73 | const filetypes = /jpeg|jpg|gif|png/; 74 | const mime = filetypes.test(mimeType); 75 | if (mime) { 76 | return true; 77 | } 78 | return false; 79 | } -------------------------------------------------------------------------------- /src/utils/uploadCDN/googleDrive.ts: -------------------------------------------------------------------------------- 1 | const { google } = require('googleapis'); 2 | const GDriveApi = require('../../API/GDrive'); 3 | import fs from 'fs'; 4 | 5 | interface Args { 6 | file: File, 7 | oauth2Client: any 8 | } 9 | interface File { 10 | fileName: string 11 | mimeType: string 12 | dirPath: string 13 | } 14 | 15 | 16 | export default async function uploadFile (args: Args) { 17 | 18 | const drive = google.drive({ 19 | version: "v3", 20 | auth: args.oauth2Client 21 | }); 22 | 23 | const requestFolderID = await GDriveApi.findFolder(args.oauth2Client); 24 | const folderID = requestFolderID.result.id; 25 | 26 | //upload file 27 | const fileMetadata: any = { 28 | name: args.file.fileName, 29 | parents: [folderID] 30 | }; 31 | const media = { 32 | mimeType: args.file.mimeType, 33 | body: fs.createReadStream(args.file.dirPath) 34 | }; 35 | const body = { 36 | value: "default", 37 | type: "anyone", 38 | role: "reader" 39 | }; 40 | return new Promise((resolve, reject) => { 41 | drive.files 42 | .create({ 43 | resource: fileMetadata, 44 | media: media, 45 | fields: "id,webViewLink" 46 | }) 47 | .then((result:any) => { 48 | drive.permissions 49 | .create({ 50 | fileId: result.data.id, 51 | resource: body 52 | }) 53 | .then(() => resolve(result)) 54 | .catch((error:any) => reject(error)); 55 | }) 56 | .catch((error:any) => resolve(error)); 57 | }); 58 | } -------------------------------------------------------------------------------- /src/utils/uploadCDN/nertiviaCDN.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import FormData from "form-data"; 3 | export function uploadFile( 4 | BufferOrStream: any, 5 | userid: string, 6 | fileid: string, 7 | filename: string, 8 | isEmoji?: Boolean 9 | ) { 10 | return new Promise((resolve, reject) => { 11 | const form = new FormData(); 12 | 13 | form.append("secret", process.env.FILE_CDN_SECRET); 14 | form.append("userid", userid || ""); 15 | form.append("fileid", fileid || ""); 16 | form.append("isemoji", isEmoji ? "1" : "0"); 17 | form.append("fileToUpload", BufferOrStream, filename); 18 | 19 | fetch("https://media.nertivia.net/indexx.php", { 20 | method: "POST", 21 | body: form, 22 | }).then(async (res) => { 23 | if (res.status == 200) return resolve(true); 24 | const error = await res.text(); 25 | reject(error); 26 | }); 27 | }); 28 | } 29 | 30 | export function deletePath(path: string) { 31 | return new Promise((resolve, reject) => { 32 | fetch("https://media.nertivia.net/indexx-remove.php", { 33 | method: 'DELETE', 34 | body: JSON.stringify({ 35 | secret: process.env.FILE_CDN_SECRET, 36 | removePath: decodeURIComponent(path), 37 | }), 38 | headers: {'Content-Type': 'application/json'} 39 | }).then(async res => { 40 | if (res.status == 200) return resolve(true); 41 | const error = await res.text(); 42 | reject(error); 43 | }) 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /src/utils/zip.ts: -------------------------------------------------------------------------------- 1 | import pako from "pako"; 2 | 3 | export function zip(string: string) { 4 | const binaryString = pako.deflate(string, { to: "string" }); 5 | return Buffer.from(binaryString).toString('base64'); 6 | } 7 | 8 | export function unzip(base64: string) { 9 | try { 10 | const binaryString = Buffer.from(base64, 'base64').toString(); 11 | return pako.inflate(binaryString, { to: "string" }); 12 | } catch { 13 | return null; 14 | } 15 | } --------------------------------------------------------------------------------