diff --git a/package.json b/package.json index f09f22da..668db489 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.34.9", + "version": "3.34.10", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/src/channel/opts.js b/src/channel/opts.js index 46da8a83..70c459da 100644 --- a/src/channel/opts.js +++ b/src/channel/opts.js @@ -31,7 +31,8 @@ function OptionsModule(channel) { allow_ascii_control: false,// Allow ASCII control characters (\x00-\x1f) playlist_max_per_user: 0, // Maximum number of playlist items per user new_user_chat_delay: 0, // Minimum account/IP age to chat - new_user_chat_link_delay: 0 // Minimum account/IP age to post links + new_user_chat_link_delay: 0, // Minimum account/IP age to post links + playlist_max_duration_per_user: 0 // Maximum total playlist time per user }; } @@ -171,6 +172,30 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { sendUpdate = true; } + if ("playlist_max_duration_per_user" in data) { + const rawMax = data.playlist_max_duration_per_user; + if (typeof rawMax !== "string" || !rawMax.match(/^(\d+:)*\d+$/)) { + user.socket.emit("validationError", { + target: "#cs-playlist_max_duration_per_user", + message: `Input must be a time in the format HH:MM:SS, not "${rawMax}"` + }); + } else { + const max = Utilities.parseTime(rawMax); + if (isNaN(max) || max < 0) { + user.socket.emit("validationError", { + target: "#cs-playlist_max_duration_per_user", + message: `Input must be a time greater than 0 in the format HH:MM:SS, not "${rawMax}"` + }); + } else { + this.opts.playlist_max_duration_per_user = max; + sendUpdate = true; + user.socket.emit("validationPassed", { + target: "#cs-playlist_max_duration_per_user" + }); + } + } + } + if ("externalcss" in data && user.account.effectiveRank >= 3) { var prefix = "Invalid URL for external CSS: "; if (typeof data.externalcss !== "string") { diff --git a/src/channel/permissions.js b/src/channel/permissions.js index d85774b8..9762c652 100644 --- a/src/channel/permissions.js +++ b/src/channel/permissions.js @@ -42,7 +42,8 @@ const DEFAULT_PERMISSIONS = { chat: 0, // Send chat messages chatclear: 2, // Use the /clear command exceedmaxitems: 2, // Exceed maximum items per user limit - deletefromchannellib: 2 // Delete channel library items + deletefromchannellib: 2, // Delete channel library items + exceedmaxdurationperuser: 2 // Exceed maximum total playlist length per user }; function PermissionsModule(channel) { @@ -224,6 +225,10 @@ PermissionsModule.prototype.canExceedMaxLength = function (account) { return this.hasPermission(account, "exceedmaxlength"); }; +PermissionsModule.prototype.canExceedMaxDurationPerUser = function (account) { + return this.hasPermission(account, "exceedmaxdurationperuser"); +}; + PermissionsModule.prototype.canShufflePlaylist = function (account) { return this.hasPermission(account, "playlistshuffle"); }; diff --git a/src/channel/playlist.js b/src/channel/playlist.js index d1c7211a..9c6fac27 100644 --- a/src/channel/playlist.js +++ b/src/channel/playlist.js @@ -922,6 +922,18 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) { } } + if (this.channel.modules.options && + this.channel.modules.options.get("playlist_max_duration_per_user") > 0) { + + const limit = this.channel.modules.options.get("playlist_max_duration_per_user"); + const totalDuration = usersItems.map(item => item.media.seconds).reduce((a, b) => a + b, 0) + media.seconds; + if (isNaN(totalDuration)) { + Logger.errlog.log("playlist_max_duration_per_user check calculated NaN: " + require('util').inspect(usersItems)); + } else if (totalDuration >= limit && !this.channel.modules.permissions.canExceedMaxDurationPerUser(user)) { + return qfail("Channel limit exceeded: maximum total playlist time per user"); + } + } + /* Warn about blocked countries */ if (media.meta.restricted) { user.socket.emit("queueWarn", { diff --git a/templates/channeloptions.pug b/templates/channeloptions.pug index 6b7e69d8..69ffb02b 100644 --- a/templates/channeloptions.pug +++ b/templates/channeloptions.pug @@ -63,6 +63,7 @@ mixin miscoptions +rcheckbox-auto("cs-allow_dupes", "Allow duplicate videos on the playlist") +textbox-auto("cs-voteskip_ratio", "Voteskip ratio", "0.5") +textbox-auto("cs-maxlength", "Max video length", "HH:MM:SS") + +textbox-timeinput-auto("cs-playlist_max_duration_per_user", "Max length of all user's videos", "HH:MM:SS") +textbox-auto("cs-afk_timeout", "Auto-AFK Delay", "0 (disabled)") .form-group .col-sm-offset-4 diff --git a/www/js/util.js b/www/js/util.js index c04bc5c7..f1a66784 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -944,24 +944,10 @@ function handleModPermissions() { $("#cs-torbanned").prop("checked", CHANNEL.opts.torbanned); $("#cs-allow_ascii_control").prop("checked", CHANNEL.opts.allow_ascii_control); $("#cs-playlist_max_per_user").val(CHANNEL.opts.playlist_max_per_user || 0); + $("#cs-playlist_max_duration_per_user").val(formatTime(CHANNEL.opts.playlist_max_duration_per_user)); $("#cs-new_user_chat_delay").val(formatTime(CHANNEL.opts.new_user_chat_delay || 0)); $("#cs-new_user_chat_link_delay").val(formatTime(CHANNEL.opts.new_user_chat_link_delay || 0)); - (function() { - if(typeof CHANNEL.opts.maxlength != "number") { - $("#cs-maxlength").val(""); - return; - } - var h = parseInt(CHANNEL.opts.maxlength / 3600); - h = ""+h; - if(h.length < 2) h = "0" + h; - var m = parseInt((CHANNEL.opts.maxlength % 3600) / 60); - m = ""+m; - if(m.length < 2) m = "0" + m; - var s = parseInt(CHANNEL.opts.maxlength % 60); - s = ""+s; - if(s.length < 2) s = "0" + s; - $("#cs-maxlength").val(h + ":" + m + ":" + s); - })(); + $("#cs-maxlength").val(formatTime(CHANNEL.opts.maxlength)); $("#cs-csstext").val(CHANNEL.css); $("#cs-jstext").val(CHANNEL.js); $("#cs-motdtext").val(CHANNEL.motd); @@ -2009,6 +1995,7 @@ function genPermissionsEditor() { makeOption("Embed custom media", "playlistaddcustom", standard, CHANNEL.perms.playlistaddcustom + ""); makeOption("Add raw video file", "playlistaddrawfile", standard, CHANNEL.perms.playlistaddrawfile + ""); makeOption("Exceed maximum media length", "exceedmaxlength", standard, CHANNEL.perms.exceedmaxlength+""); + makeOption("Exceed maximum total media length", "exceedmaxdurationperuser", standard, CHANNEL.perms.exceedmaxdurationperuser+""); makeOption("Exceed maximum number of videos per user", "exceedmaxitems", standard, CHANNEL.perms.exceedmaxitems+""); makeOption("Add nontemporary media", "addnontemp", standard, CHANNEL.perms.addnontemp+""); makeOption("Temp/untemp playlist item", "settemp", standard, CHANNEL.perms.settemp+"");