diff --git a/.eslintrc.json b/.eslintrc.json index 4397b6a8e..1723f892b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,8 +21,9 @@ "rules": { "lodash/prefer-lodash-method": [2, { "ignoreMethods": [ - "keys", "find", "endsWith", "startsWith", "filter", "reduce", "isArray", "create", - "map", "replace", "toLower", "split", "trim", "forEach", "toUpperCase", "includes" + "every", "keys", "find", "endsWith", "startsWith", "filter", "reduce", "isArray", + "create", "map", "replace", "some", "toLower", "split", "trim", "forEach", + "toUpperCase", "includes" ] }], "lodash/prefer-invoke-map": "off", diff --git a/CHANGES.md b/CHANGES.md index 66ed74be5..2b947fe16 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - Take roster nickname into consideration when rendering messages and chat headings. - Hide the textarea when a user is muted in a groupchat. - Don't restore a BOSH session without knowing the JID +- In the `/help` menu, only show allowed commands - #1296: `embedded` view mode shows `chatbox-navback` arrow in header - #1532: Converse reloads on enter pressed in the filter box diff --git a/dist/converse.js b/dist/converse.js index 8aed07e89..b67d0be4d 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -53468,6 +53468,10 @@ const _converse$env = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_ $pres = _converse$env.$pres; const u = _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].env.utils; const AFFILIATION_CHANGE_COMANDS = ['admin', 'ban', 'owner', 'member', 'revoke']; +const OWNER_COMMANDS = ['owner']; +const ADMIN_COMMANDS = ['admin', 'ban', 'deop', 'destroy', 'member', 'op', 'revoke']; +const MODERATOR_COMMANDS = ['kick', 'mute', 'voice']; +const VISITOR_COMMANDS = ['nick']; _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins.add('converse-muc-views', { /* Dependencies are other plugins which might be * overridden or relied upon, and therefore need to be loaded before @@ -54385,26 +54389,40 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins return _converse.api.sendIQ(iq).then(onSuccess).catch(onError); }, - verifyRoles(roles) { - const me = this.model.occupants.findWhere({ - 'jid': _converse.bare_jid - }); + verifyRoles(roles, occupant) { + let show_error = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; + + if (!occupant) { + occupant = this.model.occupants.findWhere({ + 'jid': _converse.bare_jid + }); + } + + if (!_.includes(roles, occupant.get('role'))) { + if (show_error) { + this.showErrorMessage(__('Forbidden: you do not have the necessary role in order to do that.')); + } - if (!_.includes(roles, me.get('role'))) { - this.showErrorMessage(__('Forbidden: you do not have the necessary role in order to do that.')); return false; } return true; }, - verifyAffiliations(affiliations) { - const me = this.model.occupants.findWhere({ - 'jid': _converse.bare_jid - }); + verifyAffiliations(affiliations, occupant) { + let show_error = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; + + if (!occupant) { + occupant = this.model.occupants.findWhere({ + 'jid': _converse.bare_jid + }); + } + + if (!_.includes(affiliations, occupant.get('affiliation'))) { + if (show_error) { + this.showErrorMessage(__('Forbidden: you do not have the necessary affiliation in order to do that.')); + } - if (!_.includes(affiliations, me.get('affiliation'))) { - this.showErrorMessage(__('Forbidden: you do not have the necessary affiliation in order to do that.')); return false; } @@ -54454,63 +54472,105 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins switch (command) { case 'admin': - if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { + { + if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.model.setAffiliation('admin', [{ + 'jid': args[0], + 'reason': args[1] + }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); break; } - this.model.setAffiliation('admin', [{ - 'jid': args[0], - 'reason': args[1] - }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); - break; - case 'ban': - if (!this.verifyAffiliations(['owner', 'admin']) || !this.validateRoleChangeCommand(command, args)) { + { + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.model.setAffiliation('outcast', [{ + 'jid': args[0], + 'reason': args[1] + }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); break; } - this.model.setAffiliation('outcast', [{ - 'jid': args[0], - 'reason': args[1] - }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); - break; - case 'deop': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + { + // FIXME: /deop only applies to setting a moderators + // role to "participant" (which only admin/owner can + // do). Moderators can however set non-moderator's role + // to participant (e.g. visitor => participant). + // Currently we don't distinguish between these two + // cases. + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); break; } - this.modifyRole(this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); - break; - case 'destroy': - if (!this.verifyAffiliations(['owner'])) { + { + if (!this.verifyAffiliations(['owner'])) { + break; + } + + this.destroy(this.model.get('jid'), args[0]).then(() => this.close()).catch(e => this.onCommandError(e)); break; } - this.destroy(this.model.get('jid'), args[0]).then(() => this.close()).catch(e => this.onCommandError(e)); - break; - case 'help': - this.showHelpMessages(_.filter([`/admin: ${__("Change user's affiliation to admin")}`, `/ban: ${__('Ban user from groupchat')}`, `/clear: ${__('Remove messages')}`, `/deop: ${__('Change user role to participant')}`, `/destroy: ${__('Remove this groupchat')}`, `/help: ${__('Show this menu')}`, `/kick: ${__('Kick user from groupchat')}`, `/me: ${__('Write in 3rd person')}`, `/member: ${__('Grant membership to a user')}`, `/mute: ${__("Remove user's ability to post messages")}`, `/nick: ${__('Change your nickname')}`, `/op: ${__('Grant moderator role to user')}`, `/owner: ${__('Grant ownership of this groupchat')}`, `/register: ${__("Register a nickname for this groupchat")}`, `/revoke: ${__("Revoke user's membership")}`, `/subject: ${__('Set groupchat subject')}`, `/topic: ${__('Set groupchat subject (alias for /subject)')}`, `/voice: ${__('Allow muted user to post messages')}`], line => _.every(disabled_commands, element => !line.startsWith(element + '<', 9)))); - break; + { + // FIXME: The availability of some of these commands + // depend on the MUCs configuration (e.g. whether it's + // moderated or not). We need to take that into + // consideration. + let allowed_commands = ['clear', 'help', 'me', 'nick', 'subject', 'topic', 'register']; + const occupant = this.model.occupants.findWhere({ + 'jid': _converse.bare_jid + }); + + if (this.verifyAffiliations('owner', occupant, false)) { + allowed_commands = allowed_commands.concat(OWNER_COMMANDS).concat(ADMIN_COMMANDS); + } else if (this.verifyAffiliations('admin', occupant, false)) { + allowed_commands = allowed_commands.concat(ADMIN_COMMANDS); + } + + if (this.verifyRoles('moderator', occupant, false)) { + allowed_commands = allowed_commands.concat(MODERATOR_COMMANDS).concat(VISITOR_COMMANDS); + } else if (!this.verifyRoles(['visitor', 'participant', 'moderator'], occupant, false)) { + allowed_commands = allowed_commands.concat(VISITOR_COMMANDS); + } + + this.showHelpMessages([`${__("You can run the following commands")}`]); + this.showHelpMessages([`/admin: ${__("Change user's affiliation to admin")}`, `/ban: ${__('Ban user from groupchat')}`, `/clear: ${__('Clear the chat area')}`, `/deop: ${__('Change user role to participant')}`, `/destroy: ${__('Remove this groupchat')}`, `/help: ${__('Show this menu')}`, `/kick: ${__('Kick user from groupchat')}`, `/me: ${__('Write in 3rd person')}`, `/member: ${__('Grant membership to a user')}`, `/mute: ${__("Remove user's ability to post messages")}`, `/nick: ${__('Change your nickname')}`, `/op: ${__('Grant moderator role to user')}`, `/owner: ${__('Grant ownership of this groupchat')}`, `/register: ${__("Register your nickname")}`, `/revoke: ${__("Revoke user's membership")}`, `/subject: ${__('Set groupchat subject')}`, `/topic: ${__('Set groupchat subject (alias for /subject)')}`, `/voice: ${__('Allow muted user to post messages')}`].filter(line => disabled_commands.every(c => !line.startsWith(c + '<', 9))).filter(line => allowed_commands.some(c => line.startsWith(c + '<', 9)))); + break; + } case 'kick': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + { + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'none', args[1], undefined, this.onCommandError.bind(this)); break; } - this.modifyRole(this.model.get('jid'), args[0], 'none', args[1], undefined, this.onCommandError.bind(this)); - break; - case 'mute': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + { + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'visitor', args[1], undefined, this.onCommandError.bind(this)); break; } - this.modifyRole(this.model.get('jid'), args[0], 'visitor', args[1], undefined, this.onCommandError.bind(this)); - break; - case 'member': { if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { @@ -54536,18 +54596,20 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins } case 'nick': - if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) { + { + if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) { + break; + } + + _converse.api.send($pres({ + from: _converse.connection.jid, + to: this.model.getRoomJIDAndNick(match[2]), + id: _converse.connection.getUniqueId() + }).tree()); + break; } - _converse.api.send($pres({ - from: _converse.connection.jid, - to: this.model.getRoomJIDAndNick(match[2]), - id: _converse.connection.getUniqueId() - }).tree()); - - break; - case 'owner': if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { break; @@ -54560,34 +54622,40 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins break; case 'op': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + { + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'moderator', args[1], undefined, this.onCommandError.bind(this)); break; } - this.modifyRole(this.model.get('jid'), args[0], 'moderator', args[1], undefined, this.onCommandError.bind(this)); - break; - case 'register': - if (args.length > 1) { - this.showErrorMessage(__('Error: invalid number of arguments')); - } else { - this.model.registerNickname().then(err_msg => { - if (err_msg) this.showErrorMessage(err_msg); - }); - } + { + if (args.length > 1) { + this.showErrorMessage(__('Error: invalid number of arguments')); + } else { + this.model.registerNickname().then(err_msg => { + if (err_msg) this.showErrorMessage(err_msg); + }); + } - break; - - case 'revoke': - if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { break; } - this.model.setAffiliation('none', [{ - 'jid': args[0], - 'reason': args[1] - }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); - break; + case 'revoke': + { + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.model.setAffiliation('none', [{ + 'jid': args[0], + 'reason': args[1] + }]).then(() => this.model.occupants.fetchMembers(), err => this.onCommandError(err)); + break; + } case 'topic': case 'subject': @@ -54603,13 +54671,15 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins break; case 'voice': - if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + { + if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { + break; + } + + this.modifyRole(this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); break; } - this.modifyRole(this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); - break; - default: return _converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments); } diff --git a/spec/muc.js b/spec/muc.js index d54c47b42..f724c3e0b 100644 --- a/spec/muc.js +++ b/spec/muc.js @@ -1781,9 +1781,7 @@ async function (done, _converse) { test_utils.createContacts(_converse, 'current'); // We need roster contacts, who can invite us - spyOn(window, 'confirm').and.callFake(function () { - return true; - }); + spyOn(window, 'confirm').and.callFake(() => true); await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); view.close(); // Hack, otherwise we have to mock stanzas. @@ -2550,23 +2548,24 @@ null, ['rosterGroupsFetched'], {}, async function (done, _converse) { + spyOn(window, 'confirm').and.callFake(() => true); await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); - var textarea = view.el.querySelector('.chat-textarea'); - textarea.value = '/help This is the groupchat subject'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 - }); + const textarea = view.el.querySelector('.chat-textarea'); + textarea.value = '/clear'; - const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); + const enter = { 'target': textarea, 'preventDefault': _.noop, 'keyCode': 13 }; + view.keyPressed(enter); + textarea.value = '/help'; + view.keyPressed(enter); + + let info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); expect(info_messages.length).toBe(19); expect(info_messages.pop().textContent).toBe('/voice: Allow muted user to post messages'); expect(info_messages.pop().textContent).toBe('/topic: Set groupchat subject (alias for /subject)'); expect(info_messages.pop().textContent).toBe('/subject: Set groupchat subject'); expect(info_messages.pop().textContent).toBe('/revoke: Revoke user\'s membership'); - expect(info_messages.pop().textContent).toBe('/register: Register a nickname for this groupchat'); + expect(info_messages.pop().textContent).toBe('/register: Register your nickname'); expect(info_messages.pop().textContent).toBe('/owner: Grant ownership of this groupchat'); expect(info_messages.pop().textContent).toBe('/op: Grant moderator role to user'); expect(info_messages.pop().textContent).toBe('/nick: Change your nickname'); @@ -2577,9 +2576,44 @@ expect(info_messages.pop().textContent).toBe('/help: Show this menu'); expect(info_messages.pop().textContent).toBe('/destroy: Remove this groupchat'); expect(info_messages.pop().textContent).toBe('/deop: Change user role to participant'); - expect(info_messages.pop().textContent).toBe('/clear: Remove messages'); + expect(info_messages.pop().textContent).toBe('/clear: Clear the chat area'); expect(info_messages.pop().textContent).toBe('/ban: Ban user from groupchat'); expect(info_messages.pop().textContent).toBe('/admin: Change user\'s affiliation to admin'); + expect(info_messages.pop().textContent).toBe('You can run the following commands'); + + const occupant = view.model.occupants.findWhere({'jid': _converse.bare_jid}); + occupant.set('affiliation', 'admin'); + textarea.value = '/clear'; + view.keyPressed(enter); + textarea.value = '/help'; + view.keyPressed(enter); + info_messages = sizzle('.chat-info', view.el).slice(1); + expect(info_messages.length).toBe(17); + let commands = info_messages.map(m => m.textContent.replace(/:.*$/, '')); + expect(commands).toEqual([ + "/admin", "/ban", "/clear", "/deop", "/destroy", + "/help", "/kick", "/me", "/member", "/mute", "/nick", + "/op", "/register", "/revoke", "/subject", "/topic", "/voice" + ]); + occupant.set('affiliation', 'member'); + textarea.value = '/clear'; + view.keyPressed(enter); + textarea.value = '/help'; + view.keyPressed(enter); + info_messages = sizzle('.chat-info', view.el).slice(1); + expect(info_messages.length).toBe(10); + commands = info_messages.map(m => m.textContent.replace(/:.*$/, '')); + expect(commands).toEqual(["/clear", "/help", "/kick", "/me", "/mute", "/nick", "/register", "/subject", "/topic", "/voice"]); + + occupant.set('role', 'participant'); + textarea.value = '/clear'; + view.keyPressed(enter); + textarea.value = '/help'; + view.keyPressed(enter); + info_messages = sizzle('.chat-info', view.el).slice(1); + expect(info_messages.length).toBe(7); + commands = info_messages.map(m => m.textContent.replace(/:.*$/, '')); + expect(commands).toEqual(["/clear", "/help", "/me", "/nick", "/register", "/subject", "/topic"]); done(); })); @@ -2599,11 +2633,11 @@ }); const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); - expect(info_messages.length).toBe(17); + expect(info_messages.length).toBe(18); expect(info_messages.pop().textContent).toBe('/topic: Set groupchat subject (alias for /subject)'); expect(info_messages.pop().textContent).toBe('/subject: Set groupchat subject'); expect(info_messages.pop().textContent).toBe('/revoke: Revoke user\'s membership'); - expect(info_messages.pop().textContent).toBe('/register: Register a nickname for this groupchat'); + expect(info_messages.pop().textContent).toBe('/register: Register your nickname'); expect(info_messages.pop().textContent).toBe('/owner: Grant ownership of this groupchat'); expect(info_messages.pop().textContent).toBe('/op: Grant moderator role to user'); expect(info_messages.pop().textContent).toBe('/nick: Change your nickname'); @@ -2613,9 +2647,10 @@ expect(info_messages.pop().textContent).toBe('/help: Show this menu'); expect(info_messages.pop().textContent).toBe('/destroy: Remove this groupchat'); expect(info_messages.pop().textContent).toBe('/deop: Change user role to participant'); - expect(info_messages.pop().textContent).toBe('/clear: Remove messages'); + expect(info_messages.pop().textContent).toBe('/clear: Clear the chat area'); expect(info_messages.pop().textContent).toBe('/ban: Ban user from groupchat'); expect(info_messages.pop().textContent).toBe('/admin: Change user\'s affiliation to admin'); + expect(info_messages.pop().textContent).toBe('You can run the following commands'); done(); })); diff --git a/src/converse-muc-views.js b/src/converse-muc-views.js index a14716d51..84bb5a006 100644 --- a/src/converse-muc-views.js +++ b/src/converse-muc-views.js @@ -41,6 +41,10 @@ import xss from "xss"; const { Backbone, Promise, Strophe, moment, f, sizzle, _, $build, $iq, $msg, $pres } = converse.env; const u = converse.env.utils; const AFFILIATION_CHANGE_COMANDS = ['admin', 'ban', 'owner', 'member', 'revoke']; +const OWNER_COMMANDS = ['owner']; +const ADMIN_COMMANDS = ['admin', 'ban', 'deop', 'destroy', 'member', 'op', 'revoke']; +const MODERATOR_COMMANDS = ['kick', 'mute', 'voice']; +const VISITOR_COMMANDS = ['nick']; converse.plugins.add('converse-muc-views', { /* Dependencies are other plugins which might be @@ -882,19 +886,27 @@ converse.plugins.add('converse-muc-views', { return _converse.api.sendIQ(iq).then(onSuccess).catch(onError); }, - verifyRoles (roles) { - const me = this.model.occupants.findWhere({'jid': _converse.bare_jid}); - if (!_.includes(roles, me.get('role'))) { - this.showErrorMessage(__('Forbidden: you do not have the necessary role in order to do that.')) + verifyRoles (roles, occupant, show_error=true) { + if (!occupant) { + occupant = this.model.occupants.findWhere({'jid': _converse.bare_jid}); + } + if (!_.includes(roles, occupant.get('role'))) { + if (show_error) { + this.showErrorMessage(__('Forbidden: you do not have the necessary role in order to do that.')) + } return false; } return true; }, - verifyAffiliations (affiliations) { - const me = this.model.occupants.findWhere({'jid': _converse.bare_jid}); - if (!_.includes(affiliations, me.get('affiliation'))) { - this.showErrorMessage(__('Forbidden: you do not have the necessary affiliation in order to do that.')) + verifyAffiliations (affiliations, occupant, show_error=true) { + if (!occupant) { + occupant = this.model.occupants.findWhere({'jid': _converse.bare_jid}); + } + if (!_.includes(affiliations, occupant.get('affiliation'))) { + if (show_error) { + this.showErrorMessage(__('Forbidden: you do not have the necessary affiliation in order to do that.')) + } return false; } return true; @@ -938,7 +950,7 @@ converse.plugins.add('converse-muc-views', { return false; } switch (command) { - case 'admin': + case 'admin': { if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { break; } @@ -950,8 +962,9 @@ converse.plugins.add('converse-muc-views', { (err) => this.onCommandError(err) ); break; - case 'ban': - if (!this.verifyAffiliations(['owner', 'admin']) || !this.validateRoleChangeCommand(command, args)) { + } + case 'ban': { + if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { break; } this.model.setAffiliation('outcast', [{ @@ -962,15 +975,23 @@ converse.plugins.add('converse-muc-views', { (err) => this.onCommandError(err) ); break; - case 'deop': + } + case 'deop': { + // FIXME: /deop only applies to setting a moderators + // role to "participant" (which only admin/owner can + // do). Moderators can however set non-moderator's role + // to participant (e.g. visitor => participant). + // Currently we don't distinguish between these two + // cases. if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { break; } this.modifyRole( - this.model.get('jid'), args[0], 'participant', args[1], - undefined, this.onCommandError.bind(this)); + this.model.get('jid'), args[0], 'participant', args[1], + undefined, this.onCommandError.bind(this)); break; - case 'destroy': + } + case 'destroy': { if (!this.verifyAffiliations(['owner'])) { break; } @@ -978,11 +999,29 @@ converse.plugins.add('converse-muc-views', { .then(() => this.close()) .catch(e => this.onCommandError(e)); break; - case 'help': - this.showHelpMessages(_.filter([ + } + case 'help': { + // FIXME: The availability of some of these commands + // depend on the MUCs configuration (e.g. whether it's + // moderated or not). We need to take that into + // consideration. + let allowed_commands = ['clear', 'help', 'me', 'nick', 'subject', 'topic', 'register']; + const occupant = this.model.occupants.findWhere({'jid': _converse.bare_jid}); + if (this.verifyAffiliations('owner', occupant, false)) { + allowed_commands = allowed_commands.concat(OWNER_COMMANDS).concat(ADMIN_COMMANDS); + } else if (this.verifyAffiliations('admin', occupant, false)) { + allowed_commands = allowed_commands.concat(ADMIN_COMMANDS); + } + if (this.verifyRoles('moderator', occupant, false)) { + allowed_commands = allowed_commands.concat(MODERATOR_COMMANDS).concat(VISITOR_COMMANDS); + } else if (!this.verifyRoles(['visitor', 'participant', 'moderator'], occupant, false)) { + allowed_commands = allowed_commands.concat(VISITOR_COMMANDS); + } + this.showHelpMessages([`${__("You can run the following commands")}`]); + this.showHelpMessages([ `/admin: ${__("Change user's affiliation to admin")}`, `/ban: ${__('Ban user from groupchat')}`, - `/clear: ${__('Remove messages')}`, + `/clear: ${__('Clear the chat area')}`, `/deop: ${__('Change user role to participant')}`, `/destroy: ${__('Remove this groupchat')}`, `/help: ${__('Show this menu')}`, @@ -993,15 +1032,16 @@ converse.plugins.add('converse-muc-views', { `/nick: ${__('Change your nickname')}`, `/op: ${__('Grant moderator role to user')}`, `/owner: ${__('Grant ownership of this groupchat')}`, - `/register: ${__("Register a nickname for this groupchat")}`, + `/register: ${__("Register your nickname")}`, `/revoke: ${__("Revoke user's membership")}`, `/subject: ${__('Set groupchat subject')}`, `/topic: ${__('Set groupchat subject (alias for /subject)')}`, `/voice: ${__('Allow muted user to post messages')}` - ], line => (_.every(disabled_commands, element => (!line.startsWith(element+'<', 9)))) - )); + ].filter(line => disabled_commands.every(c => (!line.startsWith(c+'<', 9)))) + .filter(line => allowed_commands.some(c => line.startsWith(c+'<', 9))) + ); break; - case 'kick': + } case 'kick': { if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { break; } @@ -1009,7 +1049,8 @@ converse.plugins.add('converse-muc-views', { this.model.get('jid'), args[0], 'none', args[1], undefined, this.onCommandError.bind(this)); break; - case 'mute': + } + case 'mute': { if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { break; } @@ -1017,6 +1058,7 @@ converse.plugins.add('converse-muc-views', { this.model.get('jid'), args[0], 'visitor', args[1], undefined, this.onCommandError.bind(this)); break; + } case 'member': { if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { break; @@ -1034,7 +1076,8 @@ converse.plugins.add('converse-muc-views', { .then(() => this.model.occupants.fetchMembers()) .catch(err => this.onCommandError(err)); break; - } case 'nick': + } + case 'nick': { if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) { break; } @@ -1044,6 +1087,7 @@ converse.plugins.add('converse-muc-views', { id: _converse.connection.getUniqueId() }).tree()); break; + } case 'owner': if (!this.verifyAffiliations(['owner']) || !this.validateRoleChangeCommand(command, args)) { break; @@ -1056,7 +1100,7 @@ converse.plugins.add('converse-muc-views', { (err) => this.onCommandError(err) ); break; - case 'op': + case 'op': { if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { break; } @@ -1064,7 +1108,8 @@ converse.plugins.add('converse-muc-views', { this.model.get('jid'), args[0], 'moderator', args[1], undefined, this.onCommandError.bind(this)); break; - case 'register': + } + case 'register': { if (args.length > 1) { this.showErrorMessage(__('Error: invalid number of arguments')) } else { @@ -1073,7 +1118,8 @@ converse.plugins.add('converse-muc-views', { }); } break; - case 'revoke': + } + case 'revoke': { if (!this.verifyAffiliations(['admin', 'owner']) || !this.validateRoleChangeCommand(command, args)) { break; } @@ -1085,6 +1131,7 @@ converse.plugins.add('converse-muc-views', { (err) => this.onCommandError(err) ); break; + } case 'topic': case 'subject': // TODO: should be done via API call to _converse.api.rooms @@ -1096,7 +1143,7 @@ converse.plugins.add('converse-muc-views', { }).c("subject", {xmlns: "jabber:client"}).t(match[2] || "").tree() ); break; - case 'voice': + case 'voice': { if (!this.verifyRoles(['moderator']) || !this.validateRoleChangeCommand(command, args)) { break; } @@ -1104,6 +1151,7 @@ converse.plugins.add('converse-muc-views', { this.model.get('jid'), args[0], 'participant', args[1], undefined, this.onCommandError.bind(this)); break; + } default: return _converse.ChatBoxView.prototype.parseMessageForCommands.apply(this, arguments); }