diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 05596deffc..748d941271 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -304,18 +304,18 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) """ try: - @inlineCallbacks - def _get_channel_cmdset(account_or_obj): - """ - Helper-method; Get channel-cmdsets - """ - # Create cmdset for all account's available channels - try: - channel_cmdset = yield CHANNELHANDLER.get_cmdset(account_or_obj) - returnValue([channel_cmdset]) - except Exception: - _msg_err(caller, _ERROR_CMDSETS) - raise ErrorReported(raw_string) + # @inlineCallbacks + # def _get_channel_cmdset(account_or_obj): + # """ + # Helper-method; Get channel-cmdsets + # """ + # # Create cmdset for all account's available channels + # try: + # channel_cmdset = yield CHANNELHANDLER.get_cmdset(account_or_obj) + # returnValue([channel_cmdset]) + # except Exception: + # _msg_err(caller, _ERROR_CMDSETS) + # raise ErrorReported(raw_string) @inlineCallbacks def _get_local_obj_cmdsets(obj): @@ -406,13 +406,13 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet" ] cmdsets += local_obj_cmdsets - if not current.no_channels: - # also objs may have channels - channel_cmdsets = yield _get_channel_cmdset(obj) - cmdsets += channel_cmdsets - if not current.no_channels: - channel_cmdsets = yield _get_channel_cmdset(account) - cmdsets += channel_cmdsets + # if not current.no_channels: + # # also objs may have channels + # channel_cmdsets = yield _get_channel_cmdset(obj) + # cmdsets += channel_cmdsets + # if not current.no_channels: + # channel_cmdsets = yield _get_channel_cmdset(account) + # cmdsets += channel_cmdsets elif callertype == "account": # we are calling the command from the account level @@ -430,11 +430,11 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet" ] cmdsets += local_obj_cmdsets - if not current.no_channels: - # also objs may have channels - cmdsets += yield _get_channel_cmdset(obj) - if not current.no_channels: - cmdsets += yield _get_channel_cmdset(account) + # if not current.no_channels: + # # also objs may have channels + # cmdsets += yield _get_channel_cmdset(obj) + # if not current.no_channels: + # cmdsets += yield _get_channel_cmdset(account) elif callertype == "object": # we are calling the command from the object level @@ -448,9 +448,9 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet" ] cmdsets += yield local_obj_cmdsets - if not current.no_channels: - # also objs may have channels - cmdsets += yield _get_channel_cmdset(obj) + # if not current.no_channels: + # # also objs may have channels + # cmdsets += yield _get_channel_cmdset(obj) else: raise Exception("get_and_merge_cmdsets: callertype %s is not valid." % callertype) diff --git a/evennia/commands/default/cmdset_account.py b/evennia/commands/default/cmdset_account.py index 606af42df2..8167fd21a9 100644 --- a/evennia/commands/default/cmdset_account.py +++ b/evennia/commands/default/cmdset_account.py @@ -60,10 +60,10 @@ class AccountCmdSet(CmdSet): # Comm commands self.add(comms.CmdChannel()) + # self.add(comms.CmdChannels()) self.add(comms.CmdAddCom()) self.add(comms.CmdDelCom()) self.add(comms.CmdAllCom()) - self.add(comms.CmdChannels()) self.add(comms.CmdCdestroy()) self.add(comms.CmdChannelCreate()) self.add(comms.CmdClock()) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index ce4aaf80e3..24a92cc7f6 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -30,7 +30,7 @@ __all__ = ( "CmdAddCom", "CmdDelCom", "CmdAllCom", - #"CmdChannels", + "CmdChannels", "CmdCdestroy", "CmdCBoot", "CmdCemit", @@ -60,10 +60,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): channel/list channel/all channel/history channelname [= index] - channel/sub channelname [= alias] + channel/sub channelname [= alias[;alias...]] channel/unsub channelname[,channelname, ...] - channel/alias channelname = alias - channel/unalias channelname = alias + channel/alias channelname = alias[;alias...] + channel/unalias alias channel/mute channelname[,channelname,...] channel/unmute channelname[,channelname,...] channel/create channelname;alias;alias:typeclass [= description] @@ -87,6 +87,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): "list", "all", "history", "sub", "unsub", "mute", "unmute", "alias", "unalias", "create", "destroy", "desc", "lock", "unlock", "boot", "ban", "unban", "who",) + # note - changing this will invalidate existing aliases in db + channel_msg_nick_alias = "{alias} $1" + channel_msg_nick_replacement = "channel {channelname} = $1" + def search_channel(self, channelname, exact=False): """ Helper function for searching for a single channel with some error @@ -181,6 +185,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): if channel.has_connection(caller): return False, f"Already listening to channel {channel.key}." result = channel.connect(caller) + + key_and_aliases = [channel.key.lower()] + [alias.lower() for alias in channel.aliases.all()] + for key_or_alias in key_and_aliases: + self.add_alias(channel, key_or_alias) + return result, "" if result else f"Were not allowed to subscribe to channel {channel.key}" def unsub_from_channel(self, channel): @@ -201,46 +210,87 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): if not channel.has_connection(caller): return False, f"Not listening to channel {channel.key}." # clear nicks - chkey = channel.key - for nick in [ - nick - for nick in make_iter(caller.nicks.get(category="channel", return_obj=True)) - if nick and nick.pk and nick.value[3].lower() == chkey - ]: - nick.delete() + for key_or_alias in self.get_channel_aliases(channel): + self.remove_alias(key_or_alias) result = channel.disconnect(caller) return result, "" if result else f"Could not unsubscribe from channel {channel.key}" - def add_alias(self, channel, alias): + def add_alias(self, channel, alias, **kwargs): """ - Add a new alias for the user to use with this channel. + Add a new alias (nick) for the user to use with this channel. Args: channel (Channel): The channel to alias. alias (str): The personal alias to use for this channel. + **kwargs: If given, passed into nicks.add. + + Note: + We add two nicks - one is a plain `alias -> channel.key` that + we need to be able to reference this channel easily. The other + is a templated nick to easily be able to send messages to the + channel without needing to give the full `channel` command. The + structure of this nick is given by `self.channel_msg_nick_alias` + and `self.channel_msg_nick_replacement`. By default it maps + `alias -> channel = `, so that you can + for example just write `pub Hello` to send a message. + + The alias created is `alias $1 -> channel channel = $1`, to allow + for sending to channel using the main channel command. """ - self.caller.nicks.add(alias, channel.key, category="channel") + chan_key = channel.key.lower() + msg_alias = self.channel_msg_nick_alias.format(alias=alias) + msg_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key) - def remove_alias(self, alias): + if chan_key != alias: + self.caller.nicks.add(alias, chan_key, category="channel", **kwargs) + self.caller.nicks.add(msg_alias, msg_replacement, category="channel", **kwargs) + + def remove_alias(self, alias, **kwargs): """ - Remove an alias from a given channel. + Remove an alias from a channel. Args: - alias (str, optional): The alias to remove, or `None` for all. + alias (str, optional): The alias to remove. + The channel will be reverse-determined from the + alias, if it exists. Returns: bool, str: True, None if removal succeeded. If False, the second part is an error string. + **kwargs: If given, passed into nicks.get/add. + + Note: + This will remove two nicks - the plain channel alias and the templated + nick used for easily sending messages to the channel. """ caller = self.caller - channame = caller.nicks.get(key=alias, category="channel") - if channame: - caller.nicks.remove(key=alias, category="channel") + if caller.nicks.get(alias, category="channel", **kwargs): + msg_alias = self.channel_msg_nick_alias.format(alias=alias) + caller.nicks.remove(alias, category="channel", **kwargs) + caller.nicks.remove(msg_alias, category="channel", **kwargs) return True, "" return False, "No such alias was defined." + def get_channel_aliases(self, channel): + """ + Get a user's aliases for a given channel. The user is retrieved + through self.caller. + + Args: + channel (Channel): The channel to act on. + + Returns: + list: A list of zero, one or more alias-strings. + + """ + chan_key = channel.key.lower() + nicktuples = self.caller.nicks.get(category="channel", return_tuple=True) + if nicktuples: + return [tup[2] for tup in nicktuples if tup[3].lower() == chan_key] + return [] + def mute_channel(self, channel): """ Temporarily mute a channel. @@ -419,12 +469,13 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): nick.delete() channel.disconnect(target) reason = f" Reason: {reason}" if reason else "" - target.msg(f"You were booted from channel {channel.key} by {caller.key}.{reason}") + target.msg(f"You were booted from channel {channel.key} by {self.caller.key}.{reason}") if not quiet: - channel.msg(f"{target.key} was booted from channel by {caller.key}.{reason}") + channel.msg(f"{target.key} was booted from channel by {self.caller.key}.{reason}") logger.log_sec(f"Channel Boot: {target} (Channel: {channel}, " - f"Reason: {reason}, Caller: {caller}") + f"Reason: {reason.strip()}, Caller: {self.caller}") + return True, "" def ban_user(self, channel, target, quiet=False, reason=""): """ @@ -442,7 +493,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): the second part is an error string. """ - boot_user(self.caller, channel, target, quiet=quiet, reason=reason) + self.boot_user(channel, target, quiet=quiet, reason=reason) if channel.ban(target): return True, "" return False, f"{target} is already banned from this channel." @@ -508,6 +559,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): name = subscriber.get_display_name(caller) conditions = ("muted" if subscriber in mute_list else "", "offline" if subscriber not in online_list else "") + conditions = (cond for cond in conditions if cond) cond_text = "(" + ", ".join(conditions) + ")" if conditions else "" who_list.append(f"{name}{cond_text}") @@ -547,8 +599,6 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): EvTable: Table to display. """ - caller = self.caller - comtable = self.styled_table( "|wchannel|n", "|wmy aliases|n", @@ -558,14 +608,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): ) for chan in subscribed: - clower = chan.key.lower() - nicks = caller.nicks.get(category="channel", return_obj=True) + my_aliases = ", ".join(self.get_channel_aliases(chan)) comtable.add_row( *("{}{}".format( - chan.key, - "({})".format(",".join(chan.aliases.all())) if chan.aliases.all() else ""), - ",".join(nick.db_key for nick in make_iter(nicks) - if nick and nick.value[3].lower() == clower), + chan.key, + "({})".format(",".join(chan.aliases.all())) if chan.aliases.all() else ""), + my_aliases, chan.db.desc)) return comtable @@ -593,9 +641,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): channels = subscribed + available for chan in channels: - clower = chan.key.lower() - nicks = caller.nicks.get(category="channel", return_obj=True) - nicks = nicks or [] + my_aliases = ", ".join(self.get_channel_aliases(chan)) if chan not in subscribed: substatus = "|rNo|n" elif caller in chan.mutelist: @@ -607,8 +653,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): "{}{}".format( chan.key, "({})".format(",".join(chan.aliases.all())) if chan.aliases.all() else ""), - ",".join(nick.db_key for nick in make_iter(nicks) - if nick.value[3].lower() == clower), + my_aliases, str(chan.locks), chan.db.desc)) comtable.reformat_column(0, width=9) @@ -666,6 +711,20 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(err) return + if 'unalias' in switches: + # remove a personal alias (no channel needed) + alias = self.rhs + if not alias: + self.msg("Specify the alias to remove as channel/unalias ") + return + success, err = self.remove_alias(alias) + if success: + self.msg(f"Removed your channel alias '{alias}'.") + else: + self.msg(err) + return + + channels = [] for channel_name in channel_names: # find a channel by fuzzy-matching. This also checks @@ -691,7 +750,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): # inspect a given channel subscribed, available = self.list_channels() if channel in subscribed: - table = self.display_subbed_channels[channel] + table = self.display_subbed_channels([channel]) self.msg( "\n|wSubscribed to Channel|n " f"(use |w/all|n to see all available)\n{table}") @@ -717,11 +776,16 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): if 'sub' in switches: # subscribe to a channel + aliases = set(alias.strip().lower() for alias in self.rhs.split(";")) success, err = self.sub_to_channel(channel) if success: + for alias in aliases: + self.add_alias(channel, alias) + alias_txt = ', '.join(aliases) + alias_txt = f" using alias(es) {alias_txt}" if aliases else '' self.msg("You are now subscribed " - f"to the channel {channel.key}. Use /alias to " - "be able to use different names to refer to the channel.") + f"to the channel {channel.key}{alias_txt}. Use /alias to " + "add additional aliases for referring to the channel.") else: self.msg(err) return @@ -746,19 +810,6 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg(f"Added/updated your alias '{alias}' for channel {channel.key}.") return - if 'unalias' in switches: - # remove a personal alias for a channel - alias = self.rhs - if not alias: - self.msg("Specify the alias to remove as channel/unalias channelname = alias") - return - success, err = self.remove_alias(channel, alias) - if success: - self.msg(f"Removed your alias '{alias}' for channel {channel.key}") - else: - self.msg(err) - return - if 'mute' in switches: # mute a given channel success, err = self.mute_channel(channel) @@ -798,18 +849,33 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): "Aborted." ) + if 'desc' in switches: + # set channel description + desc = self.rhs.strip() + + if not channel.access(caller, "control"): + self.msg("You can only change description of channels you control.") + return + + if not desc: + self.msg("Usage: /desc channel = description") + return + + self.set_desc(channel, desc) + self.msg("Updated channel description.") + if 'lock' in switches: # add a lockstring to channel lockstring = self.rhs.strip() - if not lockstring: - self.msg("Usage: channel/lock channelname = lockstring") - return - if not channel.access(caller, "control"): self.msg("You need 'control'-access to change locks on this channel.") return + if not lockstring: + self.msg("Usage: channel/lock channelname = lockstring") + return + success, err = self.set_lock(channel, self.rhs) if success: caller.msg("Added/updated lock on channel.") @@ -829,9 +895,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): self.msg("You need 'control'-access to change locks on this channel.") return - success, err = self.set_lock(channel, self.rhs) + success, err = self.unset_lock(channel, self.rhs) if success: - caller.msg("Removed lock on channel.") + caller.msg("Removed lock from channel.") else: caller.msg(f"Could not remove lock: {err}") return @@ -848,12 +914,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): for chan in channels: - if not chan.access(caller, "admin"): - self.msg("You need 'control'-access to boot a user from {chan.key}.") + if not chan.access(caller, "control"): + self.msg(f"You need 'control'-access to boot a user from {chan.key}.") return # the target must be a member of all given channels - target = self.search(target_str, candidates=chan.subscriptions.all()) + target = caller.search(target_str, candidates=chan.subscriptions.all()) if not target: caller.msg(f"Cannot boot '{target_str}' - not in channel {chan.key}.") return @@ -867,10 +933,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): caller.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}") channames = ", ".join(chan.key for chan in channels) + reasonwarn = (". Also note that your reason will be echoed to the channel" + if reason else '') ask_yes_no( caller, f"Are you sure you want to boot user {target.key} from " - f"channel(s) {channames} (make sure name/channels are correct) " + f"channel(s) {channames} (make sure name/channels are correct{reasonwarn}). " "{yesno}?", _boot_user, "Aborted.", @@ -885,7 +953,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): # view bans for channels if not channel.access(caller, "control"): - self.msg("You need 'control'-access to view bans on channel {channel.key}") + self.msg(f"You need 'control'-access to view bans on channel {channel.key}") return bans = ["Channel bans " @@ -899,7 +967,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): for chan in channels: # the target must be a member of all given channels - target = self.search(target_str, candidates=chan.subscriptions.all()) + if not chan.access(caller, "control"): + caller.msg(f"You don't have access to ban users on channel {chan.key}") + return + + target = caller.search(target_str, candidates=chan.subscriptions.all()) + if not target: caller.msg(f"Cannot ban '{target_str}' - not in channel {chan.key}.") return @@ -908,26 +981,57 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): for chan in channels: success, err = self.ban_user(chan, target, quiet=False, reason=reason) if success: - caller.msg(f"Banned {target.key} from channel {chan.key}.") + self.msg(f"Banned {target.key} from channel {chan.key}.") else: - caller.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}") + self.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}") channames = ", ".join(chan.key for chan in channels) + reasonwarn = (". Also note that your reason will be echoed to the channel" + if reason else '') ask_yes_no( caller, f"Are you sure you want to ban user {target.key} from " - f"channel(s) {channames} (make sure name/channels are correct) " + f"channel(s) {channames} (make sure name/channels are correct{reasonwarn}) " "{yesno}?", _ban_user, "Aborted.", ) return + if 'unban' in switches: + # unban a previously banned user from channel + target_str = self.rhs.strip() + + if not target_str: + self.msg("Usage: channel[,channel,...] = user") + return + + banlists = [] + for chan in channels: + # the target must be a member of all given channels + if not chan.access(caller, "control"): + caller.msg(f"You don't have access to unban users on channel {chan.key}") + return + banlists.extend(chan.banlist) + + target = caller.search(target_str, candidates=banlists) + if not target: + self.msg("Could not find a banned user '{target_str}' in given channel(s).") + return + + for chan in channels: + success, err = self.unban_user(channel, target) + if success: + self.msg(f"Un-banned {target_str} from channel {chan.key}") + else: + self.msg(err) + return + if "who" in switches: # view who's a member of a channel who_list = [f"Subscribed to {channel.key}:"] - who_list.expand(self.channel_list_who(channel)) + who_list.extend(self.channel_list_who(channel)) caller.msg("\n".join(who_list)) return diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 28048d7aa5..02dbaaa898 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -247,6 +247,12 @@ class CommandTest(EvenniaTest): except InterruptCommand: pass + for inp in inputs: + # if there are any inputs left, we may have a non-generator + # input to handle (get_input/ask_yes_no that uses a separate + # cmdset rather than a yield + caller.execute_cmd(inp) + # At this point the mocked .msg methods on each receiver will have # stored all calls made to them (that's a basic function of the Mock # class). We will not extract them and compare to what we expected to @@ -1662,14 +1668,15 @@ class TestCommsChannel(CommandTest): self.channel.connect(self.char1) def tearDown(self): - self.channel.delete() + if self.channel.pk: + self.channel.delete() # test channel command def test_channel__noarg(self): self.call( comms.CmdChannel(), "", - "Usage" + "Channel subscriptions" ) def test_channel__msg(self): @@ -1712,7 +1719,10 @@ class TestCommsChannel(CommandTest): "/sub testchannel", "You are now subscribed" ) - self.assertTrue(self.char1 in self.channel.subscriptions) + self.assertTrue(self.char1 in self.channel.subscriptions.all()) + alias_msg = comms.CmdChannel.channel_msg_nick_alias.format(alias='testchannel') + self.assertEqual(self.char1.nicks.get(alias_msg, category="channel"), + "channel testchannel = $1") def test_channel__unsub(self): self.call( @@ -1720,32 +1730,40 @@ class TestCommsChannel(CommandTest): "/unsub testchannel", "You un-subscribed" ) - self.assertFalse(self.char1 in self.channel.subscriptions) + self.assertFalse(self.char1 in self.channel.subscriptions.all()) - def test_channel__alias(self): + def test_channel__alias__unalias(self): + """Add and then remove a channel alias""" + # add alias self.call( comms.CmdChannel(), "/alias testchannel = foo", - "" + "Added/updated your alias 'foo' for channel testchannel." ) - self.assertEqual(self.chan1.aliases.get('foo'), "") + self.assertEqual( + self.char1.nicks.get('foo $1', category="channel"), "channel testchannel = $1") - def test_channel__unalias(self): - - self.chan1.aliases.add("foo", "", "channel") + # use alias + self.channel.msg = Mock() + self.call( + comms.CmdChannel(), + "foo = test message", + "") + self.channel.msg.assert_called_with("test message", senders=self.char1) + # remove alias self.call( comms.CmdChannel(), "/unalias testchannel = foo", - "" + "Removed your channel alias 'foo'" ) - self.assertEqual(self.chan1.aliases.get('foo'), None) + self.assertEqual(self.char1.nicks.get('foo $1', category="channel"), None) def test_channel__mute(self): self.call( comms.CmdChannel(), "/mute testchannel", - "" + "Muted channel testchannel" ) self.assertTrue(self.char1 in self.channel.mutelist) @@ -1755,7 +1773,7 @@ class TestCommsChannel(CommandTest): self.call( comms.CmdChannel(), "/unmute testchannel = Char1", - "" + "Un-muted channel testchannel" ) self.assertFalse(self.char1 in self.channel.mutelist) @@ -1763,51 +1781,96 @@ class TestCommsChannel(CommandTest): self.call( comms.CmdChannel(), "/create testchannel2", - "" + "Created (and joined) new channel" ) def test_channel__destroy(self): + self.channel.msg = Mock() self.call( comms.CmdChannel(), - "/create testchannel2", - "" + "/destroy testchannel = delete reason", + "Are you sure you want to delete channel ", + inputs=['Yes'] ) + self.channel.msg.assert_called_with( + "delete reason", bypass_mute=True, senders=self.char1) def test_channel__desc(self): self.call( comms.CmdChannel(), "/desc testchannel = Another description", - "" + "Updated channel description." ) def test_channel__lock(self): self.call( comms.CmdChannel(), - "/lock testchannel = foo:bar()", - "" + "/lock testchannel = foo:false()", + "Added/updated lock on channel" ) - self.assertEqual(self.channel.locks.all(), []) + self.assertEqual(self.channel.locks.get('foo'), 'foo:false()') def test_channel__unlock(self): - self.channel.locks.add("foo:bar()") + self.channel.locks.add("foo:true()") self.call( comms.CmdChannel(), - "/unlock testchannel = foo:bar()", - "" + "/unlock testchannel = foo", + "Removed lock from channel" ) - self.assertEqual(self.channel.locks.all(), []) + self.assertEqual(self.channel.locks.get('foo'), '') def test_channel__boot(self): - pass + self.channel.connect(self.char2) + self.assertTrue(self.char2 in self.channel.subscriptions.all()) + self.channel.msg = Mock() + self.char2.msg = Mock() - def test_channel__ban(self): - pass + self.call( + comms.CmdChannel(), + "/boot testchannel = Char2 : Booting from channel!", + "Are you sure ", + inputs=["Yes"] + ) + self.channel.msg.assert_called_with( + "Char2 was booted from channel by Char. Reason: Booting from channel!") + self.char2.msg.assert_called_with( + "You were booted from channel testchannel by Char. Reason: Booting from channel!") + + def test_channel__ban__unban(self): + """Test first ban and then unban""" + + # ban + self.channel.connect(self.char2) + self.assertTrue(self.char2 in self.channel.subscriptions.all()) + self.channel.msg = Mock() + self.char2.msg = Mock() + + self.call( + comms.CmdChannel(), + "/ban testchannel = Char2 : Banning from channel!", + "Are you sure ", + inputs=["Yes"] + ) + self.channel.msg.assert_called_with( + "Char2 was booted from channel by Char. Reason: Banning from channel!") + self.char2.msg.assert_called_with( + "You were booted from channel testchannel by Char. Reason: Banning from channel!") + self.assertTrue(self.char2 in self.channel.banlist) + + # unban + + self.call( + comms.CmdChannel(), + "/unban testchannel = Char2", + "Un-banned Char2 from channel testchannel" + ) + self.assertFalse(self.char2 in self.channel.banlist) def test_channel__who(self): self.call( comms.CmdChannel(), "/who testchannel", - "" + "Subscribed to testchannel:\nChar" ) diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index 6f0156753e..4f57722fc5 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -208,6 +208,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): banlist = self.banlist if target not in banlist: banlist.append(target) + self.db.ban_list = banlist return True return False @@ -226,9 +227,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): bool: True if unbanning was successful, False if target was not previously banned. """ - banlist = self.banlist - if target not in banlist: - banlist.append(target) + banlist = list(self.banlist) + if target in banlist: + banlist = [banned for banned in banlist if banned != target] + self.db.ban_list = banlist return True return False diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index a56ebdd3e2..74c2f7f32a 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -1405,6 +1405,11 @@ class NickHandler(AttributeHandler): a string. kwargs (any, optional): These are passed on to `AttributeHandler.get`. + Returns: + str or tuple: The nick replacement string or nick tuple. + + + """ if return_tuple or "return_obj" in kwargs: return super().get(key=key, category=category, **kwargs) @@ -1431,10 +1436,10 @@ class NickHandler(AttributeHandler): kwargs (any, optional): These are passed on to `AttributeHandler.get`. """ - if category == "channel": - nick_regex, nick_template = initialize_nick_templates(key + " $1", replacement + " $1") - else: - nick_regex, nick_template = initialize_nick_templates(key, replacement) + # if category == "channel": + # nick_regex, nick_template = initialize_nick_templates(key + " $1", replacement + " $1") + # else: + nick_regex, nick_template = initialize_nick_templates(key, replacement) super().add(key, (nick_regex, nick_template, key, replacement), category=category, **kwargs) def remove(self, key, category="inputline", **kwargs): diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index 5a27a4e65f..4cd65c332e 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -1678,11 +1678,12 @@ class CmdYesNoQuestion(Command): # cleanup self._clean() - except Exception: + except Exception as err: # make sure to clean up cmdset if something goes wrong caller.msg("|rError in ask_yes_no. Choice not confirmed (report to admin)|n") logger.log_trace("Error in ask_yes_no") self._clean() + raise class YesNoQuestionCmdSet(CmdSet):