diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a1e3ce9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*.js] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*] +insert_final_newline = true + +[{package.json,*.yml}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 05d8046..624ec21 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,18 +1,21 @@ { - "extends": "airbnb", - "rules": { - "comma-dangle": 0, - "max-len": 0, - "no-console": 0, - "no-param-reassign": 0, - "no-shadow": 0, - "consistent-return": 0, - "func-names": 0, - "indent": ["error", 4] - }, - "env": { - "browser": true, - "node": true, - "jquery": true - } + "extends": "airbnb", + "rules": { + "comma-dangle": 0, + "max-len": 0, + "no-console": 0, + "no-param-reassign": 0, + "no-shadow": 0, + "consistent-return": 0, + "func-names": 0, + "indent": ["error", "tab", { "SwitchCase": 1 }], + "strict": 0, + "guard-for-in": "warn", + "no-restricted-syntax": ["warn", "ForInStatement"] + }, + "env": { + "browser": true, + "node": true, + "jquery": true + } } \ No newline at end of file diff --git a/config.example.hjson b/config.example.hjson index f75be08..0cd6562 100644 --- a/config.example.hjson +++ b/config.example.hjson @@ -1,469 +1,473 @@ { - /* - Set this flag to false to disable web server hosting or true to enable web server hosting. - This is useful if you want to host static files in another web server such as nginx. - - If you are only hosting the socket and want musiqpad to host the frontend set this to false. - */ - hostWebserver: true - socketServer: { - host: "" // Host name or IP that the socket server is located at. Leave blank to bind to process IP address - port: 8082// Leave blank to bind to process PORT - } - webServer: { - address: "" // Leave blank to bind to process IP address. - port: 8080// Leave blank to bind to process PORT. + /* + Set this flag to false to disable web server hosting or true to enable web server hosting. + This is useful if you want to host static files in another web server such as nginx. + + If you are only hosting the socket and want musiqpad to host the frontend set this to false. + */ + hostWebserver: true + socketServer: { + host: "" // Host name or IP that the socket server is located at. Leave blank to bind to process IP address + port: 8082// Leave blank to bind to process PORT + } + webServer: { + address: "" // Leave blank to bind to process IP address. + port: 8080// Leave blank to bind to process PORT. - redirectHTTP: false// Set to true if you want HTTP redirect to HTTPS. - redirectPort: 80// Required if setting above is true. Set to the port you want to redirect HTTP to HTTPS from (Default: 80). - } - useSSL: false// If you want your pad to be accesible over HTTPS set SSL to true and add the path of your certificates - certificate: { - key: "path-to-key" - cert: "path-to-cert" - } - room: { - name: "Pad Name" // This is your pad name. It is shown as a user friendly description on the lounge and tab name. - slug: "this-is-your-slug" // Slugs are used to identify your pad when connecting to musiqpad! This slug must be unique and all in lowecase. - greet: "Welcome to musiqpad!" - bg: "" // Background image file path. Accepts external images. If this is undefined the default background will be used. - maxCon: 0 - ownerEmail: "user@domain.tld" // This needs to be set then the server restarted to take effect. - guestCanSeeChat: true - bannedCanSeeChat: false - lastmsglimit: 6// How many messages a user can see after joining. - signupcd: 0// How many miliseconds the user cannot do certain things after they sign up. - allowemojis: true - allowrecovery: false - recaptcha: false - queue: { - cycle: true - lock: false - limit: 50 - } - history: { - limit_save: 0 - limit_send: 50 - } - mail: { - confirmation: false// Whether to force user to confirm his email address before he is able to do anything - sender: "user@domain.tld" // Domain should point to this box when using direct mode - - // DIRECT PROBABLY WON'T WORK BECAUSE YOUR IP DOESN'T HAVE A GOOD REPUTATION! - - transport: "direct" // 'smtp', 'direct' or 'xoauth' - options: {} - /* - EXAMPLES: - - -- SMTP -- - transport: "smtp" - options: { - service: "gmail" - auth: { - user: "mail@somewebsite.com", - pass: "pass", - } - } - ---------- - - -- XOAUTH2 -- - transport: "xoauth" - options: { - service: "gmail" - auth: { - xoauth: { - user: '{username}' - clientId: '{Client ID}' - clientSecret: '{Client Secret}' - refreshToken: '{refresh-token}' - accessToken: '{cached access token} - } - } - } - ------------- - - -- DIRECT -- - transport: "direct" - options: {} - ------------- - */ - } - description: - ''' -

Pad Description

- Here you can put anything you want in HTML! - ''' - tags: { // Tags for Google & co - keywords: "musiqpad" - description: "" - image: "https://cdn.musiqpad.com/img/icon-256.png" // Image on twitter/facebook/slack/... - twitter: "@musiqpad" - description: // A one to two sentence description for search engines & co - ''' - - ''' - themeColor: "" // a hex color for the theme on chrome for android - favicon: "/pads/lib/img/icon.png" - } - } - apis: { - YT: { - key: "" // Required api key in order for YouTube search to work. - restrictSearchToMusic: false - } - SC: { - key: "" - } - reCaptcha: { - key: "" - secret: "" - } - musiqpad: { - key: "" // This is required in order for your socket to update the musiqpad lounge. Request an API Key here: https://musiqpad.com/lounge - sendLobbyStats: false - } - } + redirectHTTP: false// Set to true if you want HTTP redirect to HTTPS. + redirectPort: 80// Required if setting above is true. Set to the port you want to redirect HTTP to HTTPS from (Default: 80). + } + useSSL: false// If you want your pad to be accesible over HTTPS set SSL to true and add the path of your certificates + certificate: { + key: "path-to-key" + cert: "path-to-cert" + } + room: { + name: "Pad Name" // This is your pad name. It is shown as a user friendly description on the lounge and tab name. + slug: "this-is-your-slug" // Slugs are used to identify your pad when connecting to musiqpad! This slug must be unique and all in lowecase. + greet: "Welcome to musiqpad!" + bg: "" // Background image file path. Accepts external images. If this is undefined the default background will be used. + maxCon: 0 + ownerEmail: "user@domain.tld" // This needs to be set then the server restarted to take effect. + guestCanSeeChat: true + bannedCanSeeChat: false + lastmsglimit: 6// How many messages a user can see after joining. + signupcd: 0// How many miliseconds the user cannot do certain things after they sign up. + allowemojis: true + allowrecovery: false + recaptcha: false + queue: { + cycle: true + lock: false + limit: 50 + } + history: { + limit_save: 0 + limit_send: 50 + } + mail: { + confirmation: false// Whether to force user to confirm his email address before he is able to do anything + sender: "user@domain.tld" // Domain should point to this box when using direct mode + + // DIRECT PROBABLY WON'T WORK BECAUSE YOUR IP DOESN'T HAVE A GOOD REPUTATION! + + transport: "direct" // 'smtp', 'direct' or 'xoauth' + options: {} + /* + EXAMPLES: + + -- SMTP -- + transport: "smtp" + options: { + service: "gmail" + auth: { + user: "mail@somewebsite.com", + pass: "pass", + } + } + ---------- + + -- XOAUTH2 -- + transport: "xoauth" + options: { + service: "gmail" + auth: { + xoauth: { + user: '{username}' + clientId: '{Client ID}' + clientSecret: '{Client Secret}' + refreshToken: '{refresh-token}' + accessToken: '{cached access token} + } + } + } + ------------- + + -- DIRECT -- + transport: "direct" + options: {} + ------------- + */ + } + description: + ''' +

Pad Description

+ Here you can put anything you want in HTML! + ''' + tags: { // Tags for Google & co + keywords: "musiqpad" + description: "" + image: "https://cdn.musiqpad.com/img/icon-256.png" // Image on twitter/facebook/slack/... + twitter: "@musiqpad" + description: // A one to two sentence description for search engines & co + ''' + + ''' + themeColor: "" // a hex color for the theme on chrome for android + favicon: "/pads/lib/img/icon.png" + } + scripts: { // Only if you host the frontend yourself + js: [], // Example: ["https://exapmple.com/musiqpad.js", "lib/js/custom.js"] + css: [] + } + } + apis: { + YT: { + key: "" // Required api key in order for YouTube search to work. + restrictSearchToMusic: false + } + SC: { + key: "" + } + reCaptcha: { + key: "" + secret: "" + } + musiqpad: { + key: "" // This is required in order for your socket to update the musiqpad lounge. Request an API Key here: https://musiqpad.com/lounge + sendLobbyStats: false + } + } - // The amount of time users stay logged in for before having to login again in days. - // 0 = login every time - loginExpire: 7 - db: { - dbType: "level" // Values "level" for LevelDB, "mysql" for MySQL and "mongo" for MongoDB - dbDir: "./socketserver/db" // Only used for LevelDB. Directory to save databases. Default is ./socketserver/db - mysqlUser: "" // Only used for MySQL. Database username - mysqlPassword: "" // Only used for MySQL. Database password - mysqlHost: "" // Only used for MySQL. Host address - mysqlDatabase: "" // Only used for MySQL. Database being used - mongoUser: "" // Only used for MongoDB. Database username - mongoPassword: "" // Only used for MongoDB. Database password - mongoHost: "" // Only used for MongoDB. Host address - mongoDatabase: "" // Only used for MongoDB. Database being used - } - - /* - "djqueue.join": Ability to join queue - "djqueue.joinlocked": Ability to join locked queue - "djqueue.leave": Ability to leave queue - "djqueue.skip.self": Ability to skip self - "djqueue.skip.other": Ability to skip others - "djqueue.lock": Ability to lock/unlock queue - "djqueue.limit": Ability to change waitlist limit - "djqueue.cycle": Ability to enable/disable queue cycle - "djqueue.move": Ability to move, swap, add and remove people in the queue - "djqueue.playLiveVideos": Ability to play live videos with undefined duration - "djqueue.lock.bypass": Bypass locked queue - "djqueue.limit.bypass": Bypass queue limit - "chat.send": Abilty to send chat messages - "chat.delete": Ability to delete others" chat messages - "chat.specialMention": Ability to use @everyone, @guest and @djs as mention - "chat.broadcast": Ability to send a highlighted broadcast message - "chat.private": Ability to send PMs - "chat.staff": Ability to send and receive special staff chat - "playlist.create": Ability to create playlists - "playlist.delete": Ability to delete playlists - "playlist.rename": Ability to rename playlists - "playlist.import": Ability to import playlists - "playlist.shuffle": Ability to shuffle playlists - "room.grantroles": Ability to change user roles (requires canGrantPerms property) - "room.restrict.ban": Ability to ban and unban users - "room.restrict.mute": Ability to mute and unmute users - "room.restrict.mute_silent": Ability to shadow mute and unmute users - "room.ratelimit.bypass": Will bypass ratelimit - "room.whois": Possibility to request additional information about a user - "room.whois.iphistory": Possibility to request all IP addresses that the user logged from since account creation + // The amount of time users stay logged in for before having to login again in days. + // 0 = login every time + loginExpire: 7 + db: { + dbType: "level" // Values "level" for LevelDB, "mysql" for MySQL and "mongo" for MongoDB + dbDir: "./socketserver/db" // Only used for LevelDB. Directory to save databases. Default is ./socketserver/db + mysqlUser: "" // Only used for MySQL. Database username + mysqlPassword: "" // Only used for MySQL. Database password + mysqlHost: "" // Only used for MySQL. Host address + mysqlDatabase: "" // Only used for MySQL. Database being used + mongoUser: "" // Only used for MongoDB. Database username + mongoPassword: "" // Only used for MongoDB. Database password + mongoHost: "" // Only used for MongoDB. Host address + mongoDatabase: "" // Only used for MongoDB. Database being used + } + + /* + "djqueue.join": Ability to join queue + "djqueue.joinlocked": Ability to join locked queue + "djqueue.leave": Ability to leave queue + "djqueue.skip.self": Ability to skip self + "djqueue.skip.other": Ability to skip others + "djqueue.lock": Ability to lock/unlock queue + "djqueue.limit": Ability to change waitlist limit + "djqueue.cycle": Ability to enable/disable queue cycle + "djqueue.move": Ability to move, swap, add and remove people in the queue + "djqueue.playLiveVideos": Ability to play live videos with undefined duration + "djqueue.lock.bypass": Bypass locked queue + "djqueue.limit.bypass": Bypass queue limit + "chat.send": Abilty to send chat messages + "chat.delete": Ability to delete others" chat messages + "chat.specialMention": Ability to use @everyone, @guest and @djs as mention + "chat.broadcast": Ability to send a highlighted broadcast message + "chat.private": Ability to send PMs + "chat.staff": Ability to send and receive special staff chat + "playlist.create": Ability to create playlists + "playlist.delete": Ability to delete playlists + "playlist.rename": Ability to rename playlists + "playlist.import": Ability to import playlists + "playlist.shuffle": Ability to shuffle playlists + "room.grantroles": Ability to change user roles (requires canGrantPerms property) + "room.restrict.ban": Ability to ban and unban users + "room.restrict.mute": Ability to mute and unmute users + "room.restrict.mute_silent": Ability to shadow mute and unmute users + "room.ratelimit.bypass": Will bypass ratelimit + "room.whois": Possibility to request additional information about a user + "room.whois.iphistory": Possibility to request all IP addresses that the user logged from since account creation - NOTE: Changing the PROPERTY NAME will break role assignments. Title can be changed - without breaking things, but property name must stay the same. - */ + NOTE: Changing the PROPERTY NAME will break role assignments. Title can be changed + without breaking things, but property name must stay the same. + */ - - // Defines the order that roles will appear on the user list - // PROPERTY names. NOT title. (case-sensitive) - roleOrder: [ - "dev" - "owner" - "coowner" - "supervisor" - "bot" - "regular" - "default" - ] - - // Defines which roles are "staff" members - // PROPERTY names. NOT title. (case-sensitive) - staffRoles: [ - "dev" - "owner" - "coowner" - "supervisor" - "bot" - ] - + + // Defines the order that roles will appear on the user list + // PROPERTY names. NOT title. (case-sensitive) + roleOrder: [ + "dev" + "owner" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + + // Defines which roles are "staff" members + // PROPERTY names. NOT title. (case-sensitive) + staffRoles: [ + "dev" + "owner" + "coowner" + "supervisor" + "bot" + ] + - /* + /* - Role Options: + Role Options: - rolename:{ - title: "", // This is the title that gets displayed on the frontend. - showtitle: true/false, // This is whether or not to display the title on the frontend. - badge: "", // This can be any icon from the mdi package. A list of the icons is available here: https://materialdesignicons.com - style: {}, // This can be used to set specific styles to the Username of a user with this role. - permissions: [], // A list of permissions a user with this role is allowed to use. - canGrantRoles: [], // A list of the roles that a user with this role can grant. I.e. an owner should be able to grant manager. - mention: "" // A custom mention. I.e. "owner" would mention this group when someone typed @owner. - } + rolename:{ + title: "", // This is the title that gets displayed on the frontend. + showtitle: true/false, // This is whether or not to display the title on the frontend. + badge: "", // This can be any icon from the mdi package. A list of the icons is available here: https://materialdesignicons.com + style: {}, // This can be used to set specific styles to the Username of a user with this role. + permissions: [], // A list of permissions a user with this role is allowed to use. + canGrantRoles: [], // A list of the roles that a user with this role can grant. I.e. an owner should be able to grant manager. + mention: "" // A custom mention. I.e. "owner" would mention this group when someone typed @owner. + } - Below are a list of roles we suggest using. + Below are a list of roles we suggest using. - */ + */ - // Defines roles and permissions - roles: { - owner: { // REQUIRED ROLE - title: "Owner" - showtitle: true - style: { - color: "#F46B40" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "djqueue.skip.self" - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.limit" - "djqueue.move" - "djqueue.playLiveVideos" - "djqueue.limit.bypass" - "djqueue.lock.bypass" - "chat.send" - "chat.private" - "chat.broadcast" - "chat.delete" - "chat.specialMention" - "chat.staff" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - "playlist.shuffle" - "room.grantroles" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - "room.whois" - "room.whois.iphistory" - "server.checkForUpdates" - ] - canGrantRoles: [ - "dev" - "coowner" - "supervisor" - "bot" - "regular" - "default" - ] - } - dev: { // OPTIONAL ROLE FOR MUSIQPAD DEVS - title: "Dev" - showtitle: true - style: { - color: "#A77DC2" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "djqueue.skip.self" - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.limit" - "djqueue.move" - "djqueue.playLiveVideos" - "djqueue.limit.bypass" - "djqueue.lock.bypass" - "chat.send" - "chat.private" - "chat.broadcast" - "chat.delete" - "chat.specialMention" - "chat.staff" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - "playlist.shuffle" - "room.grantroles" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - "room.whois" - ] - canGrantRoles: [ - "dev" - "coowner" - "supervisor" - "bot" - "regular" - "default" - ] - mention: "devs" - } - coowner: { - title: "Co-owner" - showtitle: true - style: { - color: "#89BE6C" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "djqueue.skip.self" - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.limit" - "djqueue.move" - "djqueue.playLiveVideos" - "djqueue.limit.bypass" - "djqueue.lock.bypass" - "chat.send" - "chat.private" - "chat.delete" - "chat.specialMention" - "chat.broadcast" - "chat.staff" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - "playlist.shuffle" - "room.grantroles" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - "room.whois" - "room.whois.iphistory" - ] - canGrantRoles: [ - "supervisor" - "bot" - "regular" - "default" - ] - } - supervisor: { - title: "Supervisor" - showtitle: true - style: { - color: "#009CDD" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "djqueue.skip.self" - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.move" - "djqueue.playLiveVideos" - "djqueue.limit.bypass" - "djqueue.lock.bypass" - "chat.send" - "chat.private" - "chat.delete" - "chat.specialMention" - "chat.staff" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - "playlist.shuffle" - "room.grantroles" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - "room.whois" - ] - canGrantRoles: [ - "regular" - "default" - ] - } - bot: { - title: "Bot" - showtitle: true - badge: "android" - style: { - color: "#964B74" - } - permissions: [ - "djqueue.skip.other" - "djqueue.lock" - "djqueue.cycle" - "djqueue.move" - "chat.send" - "chat.delete" - "chat.specialMention" - "room.restrict.ban" - "room.restrict.mute" - "room.restrict.mute_silent" - "room.ratelimit.bypass" - ] - canGrantRoles: [ - ] - } - regular: { - title: "Regular" - showtitle: false - style: { - color: "#925AFF" - } - permissions: [ - "djqueue.join" - "djqueue.joinlocked" - "djqueue.leave" - "chat.send" - "chat.private" - "djqueue.skip.self" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - ] - canGrantRoles: [ - ] - } - default: { // REQUIRED ROLE - title: "Default" - showtitle: false - style: { - color: "#ffffff" - } - permissions: [ - "djqueue.join" - "djqueue.leave" - "chat.send" - "chat.private" - "djqueue.skip.self" - "playlist.create" - "playlist.delete" - "playlist.rename" - "playlist.import" - ] - canGrantRoles: [ - ] - } - } + // Defines roles and permissions + roles: { + owner: { // REQUIRED ROLE + title: "Owner" + showtitle: true + style: { + color: "#F46B40" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.broadcast" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + "room.whois.iphistory" + "server.checkForUpdates" + ] + canGrantRoles: [ + "dev" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + } + dev: { // OPTIONAL ROLE FOR MUSIQPAD DEVS + title: "Dev" + showtitle: true + style: { + color: "#A77DC2" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.broadcast" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + ] + canGrantRoles: [ + "dev" + "coowner" + "supervisor" + "bot" + "regular" + "default" + ] + mention: "devs" + } + coowner: { + title: "Co-owner" + showtitle: true + style: { + color: "#89BE6C" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.limit" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.delete" + "chat.specialMention" + "chat.broadcast" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + "room.whois.iphistory" + ] + canGrantRoles: [ + "supervisor" + "bot" + "regular" + "default" + ] + } + supervisor: { + title: "Supervisor" + showtitle: true + style: { + color: "#009CDD" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "djqueue.skip.self" + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.move" + "djqueue.playLiveVideos" + "djqueue.limit.bypass" + "djqueue.lock.bypass" + "chat.send" + "chat.private" + "chat.delete" + "chat.specialMention" + "chat.staff" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + "playlist.shuffle" + "room.grantroles" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + "room.whois" + ] + canGrantRoles: [ + "regular" + "default" + ] + } + bot: { + title: "Bot" + showtitle: true + badge: "android" + style: { + color: "#964B74" + } + permissions: [ + "djqueue.skip.other" + "djqueue.lock" + "djqueue.cycle" + "djqueue.move" + "chat.send" + "chat.delete" + "chat.specialMention" + "room.restrict.ban" + "room.restrict.mute" + "room.restrict.mute_silent" + "room.ratelimit.bypass" + ] + canGrantRoles: [ + ] + } + regular: { + title: "Regular" + showtitle: false + style: { + color: "#925AFF" + } + permissions: [ + "djqueue.join" + "djqueue.joinlocked" + "djqueue.leave" + "chat.send" + "chat.private" + "djqueue.skip.self" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + ] + canGrantRoles: [ + ] + } + default: { // REQUIRED ROLE + title: "Default" + showtitle: false + style: { + color: "#ffffff" + } + permissions: [ + "djqueue.join" + "djqueue.leave" + "chat.send" + "chat.private" + "djqueue.skip.self" + "playlist.create" + "playlist.delete" + "playlist.rename" + "playlist.import" + ] + canGrantRoles: [ + ] + } + } } \ No newline at end of file diff --git a/socketserver/database_util.js b/socketserver/database_util.js index c6ef8b1..e32b9ba 100644 --- a/socketserver/database_util.js +++ b/socketserver/database_util.js @@ -3,15 +3,15 @@ const Hash = require('./hash'); function DBUtils() {} DBUtils.prototype.makePass = function (inPass, salt) { - return Hash.md5(('' + inPass) + (salt || '')).toString(); + return Hash.md5(('' + inPass) + (salt || '')).toString(); }; DBUtils.prototype.validateEmail = function (email) { - return /^.+@.+\..+$/.test(email); + return /^.+@.+\..+$/.test(email); }; DBUtils.prototype.validateUsername = function (un) { - return /^[a-z0-9_-]{3,20}$/i.test(un); + return /^[a-z0-9_-]{3,20}$/i.test(un); }; module.exports = new DBUtils(); diff --git a/socketserver/db_level.js b/socketserver/db_level.js index 20b65d7..f0fc439 100644 --- a/socketserver/db_level.js +++ b/socketserver/db_level.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line 'use strict'; // Modules const levelup = require('levelup'); diff --git a/socketserver/db_mongo.js b/socketserver/db_mongo.js index a9779af..96ca9f2 100644 --- a/socketserver/db_mongo.js +++ b/socketserver/db_mongo.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line 'use strict'; // Modules const mongodb = require('mongodb').MongoClient; diff --git a/socketserver/mail/mailer.js b/socketserver/mail/mailer.js index 857ce83..e397714 100644 --- a/socketserver/mail/mailer.js +++ b/socketserver/mail/mailer.js @@ -1,68 +1,76 @@ +'use strict'; const nodemailer = require('nodemailer'); const xoauth2 = require('xoauth2'); const fs = require('fs-extra'); const nconf = require('nconf'); const ejs = require('ejs'); -var Mailer = function () { - const options = nconf.get('room:mail:options'); - var _this = this; - _this.test = 1; - if (nconf.get('room:allowrecovery') || nconf.get('room:mail:confirmation')) { - switch (nconf.get('room:mail:transport')) { - case 'smtp': { - _this.transporter = nodemailer.createTransport(options); - break; - } - case 'xoauth': { - const xoauth = xoauth2.createXOAuth2Generator({ - user: options.auth.xoauth.user, - clientId: options.auth.xoauth.clientId, - clientSecret: options.auth.xoauth.clientSecret, - refreshToken: options.auth.xoauth.refreshToken, - accessToken: options.auth.xoauth.accessToken - }); - options.auth.xoauth = xoauth; - _this.transporter = nodemailer.createTransport(options); - break; - } - case 'direct': { - const domain = nconf.get('room:mail:sender').split('@')[1]; - options.name = domain; - _this.transporter = nodemailer.createTransport(options); - break; - } - } +class Mailer { + constructor() { + const options = nconf.get('room:mail:options'); + const self = this; + + if (nconf.get('room:allowrecovery') || nconf.get('room:mail:confirmation')) { + switch (nconf.get('room:mail:transport')) { + case 'smtp': { + self.transporter = nodemailer.createTransport(options); + break; + } + case 'xoauth': { + const xoauth = xoauth2.createXOAuth2Generator({ + user: options.auth.xoauth.user, + clientId: options.auth.xoauth.clientId, + clientSecret: options.auth.xoauth.clientSecret, + refreshToken: options.auth.xoauth.refreshToken, + accessToken: options.auth.xoauth.accessToken + }); + options.auth.xoauth = xoauth; + self.transporter = nodemailer.createTransport(options); + break; + } + case 'direct': { + const domain = nconf.get('room:mail:sender').split('@')[1]; + options.name = domain; + self.transporter = nodemailer.createTransport(options); + break; + } + default: { + break; + } + } + } } -}; -Mailer.prototype.sendEmail = function (type, opts, receiver, callback) { - const html = ejs.render(fs.readFileSync(`socketserver/mail/templates/${type}.html`, 'utf8'), { - opts, - room: nconf.get('room'), - }); + sendEmail(type, opts, receiver, callback) { + const html = ejs.render(fs.readFileSync(`socketserver/mail/templates/${type}.html`, 'utf8'), { + opts, + room: nconf.get('room'), + }); - var subject; - switch(type) { - case 'signup': - subject = 'Welcome to musiqpad!'; - break; - case 'recovery': - subject = 'Password recovery'; - break; - } + let subject; + switch (type) { + case 'signup': + subject = 'Welcome to musiqpad!'; + break; + case 'recovery': + subject = 'Password recovery'; + break; + default: + break; + } - const emailObj = { - from: nconf.get('room:mail:sender'), - to: receiver, - subject, - html, - }; + const emailObj = { + from: nconf.get('room:mail:sender'), + to: receiver, + subject, + html, + }; - this.transporter.sendMail(emailObj, (error, response) => { - if (error) callback(error); - else callback(null, response); - }); -}; + this.transporter.sendMail(emailObj, (error, response) => { + if (error) callback(error); + else callback(null, response); + }); + } +} module.exports = new Mailer(); diff --git a/socketserver/role.js b/socketserver/role.js index e7805ac..9d0e3dc 100644 --- a/socketserver/role.js +++ b/socketserver/role.js @@ -1,97 +1,94 @@ -// eslint-disable-next-line 'use strict'; const nconf = require('nconf'); -var roles = nconf.get('roles'); +let roles = nconf.get('roles'); +let roleOrder = null; +let staffRoles = null; -// Caching the value so we don't have to loop through something every login -var roleOrder = null; -var staffRoles = null; +class Role { + getRole(inRole) { + if (this.roleExists(inRole)) { + return roles[inRole]; + } -function Role(){ - -} + return roles.default; + } -Role.prototype.getRole = function(inRole){ - if (this.roleExists(inRole)) - return roles[inRole]; - - return roles.default; -}; - -Role.prototype.roleExists = function(inRole){ - if (typeof roles[inRole] !== 'undefined') - return true; - return false; -}; - -Role.prototype.checkPermission = function(inRole, inPerm){ - var role = this.getRole(inRole); - if (role){ - if((typeof inPerm) == 'string') { - return role.permissions.indexOf(inPerm) != -1; - }else{ - for(var i = 0; i < inPerm.length; i++) - if(role.permissions.indexOf(inPerm[i]) == -1) return false; + roleExists(inRole) { + if (typeof roles[inRole] !== 'undefined') { + return true; } - return true; + return false; } - return false; -}; - -Role.prototype.checkCanGrant = function(inRole, inPerm){ - var role = this.getRole(inRole); - - if (role){ - if((typeof inPerm) == 'string') { - inPerm = inPerm.toLowerCase(); - return role.canGrantRoles.indexOf(inPerm) != -1; - }else{ - for(var i = 0; i < inPerm.length; i++) - if(role.canGrantRoles.indexOf(inPerm[i].toLowerCase()) == -1) return false; + + checkPermission(inRole, inPerm) { + const role = this.getRole(inRole); + if (role) { + if ((typeof inPerm) === 'string') { + return role.permissions.indexOf(inPerm) !== -1; + } + for (let i = 0; i < inPerm.length; i++) { + if (role.permissions.indexOf(inPerm[i]) === -1) { + return false; + } + } + return true; } - - return true; + return false; } - return false; -}; -Role.prototype.makeClientObj = function () { - return roles; -}; + checkCanGrant(inRole, inPerm) { + const role = this.getRole(inRole); -Role.prototype.getOrder = function () { - if (roleOrder) return roleOrder; + if (role) { + if ((typeof inPerm) === 'string') { + inPerm = inPerm.toLowerCase(); + return role.canGrantRoles.indexOf(inPerm) !== -1; + } + for (let i = 0; i < inPerm.length; i++) { + if (role.canGrantRoles.indexOf(inPerm[i].toLowerCase()) === -1) return false; + } - let roleOrderTemp = nconf.get('roleOrder'); - if (roleOrderTemp && Array.isArray(roleOrderTemp)){ - for (var i in roles){ - if (roleOrderTemp.indexOf(i) == -1) roleOrderTemp.push(i); + return true; } + return false; + } - roleOrder = roleOrderTemp; - return roleOrderTemp; - } - - var temp = []; - - for (var i in roles){ - temp.push(i); + makeClientObj() { + return roles; } - - roleOrder = temp; - return temp; -}; - -Role.prototype.getStaffRoles = function(){ - if(staffRoles) return staffRoles; - - if (nconf.get('staffRoles') && Array.isArray(nconf.get('staffRoles'))) { - staffRoles = nconf.get('staffRoles'); - return staffRoles; + + getOrder() { + if (roleOrder) return roleOrder; + + const roleOrderTemp = nconf.get('roleOrder'); + if (roleOrderTemp && Array.isArray(roleOrderTemp)) { + for (let i in roles) { + if (roleOrderTemp.indexOf(i) === -1) roleOrderTemp.push(i); + } + + roleOrder = roleOrderTemp; + return roleOrderTemp; + } + + const temp = []; + + for (var i in roles) { + temp.push(i); + } + + roleOrder = temp; + return temp; } - return []; -}; + getStaffRoles() { + if (staffRoles) return staffRoles; + if (nconf.get('staffRoles') && Array.isArray(nconf.get('staffRoles'))) { + staffRoles = nconf.get('staffRoles'); + return staffRoles; + } + return []; + } +} module.exports = new Role(); diff --git a/socketserver/room.js b/socketserver/room.js index 0c80083..1abedb0 100644 --- a/socketserver/room.js +++ b/socketserver/room.js @@ -1,710 +1,699 @@ -var extend = require('extend'); -var ws = require('ws'); -var https = require('https'); -var http = require('http'); -var log = new (require('basic-logger'))({showTimestamp: true, prefix: "Room"}); -var DJQueue = require('./djqueue.js'); -var Roles = require('./role'); -var DB = require('./database'); +'use strict'; +const extend = require('extend'); +const ws = require('ws'); +const https = require('https'); +const log = new (require('basic-logger'))({ showTimestamp: true, prefix: 'Room' }); +const DJQueue = require('./djqueue.js'); +const Roles = require('./role'); +const DB = require('./database'); const nconf = require('nconf'); -var defaultDBObj = function(){ +const DefaultDBObj = function () { return { - roles: {}, + roles: {}, restrictions: {}, // Uses UID as key, object containing reason and end time as value. history: [] }; }; -var Room = function(socketServer, options){ - var that = this; - - this.roomInfo = extend(true, { - name: "", // Room name - slug: "", // Room name shorthand (no spaces, alphanumeric with dashes) - greet: "", // Room greetings - maxCon: 0, // Max connections; 0 = unlimited - ownerEmail: "", // Owner email for owner promotion - guestCanSeeChat: true, // Whether guests can see the chat or not - bannedCanSeeChat: true, // Whether banned users can see the chat - roomOwnerUN: null, // Username of the room owner to use with lobby API - }, options); - - this.socketServer = socketServer; - this.queue = new DJQueue( this ); - this.attendeeList = []; - this.data = new defaultDBObj(); - this.apiUpdateTimeout = null; - this.lastChat = []; - this.createApiTimeout(); - - this.restrictiontypes = [ - 'BAN', - 'MUTE', - 'SILENT_MUTE' +class Room { + constructor(socketServer, options) { + const that = this; + + this.roomInfo = extend(true, { + name: '', // Room name + slug: '', // Room name shorthand (no spaces, alphanumeric with dashes) + greet: '', // Room greetings + maxCon: 0, // Max connections; 0 = unlimited + ownerEmail: '', // Owner email for owner promotion + guestCanSeeChat: true, // Whether guests can see the chat or not + bannedCanSeeChat: true, // Whether banned users can see the chat + roomOwnerUN: null, // Username of the room owner to use with lobby API + }, options); + + this.socketServer = socketServer; + this.queue = new DJQueue(this); + this.attendeeList = []; + this.data = new DefaultDBObj(); + this.apiUpdateTimeout = null; + this.lastChat = []; + this.createApiTimeout(); + + this.restrictiontypes = [ + 'BAN', + 'MUTE', + 'SILENT_MUTE' ]; - DB.getRoom(this.roomInfo.slug, function(err, data){ - // Just in case the slug doesn't exist yet - data = data || {}; + DB.getRoom(this.roomInfo.slug, (err, data) => { + // Just in case the slug doesn't exist yet + data = data || {}; - // If the slug doesn't exist, make owner will make the slug - if (err && !err.notFound){ - console.log(err); - return; - } - - extend(true, that.data, data); + // If the slug doesn't exist, make owner will make the slug + if (err && !err.notFound) { + console.log(err); + return; + } - that.makeOwner(); - }); -}; + extend(true, that.data, data); -Room.prototype.getRoomMeta = function(){ - return { - name: this.roomInfo.name, - slug: this.roomInfo.slug, - greet: this.roomInfo.greet, - bg: this.roomInfo.bg, - guestCanSeeChat: this.roomInfo.guestCanSeeChat, - bannedCanSeeChat: this.roomInfo.bannedCanSeeChat, - roomOwnerUN: this.roomInfo.roomOwnerUN - }; -}; + that.makeOwner(); + }); + } -Room.prototype.makeOwner = function(){ - if (!nconf.get('room:ownerEmail')) return; + getRoomMeta() { + return { + name: this.roomInfo.name, + slug: this.roomInfo.slug, + greet: this.roomInfo.greet, + bg: this.roomInfo.bg, + guestCanSeeChat: this.roomInfo.guestCanSeeChat, + bannedCanSeeChat: this.roomInfo.bannedCanSeeChat, + roomOwnerUN: this.roomInfo.roomOwnerUN + }; + } - var that = this; + makeOwner() { + if (!nconf.get('room:ownerEmail')) return; - DB.getUser(this.roomInfo.ownerEmail, function(err, data){ - if (err == 'UserNotFound') { console.log('Owner does not exist yet.'); that.data.roles.owner = []; return; } - if (err) { console.log('Cannot make Room Owner: ' + err); return; } + const that = this; - if (typeof data.uid !== 'number') { console.log('Cannot make room owner: UserUIDError'); return; } + DB.getUser(this.roomInfo.ownerEmail, (err, data) => { + if (err === 'UserNotFound') { + console.log('Owner does not exist yet.'); + that.data.roles.owner = []; + return; + } + if (err) { + console.log(`Cannot make Room Owner: ${err}`); + return; + } - log.info('Granting ' + data.un + ' (' + data.uid + ') Owner permissions'); + if (typeof data.uid !== 'number') { + console.log('Cannot make room owner: UserUIDError'); + return; + } - // Remove user from other roles to avoid interesting bugs - for (var i in that.data.roles){ - var ind = that.data.roles[i].indexOf(data.uid); - if ( ind > -1 ) that.data.roles[i].splice(ind, 1); - } + log.info(`Granting ${data.un} (${data.uid}) Owner permissions`); - // Only one owner, set entire array to one UID and set owner username for API - that.data.roles.owner = [ data.uid ]; - that.data.roomOwnerUN = data.un; - that.roomInfo.roomOwnerUN = data.un; - data.role = that.findRole(data.uid); - that.sendUserUpdate(data); - that.save(); - }); -}; + // Remove user from other roles to avoid interesting bugs + for (const i in that.data.roles) { + const ind = that.data.roles[i].indexOf(data.uid); + if (ind > -1) that.data.roles[i].splice(ind, 1); + } -Room.prototype.addUser = function( sock ){ - this.attendeeList.push( sock ); - var userSend = null; - var numGuests = 0; - sock.room = this.roomInfo.slug; + // Only one owner, set entire array to one UID and set owner username for API + that.data.roles.owner = [data.uid]; + that.data.roomOwnerUN = data.un; + that.roomInfo.roomOwnerUN = data.un; + data.role = that.findRole(data.uid); + that.sendUserUpdate(data); + that.save(); + }); + } - if (sock.user){ - this.checkMakeOwner(); - sock.user.data.role = this.findRole(sock.user.data.uid); - userSend = sock.user.getClientObj(); + addUser(sock) { + this.attendeeList.push(sock); + let userSend = null; + let numGuests = 0; + sock.room = this.roomInfo.slug; + + if (sock.user) { + this.checkMakeOwner(); + sock.user.data.role = this.findRole(sock.user.data.uid); + userSend = sock.user.getClientObj(); - for (var i = 0; i < this.attendeeList.length; i++){ - var sockObj = this.attendeeList[i]; + for (let i = 0; i < this.attendeeList.length; i++) { + const sockObj = this.attendeeList[i]; - if (!sockObj.user){ - numGuests++; - continue; - } + if (!sockObj.user) { + numGuests++; + continue; + } - if (sockObj == sock) continue; + if (sockObj === sock) continue; - if (sockObj.user && sock.user && sockObj.user.data.uid == sock.user.data.uid){ - this.removeUser(sockObj); - sockObj.close(1000, JSON.stringify({ - type: 'ConnectedElsewhere' - })); + if (sockObj.user && sock.user && sockObj.user.data.uid === sock.user.data.uid) { + this.removeUser(sockObj); + sockObj.close(1000, JSON.stringify({ + type: 'ConnectedElsewhere' + })); + } } - } - }else{ - for (var i = 0; i < this.attendeeList.length; i++){ - var sockObj = this.attendeeList[i]; + } else { + for (let i = 0; i < this.attendeeList.length; i++) { + const sockObj = this.attendeeList[i]; - if (!sockObj.user){ - numGuests++; + if (!sockObj.user) { + numGuests++; + } } } - } - //TODO: Find and add role key to user object from room db - - this.sendAll({ - type: 'userJoined', - data: { - user: userSend, - guests: numGuests - } - }, - function(sockObj){ - return sockObj != sock; - }); + // TODO: Find and add role key to user object from room db -}; + this.sendAll({ + type: 'userJoined', + data: { + user: userSend, + guests: numGuests + } + }, + sockObj => sockObj !== sock); + } -Room.prototype.replaceUser = function( sock_old, sock_new ){ - if (!sock_old || !sock_old.user || !sock_new || !sock_new.user || sock_old.user.data.uid != sock_new.user.data.uid) return false; - var ind = this.attendeeList.indexOf(sock_old); - this.checkMakeOwner(); + replaceUser(sockOld, sockNew) { + if (!sockOld || !sockOld.user || !sockNew || !sockNew.user || sockOld.user.data.uid !== sockNew.user.data.uid) return false; + const ind = this.attendeeList.indexOf(sockOld); + this.checkMakeOwner(); + if (ind === -1) return false; - if (ind == -1 ) return false; + sockNew.room = this.roomInfo.slug; + sockNew.user.data.role = this.findRole(sockOld.user.data.uid); + this.attendeeList[ind] = sockNew; + this.queue.replaceSocket(sockOld, sockNew); - sock_new.room = this.roomInfo.slug; + return true; + } - sock_new.user.data.role = this.findRole(sock_old.user.data.uid); + removeUser(sock) { + const that = this; + const ind = this.attendeeList.indexOf(sock); - this.attendeeList[ind] = sock_new; + if (ind > -1) { + sock.room = null; - this.queue.replaceSocket(sock_old, sock_new); + let userSend = null; - return true; -}; + this.queue.remove(sock); -Room.prototype.removeUser = function( sock ){ - var that = this; - var ind = this.attendeeList.indexOf(sock); + if (sock.user) { + userSend = sock.user.getClientObj(); + sock.user.data.role = null; + } - if (ind > -1) { - sock.room = null; + this.attendeeList.splice(ind, 1); - var userSend = null; + this.sendAll({ + type: 'userLeft', + data: { + user: userSend, + guests: ((() => { + let num = 0; + for (let i = 0; i < that.attendeeList.length; i++) { + if (!that.attendeeList[i].user) num++; + } + return num; + }))() + } + }); + } + } - this.queue.remove( sock ); + restrictUser(restrictObj, callback) { + /* + Expects { + restrictObj: { + uid: uid, + end: int, + start: int, + reason: '', + type: '', + source: { + uid: uid, + role: role + } + } + } + */ + const that = this; - if (sock.user) { - userSend = sock.user.getClientObj(); - sock.user.data.role = null; + if (this.restrictiontypes.indexOf(restrictObj.type) === -1) { + if (callback) callback('InvalidRestrictionType'); + return; } - this.attendeeList.splice( ind, 1 ); - - this.sendAll({ - type: 'userLeft', - data: { - user: userSend, - guests: (function(){ - var num = 0; - for (var i = 0; i < that.attendeeList.length; i++){ - if (!that.attendeeList[i].user) num++; - } - return num; - })() + DB.getUserByUid(restrictObj.uid, (err, user) => { + if (err) { + if (callback) { + callback(err); + } + return; } - }); - } -}; -Room.prototype.restrictUser = function(restrictObj, callback){ - /* - Expects { - restrictObj: { - uid: uid, - end: int, - start: int, - reason: '', - type: '', - source: { - uid: uid, - role: role + if (that.isUserRestricted(restrictObj.uid, restrictObj.type)) { + if (callback) callback('UserAlreadyRestricted'); + return; } - } - } - */ - var that = this; - - if(this.restrictiontypes.indexOf(restrictObj.type) == -1) - { - if (callback) callback("InvalidRestrictionType"); - return; - } - - DB.getUserByUid(restrictObj.uid, function(err, user) { - if (err) { - if (callback) - callback(err); - return; - } - - if (that.isUserRestricted(restrictObj.uid, restrictObj.type)){ - if (callback) callback('UserAlreadyRestricted'); - return; - } - user.role = that.findRole(user.uid); - - if (!Roles.checkCanGrant(restrictObj.source.role, [user.role])) { - if (callback) callback('UserCannotBeRestricted'); - return; - } - - restrictObj.reason = restrictObj.reason.substr(0, 50); - - that.data.restrictions[restrictObj.uid] = that.data.restrictions[restrictObj.uid] || {}; - that.data.restrictions[restrictObj.uid][restrictObj.type] = restrictObj; - that.save(); - - that.sendAll({ - type: 'userRestricted', - data: { - uid: restrictObj.uid, - type: restrictObj.type, - source: restrictObj.source.uid, + user.role = that.findRole(user.uid); + + if (!Roles.checkCanGrant(restrictObj.source.role, [user.role])) { + if (callback) callback('UserCannotBeRestricted'); + return; } - }, function(obj){ - return restrictObj.type != 'SILENT_MUTE' || (obj.user && Roles.checkPermission(obj.user.role, 'room.restrict.silent_mute')); - }); - var userSock = that.findSocketByUid(restrictObj.uid); + restrictObj.reason = restrictObj.reason.substr(0, 50); + + that.data.restrictions[restrictObj.uid] = that.data.restrictions[restrictObj.uid] || {}; + that.data.restrictions[restrictObj.uid][restrictObj.type] = restrictObj; + that.save(); - //Check if user is online - if (userSock && restrictObj.type == 'BAN'){ - that.removeUser(userSock); - userSock.close(1000, JSON.stringify({ - type: 'banned', + that.sendAll({ + type: 'userRestricted', data: { - end: restrictObj.end, - reason: restrictObj.reason + uid: restrictObj.uid, + type: restrictObj.type, + source: restrictObj.source.uid, } - })); - } - - if (callback) callback(null); - }); -}; + }, obj => restrictObj.type !== 'SILENT_MUTE' || (obj.user && Roles.checkPermission(obj.user.role, 'room.restrict.silent_mute'))); + + const userSock = that.findSocketByUid(restrictObj.uid); + + // Check if user is online + if (userSock && restrictObj.type === 'BAN') { + that.removeUser(userSock); + userSock.close(1000, JSON.stringify({ + type: 'banned', + data: { + end: restrictObj.end, + reason: restrictObj.reason + } + })); + } -Room.prototype.getRestrictions = function(arr, uid){ - var out = {}; - - for(var key in this.data.restrictions[uid]){ - if(key.indexOf(arr)) - out[key] = this.data.restrictions[uid][key]; + if (callback) callback(null); + }); } - - return out; -}; -Room.prototype.unrestrictUser = function(uid, type, sock){ - if (this.data.restrictions[uid][type]){ - delete this.data.restrictions[uid][type]; - this.save(); + getRestrictions(arr, uid) { + const out = {}; - this.sendAll({ - type: 'userUnrestricted', - data: { - uid: uid, - type: type, - source: (sock ? sock.user.data.uid : null) - } - }, function(obj){ - return type != 'SILENT_MUTE' || (obj.user && Roles.checkPermission(obj.user.role, 'room.restrict.silent_mute')); - }); + for (const key in this.data.restrictions[uid]) { + if (key.indexOf(arr)) + out[key] = this.data.restrictions[uid][key]; + } - return true; + return out; } - return false; -}; -Room.prototype.isUserRestricted = function(uid, type){ - if ((this.data.restrictions[uid] || {})[type]) { - if (this.data.restrictions[uid][type].end < new Date(Date.now())) { - this.unrestrictUser(uid, type); - return false; + unrestrictUser(uid, type, sock) { + if (this.data.restrictions[uid][type]) { + delete this.data.restrictions[uid][type]; + this.save(); + + this.sendAll({ + type: 'userUnrestricted', + data: { + uid, + type, + source: (sock ? sock.user.data.uid : null) + } + }, obj => type !== 'SILENT_MUTE' || (obj.user && Roles.checkPermission(obj.user.role, 'room.restrict.silent_mute'))); + + return true; } - else { + return false; + } + + isUserRestricted(uid, type) { + if ((this.data.restrictions[uid] || {})[type]) { + if (this.data.restrictions[uid][type].end < new Date(Date.now())) { + this.unrestrictUser(uid, type); + return false; + } return true; } + return false; } - return false; -}; -Room.prototype.setRole = function(user, role){ - if (!user) return false; + setRole(user, role) { + if (!user) return false; - if (!role) role = 'default'; + if (!role) role = 'default'; - role = role.toLowerCase(); + role = role.toLowerCase(); - if (Roles.roleExists(role)){ - if (typeof this.data.roles[role] === 'undefined') this.data.roles[role] = []; + if (Roles.roleExists(role)) { + if (typeof this.data.roles[role] === 'undefined') this.data.roles[role] = []; - var userSock = this.findSocketByUid(user.uid); + const userSock = this.findSocketByUid(user.uid); - // Remove user from other role - this.removeRole(user); + // Remove user from other role + this.removeRole(user); - if (role != 'default') - this.data.roles[role].push(user.uid); + if (role !== 'default') this.data.roles[role].push(user.uid); - user.role = role; + user.role = role; - - // Save the changes - this.save(); + // Save the changes + this.save(); - if (userSock){ - // We can't assign this user object to the socket because it lacks playlists - userSock.user.data.role = role; - } + if (userSock) { + // We can't assign this user object to the socket because it lacks playlists + userSock.user.data.role = role; + } - this.sendUserUpdate(user); + this.sendUserUpdate(user); - return true; + return true; + } + return false; } - return false; -}; + removeRole(user) { + if (!user) return; -Room.prototype.removeRole = function(user){ - if (!user) return; - - for (var i in this.data.roles){ - var ind = this.data.roles[i].indexOf(user.uid); - if ( ind > -1){ - this.data.roles[i].splice(ind, 1); + for (const i in this.data.roles) { + const ind = this.data.roles[i].indexOf(user.uid); + if (ind > -1) { + this.data.roles[i].splice(ind, 1); + } } } -}; -Room.prototype.findRole = function(uid){ - if (!uid) return 'default'; + findRole(uid) { + if (!uid) return 'default'; - for (var i in this.data.roles){ - var ind = this.data.roles[i].indexOf(uid); - if ( ind > -1 && Roles.roleExists(i) ){ - return i; + for (const i in this.data.roles) { + const ind = this.data.roles[i].indexOf(uid); + if (ind > -1 && Roles.roleExists(i)) { + return i; + } } + + return 'default'; } - return 'default'; -}; + findSocketByUid(uid) { + for (const i in this.attendeeList) { + if (!this.attendeeList[i].user) continue; -Room.prototype.findSocketByUid = function( uid ){ + if (this.attendeeList[i].user.data.uid === uid) return this.attendeeList[i]; + } - for (var i in this.attendeeList){ - if (!this.attendeeList[i].user) continue; + return null; + } - if (this.attendeeList[i].user.data.uid == uid) return this.attendeeList[i]; + getAttendees() { + return this.attendeeList; } - return null; -}; + getBannedUsers(callback) { + const banned = []; + const rawBanned = []; + const that = this; -Room.prototype.getAttendees = function(){ - return this.attendeeList; -}; + for (const i in this.data.restrictions) { + // This will unban appropriately when the list is viewed. + if (this.isUserRestricted(i, 'BAN')) + rawBanned.push(i); + } -Room.prototype.getBannedUsers = function(callback){ - var banned = []; - var rawBanned = []; - var that = this; - - for (var i in this.data.restrictions){ - // This will unban appropriately when the list is viewed. - if (this.isUserRestricted(i, 'BAN')) - rawBanned.push(i); - } + if (!rawBanned.length) { + callback('NoBans'); + return; + } + + DB.getUserByUid(rawBanned, { getPlaylists: false }, (err, users) => { + for (const j in users) { + const usr = users[j].getClientObj(); + usr.role = that.findRole(usr.uid); + banned.push(usr); + } - if (!rawBanned.length){ - callback('NoBans'); - return; + callback(err, banned); + }); } - DB.getUserByUid(rawBanned, {getPlaylists: false}, function (err, users) { - for (var j in users){ - var usr = users[j].getClientObj(); - usr.role = that.findRole(usr.uid); - banned.push(usr); + getRoomStaff(callback) { + const staff = []; + let rawStaff = []; + const that = this; + + for (const i in this.data.roles) { + if (Roles.getStaffRoles().indexOf(i) > -1) { + rawStaff = rawStaff.concat(this.data.roles[i]); + } } - callback(err, banned); - }); -}; + if (!rawStaff.length) { + callback('NoStaff'); + return; + } -Room.prototype.getRoomStaff = function(callback){ - var staff = []; - var rawStaff = []; - var that = this; + DB.getUserByUid(rawStaff, { getPlaylists: false }, (err, users) => { + for (const j in users) { + const usr = users[j].getClientObj(); + usr.role = that.findRole(usr.uid); + staff.push(usr); + } - for (var i in this.data.roles){ - if (Roles.getStaffRoles().indexOf(i) > -1) { - rawStaff = rawStaff.concat(this.data.roles[i]); - } + callback(err, staff); + }); } - if (!rawStaff.length){ - callback('NoStaff'); - return; + sendSystemMessage(message) { + this.sendAll({ type: 'systemMessage', data: message }); } - DB.getUserByUid(rawStaff, { getPlaylists: false }, function (err, users) { - for (var j in users){ - var usr = users[j].getClientObj(); - usr.role = that.findRole(usr.uid); - staff.push(usr); - } + sendBroadcastMessage(message) { + this.sendAll({ type: 'broadcastMessage', data: message }); + } - callback(err, staff); - }); -}; + sendMessage(sock, message, ext, specdata, callback) { + const that = this; -Room.prototype.sendSystemMessage = function(message) { - this.sendAll({type:'systemMessage', data:message}); -}; + message = message.substring(0, 255).replace(//g, '>'); -Room.prototype.sendBroadcastMessage = function(message) { - this.sendAll({type:'broadcastMessage', data:message}); -}; + callback = callback || (() => {}); -Room.prototype.sendMessage = function( sock, message, ext, specdata, callback ){ - var that = this; + if (this.isUserRestricted(sock.user.uid, 'SILENT_MUTE')) { + DB.logChat(sock.user.uid, message, 'res:mute_s', (err, cid) => { + sock.sendJSON({ + type: 'chat', + data: { + uid: sock.user.uid, + message, + time: Date.now(), + cid, + special: specdata, + } + }); + callback(cid); + }); + } else if (this.isUserRestricted(sock.user.uid, 'MUTE')) { + callback(null); + } else { + DB.logChat(sock.user.uid, message, specdata, (err, cid) => { + that.sendAll({ + type: 'chat', + data: { + uid: sock.user.uid, // Will always be present. Unauthd can't send messages + message, + time: Date.now(), + cid, + special: specdata + } + }, obj => { + // Guests can't see chat with config variable set + if (!that.roomInfo.guestCanSeeChat && !obj.user) return false; - message = message.substring(0,255).replace(//g, '>'); + // Banned users can't see chat with config variable set + if (!that.roomInfo.bannedCanSeeChat && obj.user && that.isUserRestricted(obj.user.uid, 'BAN')) return false; - callback = callback || function(){}; - - if(this.isUserRestricted(sock.user.uid, 'SILENT_MUTE')){ - DB.logChat(sock.user.uid, message, 'res:mute_s', function(err, cid){ - sock.sendJSON({ - type: 'chat', - data: { - uid: sock.user.uid, - message: message, - time: Date.now(), - cid: cid, - special: specdata, - } - }); - callback(cid); - }); - } else if(this.isUserRestricted(sock.user.uid, 'MUTE')){ - callback(null); - } else { - DB.logChat(sock.user.uid, message, specdata, function(err, cid){ - that.sendAll({ - type: 'chat', - data: { - uid: sock.user.uid, // Will always be present. Unauthd can't send messages - message: message, - time: Date.now(), - cid: cid, - special: specdata - } - }, function(obj){ - // Guests can't see chat with config variable set - if (!that.roomInfo.guestCanSeeChat && !obj.user) return false; - - // Banned users can't see chat with config variable set - if (!that.roomInfo.bannedCanSeeChat && obj.user && that.isUserRestricted(obj.user.uid, 'BAN')) return false; - - // Check for extensive function - if("function" === typeof ext) if(!ext(obj)) return false; - - return true; - }); - - //Save last X messages to show newly connected users - if(!specdata){ - that.lastChat.push({ - user: sock.user.getClientObj(), - message: message, - time: Date.now(), - cid: cid, + // Check for extensive function + if (typeof ext === 'function') if (!ext(obj)) return false; + + return true; }); - if(that.lastChat.length > nconf.get('room:lastmsglimit')) that.lastChat.shift(); - } - - callback(cid); - }); - } -}; -Room.prototype.makePrevChatObj = function(){ - var uids = []; - var temp = extend(true, [], this.lastChat); + // Save last X messages to show newly connected users + if (!specdata) { + that.lastChat.push({ + user: sock.user.getClientObj(), + message, + time: Date.now(), + cid, + }); + if (that.lastChat.length > nconf.get('room:lastmsglimit')) that.lastChat.shift(); + } - for (var i = 0; i < temp.length; i++){ - var ind = uids.indexOf(temp[i].user.uid); - if ( ind == -1 ){ - uids.push( temp[i].user.uid ); - continue; + callback(cid); + }); } - - temp[i].user = { uid: temp[i].user.uid }; } - return temp; -}; + makePrevChatObj() { + const uids = []; + const temp = extend(true, [], this.lastChat); -Room.prototype.deleteChat = function(cid, uid){ - for (var i = 0; i < this.lastChat.length; i++){ - if (this.lastChat[i].cid == cid){ - this.lastChat.splice(i, 1); - break; - } - } + for (let i = 0; i < temp.length; i++) { + const ind = uids.indexOf(temp[i].user.uid); + if (ind === -1) { + uids.push(temp[i].user.uid); + continue; + } - this.sendAll({ - type: 'deleteChat', - data: { - cid: cid, - mid : uid + temp[i].user = { uid: temp[i].user.uid }; } - }); -}; -Room.prototype.sendAll = function (message, condition){ - condition = condition || function(){return true;}; - for (var i in this.attendeeList){ - var obj = this.attendeeList[i]; + return temp; + } - if (obj.readyState != ws.OPEN || !condition(obj)) continue; + deleteChat(cid, uid) { + for (let i = 0; i < this.lastChat.length; i++) { + if (this.lastChat[i].cid === cid) { + this.lastChat.splice(i, 1); + break; + } + } - obj.sendJSON(message); + this.sendAll({ + type: 'deleteChat', + data: { + cid, + mid: uid + } + }); } -}; -Room.prototype.sendUserUpdate = function(user){ - if (!user) return; + sendAll(message, condition) { + if (!condition) condition = () => true; + for (const i in this.attendeeList) { + const obj = this.attendeeList[i]; - this.sendAll({ - type: 'userUpdate', - data: { - user: user.getClientObj() - } - }); -}; - -Room.prototype.getUsersObj = function(){ - var temp = { - guests: 0, - users: {} - }; - var guestCounter = 0; + if (obj.readyState !== ws.OPEN || !condition(obj)) continue; - for (var i = 0; i < this.attendeeList.length; i++){ - var obj = this.attendeeList[i]; - if (!obj.user){ - temp.guests++; - continue; + obj.sendJSON(message); } + } + + sendUserUpdate(user) { + if (!user) return; - temp.users[ obj.user.uid ] = obj.user.getClientObj(); + this.sendAll({ + type: 'userUpdate', + data: { + user: user.getClientObj() + } + }); } - return temp; -}; + getUsersObj() { + const temp = { + guests: 0, + users: {} + }; -Room.prototype.getHistoryObj = function() { - return this.data.history.slice(-nconf.get('room:history:limit_send')).reverse(); -}; + for (let i = 0; i < this.attendeeList.length; i++) { + const obj = this.attendeeList[i]; + if (!obj.user) { + temp.guests++; + continue; + } -Room.prototype.addToHistory = function(historyObj) { - //Limit history - if(nconf.get('room:history:limit_save') !== 0) - while (this.data.history.length >= nconf.get('room:history:limit_save')) { - this.data.history.shift(); + temp.users[obj.user.uid] = obj.user.getClientObj(); } - //Add to history and save - this.data.history.push(historyObj); - this.save(); -}; + return temp; + } -Room.prototype.updateLobbyServer = function(song, dj, callback) { - if (!nconf.get('apis:musiqpad:sendLobbyStats')) { - if (callback) callback(); - return; + getHistoryObj() { + return this.data.history.slice(-nconf.get('room:history:limit_send')).reverse(); } - else if (!nconf.get('apis:musiqpad:key') || nconf.get('apis:musiqpad:key') == "") { - console.log("A musiqpad key must be defined in the config for updating the lobby server."); - return; + + addToHistory(historyObj) { + // Limit history + if (nconf.get('room:history:limit_save') !== 0) { + while (this.data.history.length >= nconf.get('room:history:limit_save')) { + this.data.history.shift(); + } + } + + // Add to history and save + this.data.history.push(historyObj); + this.save(); } - var postData = { - song: song, - dj: dj, - room: this.getRoomMeta(), - userCount: this.attendeeList.length - }; - var postOptions = { - host: 'api.musiqpad.com', - port: '443', - path: '/pad/' + this.roomInfo.slug, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'apikey': nconf.get('apis:musiqpad:key') - } - }; - try { - var postReq = https.request(postOptions, function (response) { - if (response.statusCode < 200 || response.statusCode > 299) { - console.log('Request Failed with Status Code: ' + response.statusCode); - } - if (callback) callback(); - }); - postReq.write(JSON.stringify(postData)); - postReq.on('error', function() { + + updateLobbyServer(song, dj, callback) { + if (!nconf.get('apis:musiqpad:sendLobbyStats')) { + if (callback) callback(); + return; + } else if (!nconf.get('apis:musiqpad:key') || nconf.get('apis:musiqpad:key') === '') { + console.log('A musiqpad key must be defined in the config for updating the lobby server.'); + return; + } + const postData = { + song, + dj, + room: this.getRoomMeta(), + userCount: this.attendeeList.length + }; + const postOptions = { + host: 'api.musiqpad.com', + port: '443', + path: `/pad/${this.roomInfo.slug}`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + apikey: nconf.get('apis:musiqpad:key') + } + }; + try { + const postReq = https.request(postOptions, response => { + if (response.statusCode < 200 || response.statusCode > 299) { + console.log(`Request Failed with Status Code: ${response.statusCode}`); + } + if (callback) callback(); + }); + postReq.write(JSON.stringify(postData)); + postReq.on('error', () => { + postReq.end(); + console.log('Lobby Update errored.'); + }); + postReq.setTimeout(3000, () => { + console.log('Lobby Update timed out.'); + postReq.abort(); + }); postReq.end(); - console.log('Lobby Update errored.'); - }); - postReq.setTimeout(3000, function() { - console.log('Lobby Update timed out.'); - postReq.abort(); - }); - postReq.end(); - } - catch (e) { + } catch (e) { } + this.createApiTimeout(); } - this.createApiTimeout(); -}; - -Room.prototype.createApiTimeout = function() { - var that = this; - clearTimeout(this.apiUpdateTimeout); + createApiTimeout() { + const that = this; + clearTimeout(this.apiUpdateTimeout); - this.apiUpdateTimeout = setTimeout(function() { - if (that.queue.currentsong && that.queue.currentdj) { - that.updateLobbyServer(that.queue.currentsong, that.queue.currentdj ? that.queue.currentdj.user.getClientObj() : null); - } - else { - that.updateLobbyServer(null, null); - } - }, 300000); - return this.apiUpdateTimeout; -}; + this.apiUpdateTimeout = setTimeout(() => { + if (that.queue.currentsong && that.queue.currentdj) { + that.updateLobbyServer(that.queue.currentsong, that.queue.currentdj ? that.queue.currentdj.user.getClientObj() : null); + } else { + that.updateLobbyServer(null, null); + } + }, 300000); + return this.apiUpdateTimeout; + } -Room.prototype.sockIsJoined = function(sock){ - if (this.attendeeList.indexOf(sock) > -1) - return true; - return false; -}; + sockIsJoined(sock) { + if (this.attendeeList.indexOf(sock) > -1) return true; + return false; + } -Room.prototype.makeDbObject = function(){ - return this.data; -}; + makeDbObject() { + return this.data; + } -Room.prototype.save = function(){ - DB.setRoom(this.roomInfo.slug, this.makeDbObject()); -}; + save() { + DB.setRoom(this.roomInfo.slug, this.makeDbObject()); + } -Room.prototype.checkMakeOwner = function() { - if (this.data.roles.owner && this.data.roles.owner.length == 0) { - this.makeOwner(); + checkMakeOwner() { + if (this.data.roles.owner && this.data.roles.owner.length === 0) { + this.makeOwner(); + } } } diff --git a/socketserver/socketserver.js b/socketserver/socketserver.js index 78bd26e..55cb67d 100644 --- a/socketserver/socketserver.js +++ b/socketserver/socketserver.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line 'use strict'; //Modules var ws = require('ws'); diff --git a/webserver/app.js b/webserver/app.js index 0cbe913..c0c4ea4 100644 --- a/webserver/app.js +++ b/webserver/app.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line 'use strict'; const express = require('express'); const compression = require('compression'); @@ -12,83 +11,84 @@ const ejs = require('ejs'); const app = express(); let server2 = null; let server = null; +// eslint-disable-next-line let socketServer = null; - /* SSL */ if (nconf.get('useSSL') && nconf.get('certificate') && nconf.get('certificate:key') && nconf.get('certificate:cert')) { - const certificate = { - key: fs.readFileSync(nconf.get('certificate:key')), - cert: fs.readFileSync(nconf.get('certificate:cert')), - }; + const certificate = { + key: fs.readFileSync(nconf.get('certificate:key')), + cert: fs.readFileSync(nconf.get('certificate:cert')), + }; - server = https.createServer(certificate, app); - if (nconf.get('webServer:redirectHTTP') && nconf.get('webServer:redirectPort') !== '') { - server2 = http.createServer(app); - } + server = https.createServer(certificate, app); + if (nconf.get('webServer:redirectHTTP') && nconf.get('webServer:redirectPort') !== '') { + server2 = http.createServer(app); + } +} else { + server = http.createServer(app); } -else { - server = http.createServer(app); + +if (nconf.get('webServer:redirectHTTP')) { + app.use((req, res, next) => { + if (!req.secure) { + return res.redirect(['https://', req.hostname, ':', nconf.get('webServer:port') || process.env.PORT, req.url].join('')); + } + next(); + }); } app.set('view engine', 'html'); +app.set('views', `${__dirname}/public`); app.engine('html', ejs.renderFile); -app.set('views', __dirname + '/public'); app.use(compression()); -if (nconf.get('webServer:redirectHTTP')) { - app.use((req, res, next) => { - if (!req.secure) { - return res.redirect(['https://', req.hostname, ':', nconf.get('webServer:port') || process.env.PORT, req.url].join('')); - } - next(); - }); -} - app.get(['/', '/index.html'], (req, res) => { - res.render('index', { - tags: nconf.get('room:tags'), - room: nconf.get('room'), - }); + res.render('index', { + tags: nconf.get('room:tags'), + room: nconf.get('room'), + scripts: nconf.get('room:scripts'), + }); }); -app.use(express.static(path.resolve(__dirname, 'public'))); + app.use('/pads', express.static(path.resolve(__dirname, 'public'))); +app.use(express.static(path.resolve(__dirname, 'public'))); + app.get('/config', (req, res) => { - res.setHeader('Content-Type', 'application/javascript'); - res.send(fs.readFileSync(__dirname + '/public/lib/js/webconfig.js')); + res.setHeader('Content-Type', 'application/javascript'); + res.send(fs.readFileSync(`${__dirname}/public/lib/js/webconfig.js`)); }); app.get('/api/room', (req, res) => { - const roomInfo = { - slug: nconf.get('room:slug'), - name: nconf.get('room:name'), - people: null, - queue: null, - media: null, - }; - res.send(roomInfo); + const roomInfo = { + slug: nconf.get('room:slug'), + name: nconf.get('room:name'), + people: null, + queue: null, + media: null, + }; + res.send(roomInfo); }); -server.listen(nconf.get('webServer:port') || process.env.PORT, nconf.get('webServer:address') || process.env.IP, function(){ - const addr = server.address(); - console.log('Webserver listening at', addr.address + ':' + addr.port); +server.listen(nconf.get('webServer:port') || process.env.PORT, nconf.get('webServer:address') || process.env.IP, () => { + const addr = server.address(); + console.log('Webserver listening at', `${addr.address}:${addr.port}`); }); if (server2 != null) { - server2.listen(nconf.get('webServer:redirectPort') || 80, nconf.get('webServer:address') || process.env.IP, function(){ - const addr2 = server2.address(); - console.log('HTTP Webserver listening at', addr2.address + ':' + addr2.port); - }); + server2.listen(nconf.get('webServer:redirectPort') || 80, nconf.get('webServer:address') || process.env.IP, () => { + const addr2 = server2.address(); + console.log('HTTP Webserver listening at', `${addr2.address}:${addr2.port}`); + }); } -const setSocketServer = function (ss) { - socketServer = ss; +const setSocketServer = ss => { + socketServer = ss; }; - module.exports = { - app, - server, - server2, - setSocketServer, + app, + server, + server2, + setSocketServer, }; diff --git a/webserver/public/index.html b/webserver/public/index.html index ce04284..902e5bd 100644 --- a/webserver/public/index.html +++ b/webserver/public/index.html @@ -4,7 +4,7 @@ - <% if (tags) { %> + <% if (tags) { -%> @@ -13,10 +13,15 @@ - <% } else { %> - <% } %> + <% } else { -%> + <% } -%> <%- room.name -%> + <% if (scripts.css) { -%> + <% scripts.css.forEach(function(url){ -%> + + <% }); -%> + <% } -%> @@ -757,5 +762,10 @@ + <% if (scripts.js) { -%> + <% scripts.js.forEach(function(url){ -%> + + <% }); -%> + <% } -%>