diff --git a/lib/channel/opts.js b/lib/channel/opts.js index 27c55d2e..acdde7a0 100644 --- a/lib/channel/opts.js +++ b/lib/channel/opts.js @@ -24,7 +24,8 @@ function OptionsModule(channel) { password: false, // Channel password (false -> no password required for entry) allow_dupes: false, // Allow duplicate videos on the playlist torbanned: false, // Block connections from Tor exit nodes - allow_ascii_control: false // Allow ASCII control characters (\x00-\x1f) + allow_ascii_control: false,// Allow ASCII control characters (\x00-\x1f) + playlist_max_per_user: 0 // Maximum number of playlist items per user }; } @@ -254,6 +255,13 @@ OptionsModule.prototype.handleSetOptions = function (user, data) { this.opts.allow_ascii_control = Boolean(data.allow_ascii_control); } + if ("playlist_max_per_user" in data && user.account.effectiveRank >= 3) { + var max = parseInt(data.playlist_max_per_user); + if (!isNaN(max) && max >= 0) { + this.opts.playlist_max_per_user = max; + } + } + this.channel.logger.log("[mod] " + user.getName() + " updated channel options"); this.sendOpts(this.channel.users); }; diff --git a/lib/channel/permissions.js b/lib/channel/permissions.js index 6a8b4906..12df2800 100644 --- a/lib/channel/permissions.js +++ b/lib/channel/permissions.js @@ -40,7 +40,8 @@ const DEFAULT_PERMISSIONS = { leaderctl: 2, // Give/take leader drink: 1.5, // Use the /d command chat: 0, // Send chat messages - chatclear: 2 // Use the /clear command + chatclear: 2, // Use the /clear command + exceedmaxitems: 2 // Exceed maximum items per user limit }; function PermissionsModule(channel) { @@ -342,6 +343,10 @@ PermissionsModule.prototype.canUncache = function (actor) { return actor.effectiveRank >= 2; }; +PermissionsModule.prototype.canExceedMaxItemsPerUser = function (actor) { + return this.hasPermission(actor, "exceedmaxitems"); +}; + PermissionsModule.prototype.loadUnregistered = function () { var perms = { seeplaylist: -1, @@ -373,7 +378,8 @@ PermissionsModule.prototype.loadUnregistered = function () { leaderctl: 0, // Give/take leader drink: 0, // Use the /d command chat: 0, // Send chat messages - chatclear: 2 // Use the /clear command + chatclear: 2, // Use the /clear command + exceedmaxitems: 2 // Exceed max items per user }; for (var key in perms) { diff --git a/lib/channel/playlist.js b/lib/channel/playlist.js index 3b8e1470..9cb5ba8b 100644 --- a/lib/channel/playlist.js +++ b/lib/channel/playlist.js @@ -868,6 +868,19 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) { return qfail("This item is already on the playlist"); } + var usersItems = this.items.findAll(function (item) { + return item.queueby.toLowerCase() === user.getLowerName(); + }); + + if (this.channel.modules.options && + this.channel.modules.options.get("playlist_max_per_user") && + usersItems.length >= this.channel.modules.options.get("playlist_max_per_user")) { + + if (!this.channel.modules.permissions.canExceedMaxItemsPerUser(user)) { + return qfail("Channel limit exceeded: maximum number of videos per user"); + } + } + /* Warn about blocked countries */ if (media.meta.restricted) { user.socket.emit("queueWarn", { diff --git a/templates/channeloptions.jade b/templates/channeloptions.jade index ef14e827..92a6e95b 100644 --- a/templates/channeloptions.jade +++ b/templates/channeloptions.jade @@ -77,6 +77,10 @@ mixin adminoptions mixin rcheckbox-auto("cs-show_public", "List channel publicly") mixin rcheckbox-auto("cs-torbanned", "Block connections from Tor") mixin rcheckbox-auto("cs-allow_ascii_control", "Allow ASCII control characters (e.g. newlines)") + mixin textbox-auto("cs-playlist_max_per_user", "Maximum # of videos per user") + .form-group + .col-sm-8.col-sm-offset-4 + span.text-info Set to 0 for no limit .form-group .col-sm-8.col-sm-offset-4 span.text-info Changes are automatically saved. diff --git a/www/js/util.js b/www/js/util.js index bb64b15a..b9bde3c5 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -866,6 +866,7 @@ function handleModPermissions() { $("#cs-allow_dupes").prop("checked", CHANNEL.opts.allow_dupes); $("#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); (function() { if(typeof CHANNEL.opts.maxlength != "number") { $("#cs-maxlength").val(""); @@ -1755,6 +1756,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 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+""); makeOption("Lock/unlock playlist", "playlistlock", modleader, CHANNEL.perms.playlistlock+"");