Adopt old comm commands to new channel system. Allow using page without an equal sign if target name has no spaces. Not passing tests

This commit is contained in:
Griatch 2021-05-02 23:18:29 +02:00
parent 67908c5af0
commit 2da679cdd1
6 changed files with 205 additions and 435 deletions

View file

@ -17,7 +17,7 @@
initial game database state. (volund) initial game database state. (volund)
- Added new Traits contrib, converted and expanded from Ainneve project. - Added new Traits contrib, converted and expanded from Ainneve project.
- Added new `requirements_extra.txt` file for easily getting all optional dependencies. - Added new `requirements_extra.txt` file for easily getting all optional dependencies.
- Change default multimatch syntax from 1-obj, 2-obj to obj-1, obj-2. - Change default multi-match syntax from 1-obj, 2-obj to obj-1, obj-2.
- Make `object.search` support 'stacks=0' keyword - if ``>0``, the method will return - Make `object.search` support 'stacks=0' keyword - if ``>0``, the method will return
N identical matches instead of triggering a multi-match error. N identical matches instead of triggering a multi-match error.
- Add `tags.has()` method for checking if an object has a tag or tags (PR by ChrisLR) - Add `tags.has()` method for checking if an object has a tag or tags (PR by ChrisLR)
@ -32,26 +32,27 @@
- Latin (la) i18n translation (jamalainm) - Latin (la) i18n translation (jamalainm)
- Made the `evennia` dir possible to use without gamedir for purpose of doc generation. - Made the `evennia` dir possible to use without gamedir for purpose of doc generation.
- Make Scripts' timer component independent from script object deletion; can now start/stop - Make Scripts' timer component independent from script object deletion; can now start/stop
timer without deleting Script. The `.persistent` flag now only controls if timer survives timer without deleting Script. The `.persistent` flag now only controls if timer survives
reload - Script has to be removed with `.delete()` like other typeclassed entities. reload - Script has to be removed with `.delete()` like other typeclassed entities.
- Add `utils.repeat` and `utils.unrepeat` as shortcuts to TickerHandler add/remove, similar - Add `utils.repeat` and `utils.unrepeat` as shortcuts to TickerHandler add/remove, similar
to how `utils.delay` is a shortcut for TaskHandler add. to how `utils.delay` is a shortcut for TaskHandler add.
- Refactor the classic `red_button` example to use `utils.delay/repeat` and modern recommended - Refactor the classic `red_button` example to use `utils.delay/repeat` and modern recommended
code style and paradigms instead of relying on `Scripts` for everything. code style and paradigms instead of relying on `Scripts` for everything.
- Expand `CommandTest` with ability to check multipler msg-receivers; inspired by PR by - Expand `CommandTest` with ability to check multiple message-receivers; inspired by PR by
user davewiththenicehat. Also add new doc string. user davewiththenicehat. Also add new doc string.
- Add central `FuncParser` as a much more powerful replacement for the old `parse_inlinefunc` - Add central `FuncParser` as a much more powerful replacement for the old `parse_inlinefunc`
function. function.
- Add `evennia/utils/verb_conjugation` for automatic verb conjugation (English only). This - Add `evennia/utils/verb_conjugation` for automatic verb conjugation (English only). This
is useful for implementing actor-stance emoting for sending a string to different targets. is useful for implementing actor-stance emoting for sending a string to different targets.
- New version of Italian translation (rpolve) - New version of Italian translation (rpolve)
- `utils.evmenu.ask_yes_no` is a helper function that makes it easy to ask a yes/no question - `utils.evmenu.ask_yes_no` is a helper function that makes it easy to ask a yes/no question
to the user and respond to their input. This complements the existing `get_input` helper. to the user and respond to their input. This complements the existing `get_input` helper.
- Allow sending messages with `page/tell` without a `=` if target name contains no spaces.
### Evennia 0.9.5 (2019-2020) ### Evennia 0.9.5 (2019-2020)
Released 2020-11-14. Released 2020-11-14.
A transitional release, including new doc system. A transitional release, including new doc system.
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False - `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
- `py` command now reroutes stdout to output results in-game client. `py` - `py` command now reroutes stdout to output results in-game client. `py`
@ -115,13 +116,13 @@ without arguments starts a full interactive Python console.
- Make `INLINEFUNC_STACK_MAXSIZE` default visible in `settings_default.py`. - Make `INLINEFUNC_STACK_MAXSIZE` default visible in `settings_default.py`.
- Change how `ic` finds puppets; non-priveleged users will use `_playable_characters` list as - Change how `ic` finds puppets; non-priveleged users will use `_playable_characters` list as
candidates, Builders+ will use list, local search and only global search if no match found. candidates, Builders+ will use list, local search and only global search if no match found.
- Make `cmd.at_post_cmd()` always run after `cmd.func()`, even when the latter uses delays - Make `cmd.at_post_cmd()` always run after `cmd.func()`, even when the latter uses delays
with yield. with yield.
- `EvMore` support for db queries and django paginators as well as easier to override for custom - `EvMore` support for db queries and django paginators as well as easier to override for custom
pagination (e.g. to create EvTables for every page instead of splittine one table) pagination (e.g. to create EvTables for every page instead of splittine one table)
- Using `EvMore pagination`, dramatically improves performance of `spawn/list` and `scripts` listings - Using `EvMore pagination`, dramatically improves performance of `spawn/list` and `scripts` listings
(100x speed increase for displaying 1000+ prototypes/scripts). (100x speed increase for displaying 1000+ prototypes/scripts).
- `EvMenu` now uses the more logically named `.ndb._evmenu` instead of `.ndb._menutree` to store itself. - `EvMenu` now uses the more logically named `.ndb._evmenu` instead of `.ndb._menutree` to store itself.
Both still work for backward compatibility, but `_menutree` is deprecated. Both still work for backward compatibility, but `_menutree` is deprecated.
- `EvMenu.msg(txt)` added as a central place to send text to the user, makes it easier to override. - `EvMenu.msg(txt)` added as a central place to send text to the user, makes it easier to override.
Default `EvMenu.msg` sends with OOB type="menu" for use with OOB and webclient pane-redirects. Default `EvMenu.msg` sends with OOB type="menu" for use with OOB and webclient pane-redirects.

View file

@ -11,11 +11,10 @@ from django.conf import settings
from evennia.comms.models import Msg from evennia.comms.models import Msg
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
from evennia.accounts import bots from evennia.accounts import bots
from evennia.comms.channelhandler import CHANNELHANDLER
from evennia.locks.lockhandler import LockException from evennia.locks.lockhandler import LockException
from evennia.utils import create, logger, utils from evennia.utils import create, logger, utils
from evennia.utils.logger import tail_log_file from evennia.utils.logger import tail_log_file
from evennia.utils.utils import make_iter, class_from_module from evennia.utils.utils import class_from_module
from evennia.utils.evmenu import ask_yes_no from evennia.utils.evmenu import ask_yes_no
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -29,10 +28,8 @@ __all__ = (
"CmdAddCom", "CmdAddCom",
"CmdDelCom", "CmdDelCom",
"CmdAllCom", "CmdAllCom",
"CmdChannels",
"CmdCdestroy", "CmdCdestroy",
"CmdCBoot", "CmdCBoot",
"CmdCemit",
"CmdCWho", "CmdCWho",
"CmdChannelCreate", "CmdChannelCreate",
"CmdClock", "CmdClock",
@ -72,7 +69,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
channel/unlock channelname = lockstring channel/unlock channelname = lockstring
channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason] channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]
channel/ban channelname (list bans) channel/ban channelname (list bans)
channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason] channe/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]
channel/unban[/quiet] channelname[, channelname, ...] = subscribername channel/unban[/quiet] channelname[, channelname, ...] = subscribername
channel/who channelname channel/who channelname
@ -100,7 +97,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
channel_msg_nick_alias = r"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)" channel_msg_nick_alias = r"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)"
channel_msg_nick_replacement = "channel {channelname} = $1" channel_msg_nick_replacement = "channel {channelname} = $1"
def search_channel(self, channelname, exact=False): def search_channel(self, channelname, exact=False, handle_errors=True):
""" """
Helper function for searching for a single channel with some error Helper function for searching for a single channel with some error
handling. handling.
@ -111,9 +108,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
exact (bool, optional): If an exact or fuzzy-match of the name should be done. exact (bool, optional): If an exact or fuzzy-match of the name should be done.
Note that even for a fuzzy match, an exactly given, unique channel name Note that even for a fuzzy match, an exactly given, unique channel name
will always be returned. will always be returned.
handle_errors (bool): If true, use `self.msg` to report errors if
there are non/multiple matches. If so, the return will always be
a single match or None.
Returns: Returns:
list: A list of zero, one or more channels found. object, list or None: If `handle_errors` is `True`, this is either a found Channel
or `None`. Otherwise it's a list of zero, one or more channels found.
Notes: Notes:
The 'listen' and 'control' accesses are checked before returning. The 'listen' and 'control' accesses are checked before returning.
@ -133,11 +133,22 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
channels = [channel for channel in channels channels = [channel for channel in channels
if channel.access(caller, 'listen') or channel.access(caller, 'control')] if channel.access(caller, 'listen') or channel.access(caller, 'control')]
if not channels: if handle_errors:
return [] if not channels:
elif len(channels) > 1: self.msk(f"No channel found matching '{channelname}' "
return list(channels) "could also be due to missing access).")
return [channels[0]] return None
elif len(channels) > 1:
self.msg("Multiple possible channel matches/alias for "
"'{channelname}':\n" + ", ".join(chan.key for chan in channels))
return None
return channels[0]
else:
if not channels:
return []
elif len(channels) > 1:
return list(channels)
return [channels[0]]
def msg_channel(self, channel, message, **kwargs): def msg_channel(self, channel, message, **kwargs):
""" """
@ -284,7 +295,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
""" """
caller = self.caller caller = self.caller
if caller.nicks.get(alias, category="channel", **kwargs): if caller.nicks.get(alias, category="channel", **kwargs):
caller.nicks.remove(alias, category="channel", **kwargs) caller.nicks.remove(alias, category="chan nel", **kwargs)
msg_alias = self.channel_msg_nick_alias.format(alias=alias) msg_alias = self.channel_msg_nick_alias.format(alias=alias)
caller.nicks.remove(msg_alias, category="inputline", **kwargs) caller.nicks.remove(msg_alias, category="inputline", **kwargs)
return True, "" return True, ""
@ -747,13 +758,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
# 'listen/control' perms. # 'listen/control' perms.
channel = self.search_channel(channel_name, exact=False) channel = self.search_channel(channel_name, exact=False)
if not channel: if not channel:
self.msg(f"No channel found matching '{channel_name}'.")
return return
elif len(channel) > 1: channels.append(channel)
self.msg("Multiple possible channel matches/alias for "
"'{channel_name}':\n" + ", ".join(chan.key for chan in channel))
return
channels.extend(channel)
# we have at least one channel at this point # we have at least one channel at this point
channel = channels[0] channel = channels[0]
@ -795,8 +801,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
return return
if 'sub' in switches: if 'sub' in switches:
# subscribe to a channel # subscribe to a channel aliases = set(alias.strip().lower() for
aliases = set(alias.strip().lower() for alias in self.rhs.split(";")) # alias in self.rhs.split(";"))
success, err = self.sub_to_channel(channel) success, err = self.sub_to_channel(channel)
if success: if success:
for alias in aliases: for alias in aliases:
@ -1056,9 +1062,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
return return
class CmdAddCom(COMMAND_DEFAULT_CLASS): class CmdAddCom(CmdChannel):
""" """
add a channel alias and/or subscribe to a channel Add a channel alias and/or subscribe to a channel
Usage: Usage:
addcom [alias=] <channel> addcom [alias=] <channel>
@ -1082,7 +1088,6 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS):
caller = self.caller caller = self.caller
args = self.args args = self.args
account = caller
if not args: if not args:
self.msg("Usage: addcom [alias =] channelname.") self.msg("Usage: addcom [alias =] channelname.")
@ -1096,42 +1101,37 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS):
channelname = self.args channelname = self.args
alias = None alias = None
channel = find_channel(caller, channelname) channel = self.search_channel(channelname)
if not channel: if not channel:
# we use the custom search method to handle errors.
return
# check permissions
if not channel.access(account, "listen"):
self.msg("%s: You are not allowed to listen to this channel." % channel.key)
return return
string = "" string = ""
if not channel.has_connection(account): if not channel.has_connection(caller):
# we want to connect as well. # we want to connect as well.
if not channel.connect(account): success, err = self.sub_to_channel(channel)
if success:
# if this would have returned True, the account is connected # if this would have returned True, the account is connected
self.msg("%s: You are not allowed to join this channel." % channel.key) self.msg(f"You now listen to the channel {channel.key}")
else:
self.msg(f"{channel.key}: You are not allowed to join this channel.")
return return
else:
string += "You now listen to the channel %s. " % channel.key if channel.unmute(caller):
string += "You unmute channel %s." % channel.key
else: else:
if channel.unmute(account): string += "You are already connected to channel %s." % channel.key
string += "You unmute channel %s." % channel.key
else:
string += "You are already connected to channel %s." % channel.key
if alias: if alias:
# create a nick and add it to the caller. # create a nick and add it to the caller.
caller.nicks.add(alias, channel.key, category="channel") self.add_alias(channel, alias)
string += " You can now refer to the channel %s with the alias '%s'." self.msg(f" You can now refer to the channel {channel} with the alias '{alias}'.")
self.msg(string % (channel.key, alias)) self.msg(string % (channel.key, alias))
else: else:
string += " No alias added." string += " No alias added."
self.msg(string) self.msg(string)
class CmdDelCom(COMMAND_DEFAULT_CLASS): class CmdDelCom(CmdChannel):
""" """
remove a channel alias and/or unsubscribe from channel remove a channel alias and/or unsubscribe from channel
@ -1157,49 +1157,42 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS):
"""Implementing the command. """ """Implementing the command. """
caller = self.caller caller = self.caller
account = caller
if not self.args: if not self.args:
self.msg("Usage: delcom <alias or channel>") self.msg("Usage: delcom <alias or channel>")
return return
ostring = self.args.lower() ostring = self.args.lower().strip()
channel = find_channel(caller, ostring, silent=True, noaliases=True) channel = self.search_channel(ostring)
if channel: if not channel:
# we have given a channel name - unsubscribe return
if not channel.has_connection(account):
self.msg("You are not listening to that channel.") if not channel.has_connection(caller):
return self.msg("You are not listening to that channel.")
chkey = channel.key.lower() return
if ostring == channel.key.lower():
# an exact channel name - unsubscribe
delnicks = "all" in self.switches delnicks = "all" in self.switches
# find all nicks linked to this channel and delete them # find all nicks linked to this channel and delete them
if delnicks: if delnicks:
for nick in [ aliases = self.get_channel_aliases(channel)
nick for alias in aliases:
for nick in make_iter(caller.nicks.get(category="channel", return_obj=True)) self.remove_alias(alias)
if nick and nick.pk and nick.value[3].lower() == chkey success, err = self.unsub_from_channel(channel)
]: if success:
nick.delete()
disconnect = channel.disconnect(account)
if disconnect:
wipednicks = " Eventual aliases were removed." if delnicks else "" wipednicks = " Eventual aliases were removed." if delnicks else ""
self.msg("You stop listening to channel '%s'.%s" % (channel.key, wipednicks)) self.msg(f"You stop listening to channel '{channel.key}'.{wipednicks}")
else:
self.msg(err)
return return
else: else:
# we are removing a channel nick # we are removing a channel nick
channame = caller.nicks.get(key=ostring, category="channel") self.remove_alias(ostring)
channel = find_channel(caller, channame, silent=True) self.msg(f"Any alias '{ostring}' for channel {channel.key} was cleared.")
if not channel:
self.msg("No channel with alias '%s' was found." % ostring)
else:
if caller.nicks.get(ostring, category="channel"):
caller.nicks.remove(ostring, category="channel")
self.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key))
else:
self.msg("You had no such alias defined for this channel.")
class CmdAllCom(COMMAND_DEFAULT_CLASS): class CmdAllCom(CmdChannel):
""" """
perform admin operations on all channels perform admin operations on all channels
@ -1271,125 +1264,7 @@ class CmdAllCom(COMMAND_DEFAULT_CLASS):
# wrong input # wrong input
self.msg("Usage: allcom on | off | who | clear") self.msg("Usage: allcom on | off | who | clear")
class CmdCdestroy(CmdChannel):
class CmdChannels(COMMAND_DEFAULT_CLASS):
"""
list all channels available to you
Usage:
channels
clist
comlist
Lists all channels available to you, whether you listen to them or not.
Use 'comlist' to only view your current channel subscriptions.
Use addcom/delcom to join and leave channels
"""
key = "channels"
aliases = ["clist", "comlist", "chanlist", "channellist", "all channels"]
help_category = "Comms"
locks = "cmd: not pperm(channel_banned)"
# this is used by the COMMAND_DEFAULT_CLASS parent
account_caller = True
def func(self):
"""Implement function"""
caller = self.caller
# all channels we have available to listen to
channels = [
chan
for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels()
if chan.access(caller, "listen")
]
if not channels:
self.msg("No channels available.")
return
# all channel we are already subscribed to
subs = CHANNEL_DEFAULT_TYPECLASS.objects.get_subscriptions(caller)
if self.cmdstring == "comlist":
# just display the subscribed channels with no extra info
comtable = self.styled_table(
"|wchannel|n",
"|wmy aliases|n",
"|wdescription|n",
align="l",
maxwidth=_DEFAULT_WIDTH,
)
for chan in subs:
clower = chan.key.lower()
nicks = caller.nicks.get(category="channel", return_obj=True)
comtable.add_row(
*[
"%s%s"
% (
chan.key,
chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or "",
),
"%s"
% ",".join(
nick.db_key
for nick in make_iter(nicks)
if nick and nick.value[3].lower() == clower
),
chan.db.desc,
]
)
self.msg(
"\n|wChannel subscriptions|n (use |wchannels|n to list all,"
" |waddcom|n/|wdelcom|n to sub/unsub):|n\n%s" % comtable
)
else:
# full listing (of channels caller is able to listen to)
comtable = self.styled_table(
"|wsub|n",
"|wchannel|n",
"|wmy aliases|n",
"|wlocks|n",
"|wdescription|n",
maxwidth=_DEFAULT_WIDTH,
)
for chan in channels:
clower = chan.key.lower()
nicks = caller.nicks.get(category="channel", return_obj=True)
nicks = nicks or []
if chan not in subs:
substatus = "|rNo|n"
elif caller in chan.mutelist:
substatus = "|rMuted|n"
else:
substatus = "|gYes|n"
comtable.add_row(
*[
substatus,
"%s%s"
% (
chan.key,
chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or "",
),
"%s"
% ",".join(
nick.db_key
for nick in make_iter(nicks)
if nick.value[3].lower() == clower
),
str(chan.locks),
chan.db.desc,
]
)
comtable.reformat_column(0, width=9)
comtable.reformat_column(3, width=14)
self.msg(
"\n|wAvailable channels|n (use |wcomlist|n,|waddcom|n and |wdelcom|n"
" to manage subscriptions):\n%s" % comtable
)
class CmdCdestroy(COMMAND_DEFAULT_CLASS):
""" """
destroy a channel you created destroy a channel you created
@ -1408,12 +1283,15 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
def func(self): def func(self):
"""Destroy objects cleanly.""" """Destroy objects cleanly."""
caller = self.caller caller = self.caller
if not self.args: if not self.args:
self.msg("Usage: cdestroy <channelname>") self.msg("Usage: cdestroy <channelname>")
return return
channel = find_channel(caller, self.args)
channel = self.search_channel(self.args)
if not channel: if not channel:
self.msg("Could not find channel %s." % self.args) self.msg("Could not find channel %s." % self.args)
return return
@ -1421,11 +1299,8 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
self.msg("You are not allowed to do that.") self.msg("You are not allowed to do that.")
return return
channel_key = channel.key channel_key = channel.key
message = "%s is being destroyed. Make sure to change your aliases." % channel_key message = f"{channel.key} is being destroyed. Make sure to change your aliases."
msgobj = create.create_message(caller, message, channel) self.destroy_channel(channel, message)
channel.msg(msgobj)
channel.delete()
CHANNELHANDLER.update()
self.msg("Channel '%s' was destroyed." % channel_key) self.msg("Channel '%s' was destroyed." % channel_key)
logger.log_sec( logger.log_sec(
"Channel Deleted: %s (Caller: %s, IP: %s)." "Channel Deleted: %s (Caller: %s, IP: %s)."
@ -1433,7 +1308,7 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
) )
class CmdCBoot(COMMAND_DEFAULT_CLASS): class CmdCBoot(CmdChannel):
""" """
kick an account from a channel you control kick an account from a channel you control
@ -1463,17 +1338,21 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
self.msg(string) self.msg(string)
return return
channel = find_channel(self.caller, self.lhs) channel = self.search_channel(self.lhs)
if not channel: if not channel:
return return
reason = "" reason = ""
if ":" in self.rhs: if ":" in self.rhs:
accountname, reason = self.rhs.rsplit(":", 1) target, reason = self.rhs.rsplit(":", 1)
searchstring = accountname.lstrip("*") is_account = target.strip().startswith("*")
searchstring = target.lstrip("*")
else: else:
is_account = target.strip().startswith("*")
searchstring = self.rhs.lstrip("*") searchstring = self.rhs.lstrip("*")
account = self.caller.search(searchstring, account=True)
if not account: target = self.caller.search(searchstring, account=is_account)
if not target:
return return
if reason: if reason:
reason = " (reason: %s)" % reason reason = " (reason: %s)" % reason
@ -1481,79 +1360,19 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
string = "You don't control this channel." string = "You don't control this channel."
self.msg(string) self.msg(string)
return return
if not channel.subscriptions.has(account):
string = "Account %s is not connected to channel %s." % (account.key, channel.key) success, err = self.boot_user(target, quiet='quiet' in self.switches)
self.msg(string) if success:
return self.msg(f"Booted {target.key} from {channel.key}")
if "quiet" not in self.switches: logger.log_sec(
string = "%s boots %s from channel.%s" % (self.caller, account.key, reason) "Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s)."
channel.msg(string) % (self.caller, channel, reason, self.caller, self.session.address)
# find all account's nicks linked to this channel and delete them )
for nick in [ else:
nick self.msg(err)
for nick in account.character.nicks.get(category="channel") or []
if nick.value[3].lower() == channel.key
]:
nick.delete()
# disconnect account
channel.disconnect(account)
CHANNELHANDLER.update()
logger.log_sec(
"Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s)."
% (account, channel, reason, self.caller, self.session.address)
)
class CmdCemit(COMMAND_DEFAULT_CLASS): class CmdCWho(CmdChannel):
"""
send an admin message to a channel you control
Usage:
cemit[/switches] <channel> = <message>
Switches:
sendername - attach the sender's name before the message
quiet - don't echo the message back to sender
Allows the user to broadcast a message over a channel as long as
they control it. It does not show the user's name unless they
provide the /sendername switch.
"""
key = "cemit"
aliases = ["cmsg"]
switch_options = ("sendername", "quiet")
locks = "cmd: not pperm(channel_banned) and pperm(Player)"
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
account_caller = True
def func(self):
"""Implement function"""
if not self.args or not self.rhs:
string = "Usage: cemit[/switches] <channel> = <message>"
self.msg(string)
return
channel = find_channel(self.caller, self.lhs)
if not channel:
return
if not channel.access(self.caller, "control"):
string = "You don't control this channel."
self.msg(string)
return
message = self.rhs
if "sendername" in self.switches:
message = "%s: %s" % (self.caller.key, message)
channel.msg(message)
if "quiet" not in self.switches:
string = "Sent to channel %s: %s" % (channel.key, message)
self.msg(string)
class CmdCWho(COMMAND_DEFAULT_CLASS):
""" """
show who is listening to a channel show who is listening to a channel
@ -1578,7 +1397,7 @@ class CmdCWho(COMMAND_DEFAULT_CLASS):
self.msg(string) self.msg(string)
return return
channel = find_channel(self.caller, self.lhs) channel = self.search_channel(self.lhs)
if not channel: if not channel:
return return
if not channel.access(self.caller, "listen"): if not channel.access(self.caller, "listen"):
@ -1590,7 +1409,7 @@ class CmdCWho(COMMAND_DEFAULT_CLASS):
self.msg(string.strip()) self.msg(string.strip())
class CmdChannelCreate(COMMAND_DEFAULT_CLASS): class CmdChannelCreate(CmdChannel):
""" """
create a new channel create a new channel
@ -1611,8 +1430,6 @@ class CmdChannelCreate(COMMAND_DEFAULT_CLASS):
def func(self): def func(self):
"""Implement the command""" """Implement the command"""
caller = self.caller
if not self.args: if not self.args:
self.msg("Usage ccreate <channelname>[;alias;alias..] = description") self.msg("Usage ccreate <channelname>[;alias;alias..] = description")
return return
@ -1627,19 +1444,15 @@ class CmdChannelCreate(COMMAND_DEFAULT_CLASS):
if ";" in lhs: if ";" in lhs:
channame, aliases = lhs.split(";", 1) channame, aliases = lhs.split(";", 1)
aliases = [alias.strip().lower() for alias in aliases.split(";")] aliases = [alias.strip().lower() for alias in aliases.split(";")]
channel = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channame)
if channel: new_chan, err = self.create_channel(channame, description, aliases=aliases)
self.msg("A channel with that name already exists.") if new_chan:
return self.msg(f"Created channel {new_chan.key} and connected to it.")
# Create and set the channel up else:
lockstring = "send:all();listen:all();control:id(%s)" % caller.id self.msg(err)
new_chan = create.create_channel(channame.strip(), aliases, description, locks=lockstring)
new_chan.connect(caller)
CHANNELHANDLER.update()
self.msg("Created channel %s and connected to it." % new_chan.key)
class CmdClock(COMMAND_DEFAULT_CLASS): class CmdClock(CmdChannel):
""" """
change channel locks of a channel you control change channel locks of a channel you control
@ -1666,14 +1479,13 @@ class CmdClock(COMMAND_DEFAULT_CLASS):
self.msg(string) self.msg(string)
return return
channel = find_channel(self.caller, self.lhs) channel = self.search_channel(self.lhs)
if not channel: if not channel:
return return
if not self.rhs: if not self.rhs:
# no =, so just view the current locks # no =, so just view the current locks
string = "Current locks on %s:" % channel.key self.msg(f"Current locks on {channel.key}\n{channel.locks}")
string = "%s\n %s" % (string, channel.locks)
self.msg(string)
return return
# we want to add/change a lock. # we want to add/change a lock.
if not channel.access(self.caller, "control"): if not channel.access(self.caller, "control"):
@ -1681,18 +1493,13 @@ class CmdClock(COMMAND_DEFAULT_CLASS):
self.msg(string) self.msg(string)
return return
# Try to add the lock # Try to add the lock
try: success, err = self.set_lock(channel, self.rhs)
channel.locks.add(self.rhs) if success:
except LockException as err: self.msg(f"Lock(s) applied. Current locks on {channel.key}:\n{channel.locks}")
else:
self.msg(err) self.msg(err)
return
string = "Lock(s) applied. "
string += "Current locks on %s:" % channel.key
string = "%s\n %s" % (string, channel.locks)
self.msg(string)
class CmdCdesc(CmdChannel):
class CmdCdesc(COMMAND_DEFAULT_CLASS):
""" """
describe a channel you control describe a channel you control
@ -1701,6 +1508,7 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS):
Changes the description of the channel as shown in Changes the description of the channel as shown in
channel lists. channel lists.
""" """
key = "cdesc" key = "cdesc"
@ -1718,18 +1526,15 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS):
if not self.rhs: if not self.rhs:
self.msg("Usage: cdesc <channel> = <description>") self.msg("Usage: cdesc <channel> = <description>")
return return
channel = find_channel(caller, self.lhs) channel = self.search_channel(self.lhs)
if not channel: if not channel:
self.msg("Channel '%s' not found." % self.lhs)
return return
# check permissions # check permissions
if not channel.access(caller, "control"): if not channel.access(caller, "control"):
self.msg("You cannot admin this channel.") self.msg("You cannot admin this channel.")
return return
# set the description self.set_desc(channel, self.rhs)
channel.db.desc = self.rhs self.msg(f"Description of channel '{channel.key}' set to '{self.rhs}'.")
channel.save()
self.msg("Description of channel '%s' set to '%s'." % (channel.key, self.rhs))
class CmdPage(COMMAND_DEFAULT_CLASS): class CmdPage(COMMAND_DEFAULT_CLASS):
@ -1737,16 +1542,19 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
send a private message to another account send a private message to another account
Usage: Usage:
page <account> <message>
page[/switches] [<account>,<account>,... = <message>] page[/switches] [<account>,<account>,... = <message>]
tell '' tell ''
page <number> page <number>
Switch: Switches:
last - shows who you last messaged last - shows who you last messaged
list - show your last <number> of tells/pages (default) list - show your last <number> of tells/pages (default)
Send a message to target user (if online). If no Send a message to target user (if online). If no argument is given, you will
argument is given, you will get a list of your latest messages. get a list of your latest messages. The equal sign is needed for multiple
targets or if sending to target with space in the name.
""" """
key = "page" key = "page"
@ -1768,6 +1576,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
pages_we_sent = Msg.objects.get_messages_by_sender(caller, exclude_channel_messages=True) pages_we_sent = Msg.objects.get_messages_by_sender(caller, exclude_channel_messages=True)
# get last messages we've got # get last messages we've got
pages_we_got = Msg.objects.get_messages_by_receiver(caller) pages_we_got = Msg.objects.get_messages_by_receiver(caller)
targets, message, number = [], None, None
if "last" in self.switches: if "last" in self.switches:
if pages_we_sent: if pages_we_sent:
@ -1778,19 +1587,75 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
self.msg("You haven't paged anyone yet.") self.msg("You haven't paged anyone yet.")
return return
if not self.args or not self.rhs: if self.args:
pages = pages_we_sent + pages_we_got if self.rhs:
pages = sorted(pages, key=lambda page: page.date_created) for target in self.lhslist:
target_obj = self.caller.search(target)
if not target_obj:
return
targets.append(target_obj)
message = self.rhs.strip()
else:
target, *message = self.args.split(" ", 4)
if target and target.isnumeric():
# a number to specify a historic page
number = int(target)
elif target:
target_obj = self.caller.search(target, quiet=True)
if target_obj:
# a proper target
targets = [target_obj[0]]
else:
# a message with a space in it - put it back together
message = target + " " + (message[0] if message else "")
else:
# a single-word message
message = message[0].strip()
number = 5 pages = pages_we_sent + pages_we_got
if self.args: pages = sorted(pages, key=lambda page: page.date_created)
try:
number = int(self.args) if message:
except ValueError: # send a message
self.msg("Usage: tell [<account> = msg]") if not targets:
# no target given - send to last person we paged
if pages_we_sent:
targets = pages_we_sent[-1].receivers
else:
self.msg("Who do you want page?")
return return
if len(pages) > number: header = "|wAccount|n |c%s|n |wpages:|n" % caller.key
if message.startswith(":"):
message = "%s %s" % (caller.key, message.strip(":").strip())
# create the persistent message object
create.create_message(caller, message, receivers=targets)
# tell the accounts they got a message.
received = []
rstrings = []
for target in targets:
if not target.access(caller, "msg"):
rstrings.append(f"You are not allowed to page {target}.")
continue
target.msg(f"{header} {message}")
if hasattr(target, "sessions") and not target.sessions.count():
received.append(f"|C{target.name}|n")
rstrings.append(
f"{received[-1]} is offline. They will see your message "
"if they list their pages later."
)
else:
received.append(f"|c{target.name}|n")
if rstrings:
self.msg("\n".join(rstrings))
self.msg("You paged %s with: '%s'." % (", ".join(received), message))
return
else:
# no message to send
if number is not None and len(pages) > number:
lastpages = pages[-number:] lastpages = pages[-number:]
else: else:
lastpages = pages lastpages = pages
@ -1819,7 +1684,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
receiver = f"|n,{clr}".join([obj.name for obj in page.receivers]) receiver = f"|n,{clr}".join([obj.name for obj in page.receivers])
if sending: if sending:
template = to_template template = to_template
sender = f"{sender} " if multi_send else "" sender = f"{sender} " if multi_send else ""
receiver = f" {receiver}" if multi_recv else f" {receiver}" receiver = f" {receiver}" if multi_recv else f" {receiver}"
else: else:
template = from_template template = from_template
@ -1845,64 +1710,6 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
self.msg(string) self.msg(string)
return return
# We are sending. Build a list of targets
if not self.lhs:
# If there are no targets, then set the targets
# to the last person we paged.
if pages_we_sent:
receivers = pages_we_sent[-1].receivers
else:
self.msg("Who do you want to page?")
return
else:
receivers = self.lhslist
recobjs = []
for receiver in set(receivers):
if isinstance(receiver, str):
pobj = caller.search(receiver)
elif hasattr(receiver, "character"):
pobj = receiver
else:
self.msg("Who do you want to page?")
return
if pobj:
recobjs.append(pobj)
if not recobjs:
self.msg("Noone found to page.")
return
header = "|wAccount|n |c%s|n |wpages:|n" % caller.key
message = self.rhs
# if message begins with a :, we assume it is a 'page-pose'
if message.startswith(":"):
message = "%s %s" % (caller.key, message.strip(":").strip())
# create the persistent message object
create.create_message(caller, message, receivers=recobjs)
# tell the accounts they got a message.
received = []
rstrings = []
for pobj in recobjs:
if not pobj.access(caller, "msg"):
rstrings.append("You are not allowed to page %s." % pobj)
continue
pobj.msg("%s %s" % (header, message))
if hasattr(pobj, "sessions") and not pobj.sessions.count():
received.append("|C%s|n" % pobj.name)
rstrings.append(
"%s is offline. They will see your message if they list their pages later."
% received[-1]
)
else:
received.append("|c%s|n" % pobj.name)
if rstrings:
self.msg("\n".join(rstrings))
self.msg("You paged %s with: '%s'." % (", ".join(received), message))
def _list_bots(cmd): def _list_bots(cmd):
""" """
@ -1943,7 +1750,6 @@ def _list_bots(cmd):
else: else:
return "No irc bots found." return "No irc bots found."
class CmdIRC2Chan(COMMAND_DEFAULT_CLASS): class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
""" """
Link an evennia channel to an external IRC channel Link an evennia channel to an external IRC channel

View file

@ -11,13 +11,11 @@ import re
from django.conf import settings from django.conf import settings
from collections import defaultdict from collections import defaultdict
from evennia.utils.utils import fill, dedent from evennia.utils.utils import fill, dedent
from evennia.commands.command import Command
from evennia.help.models import HelpEntry from evennia.help.models import HelpEntry
from evennia.utils import create, evmore from evennia.utils import create, evmore
from evennia.utils.ansi import ANSIString from evennia.utils.ansi import ANSIString
from evennia.utils.eveditor import EvEditor from evennia.utils.eveditor import EvEditor
from evennia.utils.utils import ( from evennia.utils.utils import (
string_suggestions,
class_from_module, class_from_module,
inherits_from, inherits_from,
format_grid, pad format_grid, pad
@ -500,8 +498,6 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
""" """
caller = self.caller caller = self.caller
query, subtopics, cmdset = self.topic, self.subtopics, self.cmdset query, subtopics, cmdset = self.topic, self.subtopics, self.cmdset
suggestion_cutoff = self.suggestion_cutoff
suggestion_maxnum = self.suggestion_maxnum
# removing doublets in cmdset, caused by cmdhandler # removing doublets in cmdset, caused by cmdhandler
# having to allow doublet commands to manage exits etc. # having to allow doublet commands to manage exits etc.
@ -514,9 +510,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
] ]
all_categories = list(set( all_categories = list(set(
[HelpCategory(cmd.help_category) for cmd in all_cmds] [HelpCategory(cmd.help_category) for cmd in all_cmds]
+ [HelpCategory(topic.help_category) for topic in all_db_topics] + [HelpCategory(topic.help_category) for topic in all_db_topics]
) ))
)
if not query: if not query:
# list all available help entries, grouped by category. We want to # list all available help entries, grouped by category. We want to
@ -593,7 +588,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
topic = match.key topic = match.key
help_text = match.get_help(caller, cmdset) help_text = match.get_help(caller, cmdset)
aliases = match.aliases aliases = match.aliases
suggested=suggestions[1:] suggested = suggestions[1:]
else: else:
# a database match # a database match
topic = match.key topic = match.key

View file

@ -563,7 +563,7 @@ class TestHelp(CommandTest):
"Help for test/more/second-more\n\n" "Help for test/more/second-more\n\n"
"The Second More text.\n\n" "The Second More text.\n\n"
"Subtopics:\n" "Subtopics:\n"
" test/more/second-more/more again\n\n" " test/more/second-more/more again\n"
" test/more/second-more/third more" " test/more/second-more/third more"
), ),
("test/More/-more", # partial match ("test/More/-more", # partial match
@ -574,7 +574,7 @@ class TestHelp(CommandTest):
" test/more/second-more/third more" " test/more/second-more/third more"
), ),
("test/more/second/more again", ("test/more/second/more again",
"Help for test/more/second-more/more again\n" "Help for test/more/second-more/more again\n\n"
"Even more text.\n" "Even more text.\n"
), ),
("test/more/second/third", ("test/more/second/third",

View file

@ -1069,34 +1069,6 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
deferred.addCallback(_callback) deferred.addCallback(_callback)
return deferred return deferred
def test_autocmdsets(self):
import evennia
from evennia.commands.default.cmdset_account import AccountCmdSet
from evennia.comms.channelhandler import CHANNEL_HANDLER
testchannel = evennia.create_channel("channeltest", locks="listen:all();send:all()")
CHANNEL_HANDLER.add(testchannel)
CHANNEL_HANDLER.update()
self.assertTrue(testchannel.connect(self.account))
self.assertTrue(testchannel.has_connection(self.account))
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
self.set_cmdsets(self.account, a, b, c, d)
deferred = cmdhandler.get_and_merge_cmdsets(
self.session, self.session, self.account, self.char1, "session", ""
)
def _callback(cmdset):
pcmdset = AccountCmdSet()
pcmdset.at_cmdset_creation()
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] + ["out"]
self.assertTrue(
all(cmd.key or hasattr(cmd, "is_channel") in pcmds for cmd in cmdset.commands)
)
self.assertTrue(any(hasattr(cmd, "is_channel") for cmd in cmdset.commands))
deferred.addCallback(_callback)
return deferred
def test_duplicates(self): def test_duplicates(self):
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.no_exits = True a.no_exits = True

View file

@ -13,10 +13,6 @@ autobahn >= 17.9.3
lunr == 0.5.6 lunr == 0.5.6
simpleeval <= 1.0 simpleeval <= 1.0
# conjugation library, py3 version
git+https://github.com/markrogersjr/nodebox_linguistics_extended.git
# try to resolve dependency issue in py3.7 # try to resolve dependency issue in py3.7
attrs >= 19.2.0 attrs >= 19.2.0