Move alias/connect setup to channel class rather than cmd
This commit is contained in:
parent
8e19017dc3
commit
51bef9bf97
8 changed files with 224 additions and 84 deletions
|
|
@ -315,6 +315,12 @@ gets its data from. A channel's log will rotate when it grows too big, which
|
||||||
thus also automatically limits the max amount of history a user can view with
|
thus also automatically limits the max amount of history a user can view with
|
||||||
`/history`.
|
`/history`.
|
||||||
|
|
||||||
|
The log file name is set on the channel class as the `log_file` property. This
|
||||||
|
is a string that takes the formatting token `{channelname}` to be replaced with
|
||||||
|
the (lower-case) name of the channel. By default the log is written to in the
|
||||||
|
channel's `at_post_channel_msg` method.
|
||||||
|
|
||||||
|
|
||||||
### Properties on Channels
|
### Properties on Channels
|
||||||
|
|
||||||
Channels have all the standard properties of a Typeclassed entity (`key`,
|
Channels have all the standard properties of a Typeclassed entity (`key`,
|
||||||
|
|
@ -323,16 +329,24 @@ see the [Channel api docs](api:evennia.comms.comms.DefaultChannel) for details.
|
||||||
|
|
||||||
- `send_to_online_only` - this class boolean defaults to `True` and is a
|
- `send_to_online_only` - this class boolean defaults to `True` and is a
|
||||||
sensible optimization since people offline people will not see the message anyway.
|
sensible optimization since people offline people will not see the message anyway.
|
||||||
- `log_to_file` - this is a string that determines the name of the channel log file. Default
|
- `log_file` - this is a string that determines the name of the channel log file. Default
|
||||||
is `"channel_{channel_key}.log"`. You should usually not change this.
|
is `"channel_{channelname}.log"`. The log file will appear in `settings.LOG_DIR` (usually
|
||||||
|
`mygame/server/logs/`). You should usually not change this.
|
||||||
- `channel_prefix_string` - this property is a string to easily change how
|
- `channel_prefix_string` - this property is a string to easily change how
|
||||||
the channel is prefixed. It takes the `channel_key` format key. Default is `"[{channel_key}] "`
|
the channel is prefixed. It takes the `channelname` format key. Default is `"[{channelname}] "`
|
||||||
and produces output like `[public] ...``.
|
and produces output like `[public] ...``.
|
||||||
- `subscriptions` - this is the [SubscriptionHandler](`api:evennia.comms.comms.SubscriptionHandler`), which
|
- `subscriptions` - this is the [SubscriptionHandler](`api:evennia.comms.comms.SubscriptionHandler`), which
|
||||||
has methods `has`, `add`, `remove`, `all`, `clear` and also `online` (to get
|
has methods `has`, `add`, `remove`, `all`, `clear` and also `online` (to get
|
||||||
only actually online channel-members).
|
only actually online channel-members).
|
||||||
- `wholist`, `mutelist`, `banlist` are properties that return a list of subscribers,
|
- `wholist`, `mutelist`, `banlist` are properties that return a list of subscribers,
|
||||||
as well as who are currently muted or banned.
|
as well as who are currently muted or banned.
|
||||||
|
- `channel_msg_nick_pattern` - this is a regex pattern for performing the in-place nick
|
||||||
|
replacement (detect that `channelalias <msg` means that you want to send a message to a channel).
|
||||||
|
This pattern accepts an `{alias}` formatting marker. Don't mess with this unless you really
|
||||||
|
want to change how channels work.
|
||||||
|
- `channel_msg_nick_replacement` - this is a string on the [nick replacement
|
||||||
|
- form](Nicks). It accepts the `{channelname}` formatting tag. This is strongly tied to the
|
||||||
|
`channel` command and is by default `channel {channelname} = $1`.
|
||||||
|
|
||||||
Notable `Channel` hooks:
|
Notable `Channel` hooks:
|
||||||
|
|
||||||
|
|
@ -347,12 +361,19 @@ Notable `Channel` hooks:
|
||||||
also just remove that call.
|
also just remove that call.
|
||||||
- every channel message. By default it just returns `channel_prefix_string`.
|
- every channel message. By default it just returns `channel_prefix_string`.
|
||||||
- `has_connection(subscriber)` - shortcut to check if an entity subscribes to
|
- `has_connection(subscriber)` - shortcut to check if an entity subscribes to
|
||||||
this channel
|
this channel.
|
||||||
- `mute/unmute(subscriber)` - this mutes the channel for this user.
|
- `mute/unmute(subscriber)` - this mutes the channel for this user.
|
||||||
- `ban/unban(subscriber)` - adds/remove user from banlist.
|
- `ban/unban(subscriber)` - adds/remove user from banlist.
|
||||||
- `connect/disconnect(subscriber)` - adds/removes a subscriber.
|
- `connect/disconnect(subscriber)` - adds/removes a subscriber.
|
||||||
|
- `add_user_channel_alias(user, alias, **kwargs)` - sets up a user-nick for this channel. This is
|
||||||
|
what maps e.g. `alias <msg>` to `channel channelname = <msg>`.
|
||||||
|
- `remove_user_channel_alias(user, alias, **kwargs)` - remove an alias. Note that this is
|
||||||
|
a class-method that will happily remove found channel-aliases from the user linked to _any_
|
||||||
|
channel, not only from the channel the method is called on.
|
||||||
- `pre_join_channel(subscriber)` - if this returns `False`, connection will be refused.
|
- `pre_join_channel(subscriber)` - if this returns `False`, connection will be refused.
|
||||||
- `post_join_channel(subscriber)` - unused by default.
|
- `post_join_channel(subscriber)` - by default this sets up a users's channel-nicks/aliases.
|
||||||
- `pre_leave_channel(subscriber)` - if this returns `False`, the user is not allowed to leave.
|
- `pre_leave_channel(subscriber)` - if this returns `False`, the user is not allowed to leave.
|
||||||
- `post_leave_channel(subscriber)` - unused by default.
|
- `post_leave_channel(subscriber)` - this will clean up any channel aliases/nicks of the user.
|
||||||
|
- `delete` the standard typeclass-delete mechanism will also automatically un-subscribe all
|
||||||
|
subscribers (and thus wipe all their aliases).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ 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.locks.lockhandler import LockException
|
from evennia.locks.lockhandler import LockException
|
||||||
|
from evennia.comms.comms import DefaultChannel
|
||||||
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 class_from_module
|
from evennia.utils.utils import class_from_module
|
||||||
|
|
@ -229,17 +230,6 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
# disable this in child command classes if wanting on-character channels
|
# disable this in child command classes if wanting on-character channels
|
||||||
account_caller = True
|
account_caller = True
|
||||||
|
|
||||||
# note - changing this will invalidate existing aliases in db
|
|
||||||
# channel_msg_nick_alias = r"{alias}\s*?(?P<arg1>.+?){{0,1}}"
|
|
||||||
channel_msg_nick_alias = r"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)"
|
|
||||||
channel_msg_nick_replacement = "channel {channelname} = $1"
|
|
||||||
|
|
||||||
# to make it easier to override help functionality, we add the ability to
|
|
||||||
# tweak access to different sub-functionality. Note that the system will
|
|
||||||
# still check control lock etc even if you can use this functionality.
|
|
||||||
# changing these does not change access to this command itself (that's the
|
|
||||||
# locks property)
|
|
||||||
|
|
||||||
def search_channel(self, channelname, exact=False, handle_errors=True):
|
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
|
||||||
|
|
@ -323,9 +313,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
log_file = channel.get_log_filename()
|
||||||
log_file = channel.attributes.get(
|
|
||||||
"log_file", default=channel.log_to_file.format(channel_key=channel.key))
|
|
||||||
|
|
||||||
def send_msg(lines):
|
def send_msg(lines):
|
||||||
return self.msg(
|
return self.msg(
|
||||||
|
|
@ -351,11 +339,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if channel.has_connection(caller):
|
if channel.has_connection(caller):
|
||||||
return False, f"Already listening to channel {channel.key}."
|
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()]
|
# this sets up aliases in post_join_channel by default
|
||||||
for key_or_alias in key_and_aliases:
|
result = channel.connect(caller)
|
||||||
self.add_alias(channel, key_or_alias)
|
|
||||||
|
|
||||||
return result, "" if result else f"Were not allowed to subscribe to channel {channel.key}"
|
return result, "" if result else f"Were not allowed to subscribe to channel {channel.key}"
|
||||||
|
|
||||||
|
|
@ -377,14 +363,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if not channel.has_connection(caller):
|
if not channel.has_connection(caller):
|
||||||
return False, f"Not listening to channel {channel.key}."
|
return False, f"Not listening to channel {channel.key}."
|
||||||
# clear aliases
|
|
||||||
for key_or_alias in self.get_channel_aliases(channel):
|
|
||||||
self.remove_alias(key_or_alias, **kwargs)
|
|
||||||
# remove the channel-name alias too
|
|
||||||
msg_alias = self.channel_msg_nick_alias.format(alias=channel.key.lower())
|
|
||||||
caller.nicks.remove(msg_alias, category="inputline", **kwargs)
|
|
||||||
|
|
||||||
|
# this will also clean aliases
|
||||||
result = channel.disconnect(caller)
|
result = channel.disconnect(caller)
|
||||||
|
|
||||||
return result, "" if result else f"Could not unsubscribe from channel {channel.key}"
|
return result, "" if result else f"Could not unsubscribe from channel {channel.key}"
|
||||||
|
|
||||||
def add_alias(self, channel, alias, **kwargs):
|
def add_alias(self, channel, alias, **kwargs):
|
||||||
|
|
@ -401,7 +383,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
we need to be able to reference this channel easily. The other
|
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
|
is a templated nick to easily be able to send messages to the
|
||||||
channel without needing to give the full `channel` command. The
|
channel without needing to give the full `channel` command. The
|
||||||
structure of this nick is given by `self.channel_msg_nick_alias`
|
structure of this nick is given by `self.channel_msg_pattern`
|
||||||
and `self.channel_msg_nick_replacement`. By default it maps
|
and `self.channel_msg_nick_replacement`. By default it maps
|
||||||
`alias <msg> -> channel <channelname> = <msg>`, so that you can
|
`alias <msg> -> channel <channelname> = <msg>`, so that you can
|
||||||
for example just write `pub Hello` to send a message.
|
for example just write `pub Hello` to send a message.
|
||||||
|
|
@ -410,16 +392,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
for sending to channel using the main channel command.
|
for sending to channel using the main channel command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
chan_key = channel.key.lower()
|
channel.add_user_channel_alias(self.caller, alias, **kwargs)
|
||||||
# the message-pattern allows us to type the channel on its own without
|
|
||||||
# needing to use the `channel` command explicitly.
|
|
||||||
msg_pattern = self.channel_msg_nick_alias.format(alias=alias)
|
|
||||||
msg_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key)
|
|
||||||
|
|
||||||
if chan_key != alias:
|
|
||||||
self.caller.nicks.add(alias, chan_key, category="channel", **kwargs)
|
|
||||||
self.caller.nicks.add(msg_pattern, msg_replacement, category="inputline",
|
|
||||||
pattern_is_regex=True, **kwargs)
|
|
||||||
|
|
||||||
def remove_alias(self, alias, **kwargs):
|
def remove_alias(self, alias, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -440,13 +413,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
nick used for easily sending messages to the channel.
|
nick used for easily sending messages to the channel.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
if self.caller.nicks.has(alias, category="channel", **kwargs):
|
||||||
if caller.nicks.get(alias, category="channel", **kwargs):
|
DefaultChannel.remove_user_channel_alias(self.caller, alias)
|
||||||
caller.nicks.remove(alias, category="chan nel", **kwargs)
|
|
||||||
msg_alias = self.channel_msg_nick_alias.format(alias=alias)
|
|
||||||
caller.nicks.remove(msg_alias, category="inputline", **kwargs)
|
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
return False, "No such alias was defined."
|
return False, "No such alias was defined."
|
||||||
|
|
||||||
def get_channel_aliases(self, channel):
|
def get_channel_aliases(self, channel):
|
||||||
|
|
@ -462,7 +431,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
chan_key = channel.key.lower()
|
chan_key = channel.key.lower()
|
||||||
nicktuples = self.caller.nicks.get(category="channel", return_tuple=True)
|
nicktuples = self.caller.nicks.get(category="channel", return_tuple=True, return_list=True)
|
||||||
if nicktuples:
|
if nicktuples:
|
||||||
return [tup[2] for tup in nicktuples if tup[3].lower() == chan_key]
|
return [tup[2] for tup in nicktuples if tup[3].lower() == chan_key]
|
||||||
return []
|
return []
|
||||||
|
|
@ -915,6 +884,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
# first 'channel name' is in fact 'channelname text'
|
# first 'channel name' is in fact 'channelname text'
|
||||||
no_rhs_channel_name = self.args.split(" ", 1)[0]
|
no_rhs_channel_name = self.args.split(" ", 1)[0]
|
||||||
possible_lhs_message = self.args[len(no_rhs_channel_name):]
|
possible_lhs_message = self.args[len(no_rhs_channel_name):]
|
||||||
|
if possible_lhs_message.strip() == '=':
|
||||||
|
possible_lhs_message = ""
|
||||||
channel_names.append(no_rhs_channel_name)
|
channel_names.append(no_rhs_channel_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -952,13 +923,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
subscribed, available = self.list_channels()
|
subscribed, available = self.list_channels()
|
||||||
if channel in subscribed:
|
if channel in subscribed:
|
||||||
table = self.display_subbed_channels([channel])
|
table = self.display_subbed_channels([channel])
|
||||||
inputname = self.raw_cmdname
|
header = f"Channel |w{channel.key}|n"
|
||||||
if inputname.lower() != channel.key.lower():
|
self.msg(f"{header}\n(use |w{channel.key} <msg>|n (or a channel-alias) "
|
||||||
header = f"Channel |w{inputname}|n (alias for {channel.key} channel)"
|
f"to chat and the 'channel' command "
|
||||||
else:
|
f"to customize)\n{table}")
|
||||||
header = f"Channel |w{channel.key}|n"
|
|
||||||
self.msg(f"{header}\n(use |w{inputname} <msg>|n to chat and "
|
|
||||||
f"the 'channel' command to customize)\n{table}")
|
|
||||||
elif channel in available:
|
elif channel in available:
|
||||||
table = self.display_all_channels([], [channel])
|
table = self.display_all_channels([], [channel])
|
||||||
self.msg(
|
self.msg(
|
||||||
|
|
@ -1055,7 +1023,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
ask_yes_no(
|
ask_yes_no(
|
||||||
caller,
|
caller,
|
||||||
prompt=f"Are you sure you want to delete channel '{channel.key}' "
|
prompt=f"Are you sure you want to delete channel '{channel.key}' "
|
||||||
"(make sure name is correct!)? This will disconnect and "
|
"(make sure name is correct!)?\nThis will disconnect and "
|
||||||
"remove all users' aliases. {options}?",
|
"remove all users' aliases. {options}?",
|
||||||
yes_action=_perform_delete,
|
yes_action=_perform_delete,
|
||||||
no_action="Aborted.",
|
no_action="Aborted.",
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
Class-level variables:
|
Class-level variables:
|
||||||
- `send_to_online_only` (bool, default True) - if set, will only try to
|
- `send_to_online_only` (bool, default True) - if set, will only try to
|
||||||
send to subscribers that are actually active. This is a useful optimization.
|
send to subscribers that are actually active. This is a useful optimization.
|
||||||
- `log_to_file` (str, default `"channel_{channel_key}.log"`). This is the
|
- `log_file` (str, default `"channel_{channelname}.log"`). This is the
|
||||||
log file to which the channel history will be saved. The `{channel_key}` tag
|
log file to which the channel history will be saved. The `{channelname}` tag
|
||||||
will be replaced by the key of the Channel. If an Attribute 'log_file'
|
will be replaced by the key of the Channel. If an Attribute 'log_file'
|
||||||
is set, this will be used instead. If this is None and no Attribute is found,
|
is set, this will be used instead. If this is None and no Attribute is found,
|
||||||
no history will be saved.
|
no history will be saved.
|
||||||
- `channel_prefix_string` (str, default `"[{channel_key} ]"`) - this is used
|
- `channel_prefix_string` (str, default `"[{channelname} ]"`) - this is used
|
||||||
as a simple template to get the channel prefix with `.channel_prefix()`.
|
as a simple template to get the channel prefix with `.channel_prefix()`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -40,10 +40,15 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
send_to_online_only = True
|
send_to_online_only = True
|
||||||
# store log in log file. `channel_key tag will be replace with key of channel.
|
# store log in log file. `channel_key tag will be replace with key of channel.
|
||||||
# Will use log_file Attribute first, if given
|
# Will use log_file Attribute first, if given
|
||||||
log_to_file = "channel_{channel_key}.log"
|
log_file = "channel_{channelname}.log"
|
||||||
# which prefix to use when showing were a message is coming from. Set to
|
# which prefix to use when showing were a message is coming from. Set to
|
||||||
# None to disable and set this later.
|
# None to disable and set this later.
|
||||||
channel_prefix_string = "[{channel_key}] "
|
channel_prefix_string = "[{channelname}] "
|
||||||
|
|
||||||
|
# default nick-alias replacements (default using the 'channel' command)
|
||||||
|
channel_msg_nick_pattern = r"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)"
|
||||||
|
channel_msg_nick_replacement = "channel {channelname} = $1"
|
||||||
|
|
||||||
|
|
||||||
def at_first_save(self):
|
def at_first_save(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -54,7 +59,6 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
self.basetype_setup()
|
self.basetype_setup()
|
||||||
self.at_channel_creation()
|
self.at_channel_creation()
|
||||||
self.attributes.add("log_file", "channel_%s.log" % self.key)
|
|
||||||
if hasattr(self, "_createdict"):
|
if hasattr(self, "_createdict"):
|
||||||
# this is only set if the channel was created
|
# this is only set if the channel was created
|
||||||
# with the utils.create.create_channel function.
|
# with the utils.create.create_channel function.
|
||||||
|
|
@ -78,6 +82,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
def basetype_setup(self):
|
def basetype_setup(self):
|
||||||
self.locks.add("send:all();listen:all();control:perm(Admin)")
|
self.locks.add("send:all();listen:all();control:perm(Admin)")
|
||||||
|
|
||||||
|
# make sure we don't have access to a same-named old channel's history.
|
||||||
|
log_file = self.get_log_filename()
|
||||||
|
logger.rotate_log_file(log_file, num_lines_to_append=0)
|
||||||
|
|
||||||
def at_channel_creation(self):
|
def at_channel_creation(self):
|
||||||
"""
|
"""
|
||||||
Called once, when the channel is first created.
|
Called once, when the channel is first created.
|
||||||
|
|
@ -87,6 +95,33 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
# helper methods, for easy overloading
|
# helper methods, for easy overloading
|
||||||
|
|
||||||
|
_log_file = None
|
||||||
|
|
||||||
|
def get_log_filename(self):
|
||||||
|
"""
|
||||||
|
File name to use for channel log.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The filename to use (this is always assumed to be inside
|
||||||
|
settings.LOG_DIR)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self._log_file:
|
||||||
|
self._log_file = self.attributes.get(
|
||||||
|
"log_file", self.log_file.format(channelname=self.key.lower()))
|
||||||
|
return self._log_file
|
||||||
|
|
||||||
|
def set_log_filename(self, filename):
|
||||||
|
"""
|
||||||
|
Set a custom log filename.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): The filename to set. This is a path starting from
|
||||||
|
inside the settings.LOG_DIR location.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.attributes.add("log_file", filename)
|
||||||
|
|
||||||
def has_connection(self, subscriber):
|
def has_connection(self, subscriber):
|
||||||
"""
|
"""
|
||||||
Checks so this account is actually listening
|
Checks so this account is actually listening
|
||||||
|
|
@ -368,6 +403,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
self.attributes.clear()
|
self.attributes.clear()
|
||||||
self.aliases.clear()
|
self.aliases.clear()
|
||||||
|
for subscriber in self.subscriptions.all():
|
||||||
|
self.disconnect(subscriber)
|
||||||
super().delete()
|
super().delete()
|
||||||
|
|
||||||
def channel_prefix(self):
|
def channel_prefix(self):
|
||||||
|
|
@ -378,7 +415,73 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
str: The channel prefix.
|
str: The channel prefix.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.channel_prefix_string.format(channel_key=self.key)
|
return self.channel_prefix_string.format(channelname=self.key)
|
||||||
|
|
||||||
|
def add_user_channel_alias(self, user, alias, **kwargs):
|
||||||
|
"""
|
||||||
|
Add a personal user-alias for this channel to a given subscriber.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (Object or Account): The one to alias this channel.
|
||||||
|
alias (str): The desired alias.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This is tightly coupled to the default `channel` command. If you
|
||||||
|
change that, you need to change this as well.
|
||||||
|
|
||||||
|
We add two nicks - one is a plain `alias -> channel.key` that
|
||||||
|
users 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_pattern`
|
||||||
|
and `self.channel_msg_nick_replacement`. By default it maps
|
||||||
|
`alias <msg> -> channel <channelname> = <msg>`, 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
chan_key = self.key.lower()
|
||||||
|
|
||||||
|
# the message-pattern allows us to type the channel on its own without
|
||||||
|
# needing to use the `channel` command explicitly.
|
||||||
|
msg_nick_pattern = self.channel_msg_nick_pattern.format(alias=alias)
|
||||||
|
msg_nick_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key)
|
||||||
|
user.nicks.add(msg_nick_pattern, msg_nick_replacement, category="inputline",
|
||||||
|
pattern_is_regex=True, **kwargs)
|
||||||
|
|
||||||
|
if chan_key != alias:
|
||||||
|
# this allows for using the alias for general channel lookups
|
||||||
|
user.nicks.add(alias, chan_key, category="channel", **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_user_channel_alias(cls, user, alias, **kwargs):
|
||||||
|
"""
|
||||||
|
Remove a personal channel alias from a user.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (Object or Account): The user to remove an alias from.
|
||||||
|
alias (str): The alias to remove.
|
||||||
|
**kwargs: Unused by default. Can be used to pass extra variables
|
||||||
|
into a custom implementation.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The channel-alias actually consists of two aliases - one
|
||||||
|
channel-based one for searching channels with the alias and one
|
||||||
|
inputline one for doing the 'channelalias msg' - call.
|
||||||
|
|
||||||
|
This is a classmethod because it doesn't actually operate on the
|
||||||
|
channel instance.
|
||||||
|
|
||||||
|
It sits on the channel because the nick structure for this is
|
||||||
|
pretty complex and needs to be located in a central place (rather
|
||||||
|
on, say, the channel command).
|
||||||
|
|
||||||
|
"""
|
||||||
|
user.nicks.remove(alias, category="channel", **kwargs)
|
||||||
|
msg_nick_pattern = cls.channel_msg_nick_pattern.format(alias=alias)
|
||||||
|
user.nicks.remove(msg_nick_pattern, category="inputline", **kwargs)
|
||||||
|
|
||||||
def at_pre_msg(self, message, **kwargs):
|
def at_pre_msg(self, message, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -472,9 +575,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# save channel history to log file
|
# save channel history to log file
|
||||||
default_log_file = (self.log_to_file.format(channel_key=self.key)
|
log_file = self.get_log_filename()
|
||||||
if self.log_to_file else None)
|
|
||||||
log_file = self.attributes.get("log_file", default=default_log_file)
|
|
||||||
if log_file:
|
if log_file:
|
||||||
senders = ",".join(sender.key for sender in kwargs.get("senders", []))
|
senders = ",".join(sender.key for sender in kwargs.get("senders", []))
|
||||||
senders = f"{senders}: " if senders else ""
|
senders = f"{senders}: " if senders else ""
|
||||||
|
|
@ -506,8 +607,13 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
**kwargs (dict): Arbitrary, optional arguments for users
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
overriding the call (unused by default).
|
overriding the call (unused by default).
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
By default this adds the needed channel nicks to the joiner.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
key_and_aliases = [self.key.lower()] + [alias.lower() for alias in self.aliases.all()]
|
||||||
|
for key_or_alias in key_and_aliases:
|
||||||
|
self.add_user_channel_alias(joiner, key_or_alias, **kwargs)
|
||||||
|
|
||||||
def pre_leave_channel(self, leaver, **kwargs):
|
def pre_leave_channel(self, leaver, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -535,7 +641,12 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
overriding the call (unused by default).
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
chan_key = self.key.lower()
|
||||||
|
key_or_aliases = [self.key.lower()] + [alias.lower() for alias in self.aliases.all()]
|
||||||
|
nicktuples = leaver.nicks.get(category="channel", return_tuple=True, return_list=True)
|
||||||
|
key_or_aliases += [tup[2] for tup in nicktuples if tup[3].lower() == chan_key]
|
||||||
|
for key_or_alias in key_or_aliases:
|
||||||
|
self.remove_user_channel_alias(leaver, key_or_alias, **kwargs)
|
||||||
|
|
||||||
def at_init(self):
|
def at_init(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ class CraftingRecipeBase:
|
||||||
are optional but will be passed into all of the following hooks.
|
are optional but will be passed into all of the following hooks.
|
||||||
2. `.pre_craft(**kwargs)` - this normally validates inputs and stores them in
|
2. `.pre_craft(**kwargs)` - this normally validates inputs and stores them in
|
||||||
`.validated_inputs.`. Raises `CraftingValidationError` otherwise.
|
`.validated_inputs.`. Raises `CraftingValidationError` otherwise.
|
||||||
4. `.craft(**kwargs)` - should return the crafted item(s) or the empty list. Any
|
4. `.do_craft(**kwargs)` - should return the crafted item(s) or the empty list. Any
|
||||||
crafting errors should be immediately reported to user.
|
crafting errors should be immediately reported to user.
|
||||||
5. `.post_craft(crafted_result, **kwargs)`- always called, even if `pre_craft`
|
5. `.post_craft(crafted_result, **kwargs)`- always called, even if `pre_craft`
|
||||||
raised a `CraftingError` or `CraftingValidationError`.
|
raised a `CraftingError` or `CraftingValidationError`.
|
||||||
|
|
@ -252,7 +252,7 @@ class CraftingRecipeBase:
|
||||||
else:
|
else:
|
||||||
raise CraftingValidationError
|
raise CraftingValidationError
|
||||||
|
|
||||||
def craft(self, **kwargs):
|
def do_craft(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook to override.
|
Hook to override.
|
||||||
|
|
||||||
|
|
@ -277,7 +277,7 @@ class CraftingRecipeBase:
|
||||||
method is to delete the inputs.
|
method is to delete the inputs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
crafting_result (any): The outcome of crafting, as returned by `craft()`.
|
crafting_result (any): The outcome of crafting, as returned by `do_craft`.
|
||||||
**kwargs: Any extra flags passed at initialization.
|
**kwargs: Any extra flags passed at initialization.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -324,7 +324,7 @@ class CraftingRecipeBase:
|
||||||
if raise_exception:
|
if raise_exception:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
craft_result = self.craft(**craft_kwargs)
|
craft_result = self.do_craft(**craft_kwargs)
|
||||||
finally:
|
finally:
|
||||||
craft_result = self.post_craft(craft_result, **craft_kwargs)
|
craft_result = self.post_craft(craft_result, **craft_kwargs)
|
||||||
except (CraftingError, CraftingValidationError):
|
except (CraftingError, CraftingValidationError):
|
||||||
|
|
@ -455,7 +455,7 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
3. `.pre_craft(**kwargs)` should handle validation of inputs. Results should
|
3. `.pre_craft(**kwargs)` should handle validation of inputs. Results should
|
||||||
be stored in `validated_consumables/tools` respectively. Raises `CraftingValidationError`
|
be stored in `validated_consumables/tools` respectively. Raises `CraftingValidationError`
|
||||||
otherwise.
|
otherwise.
|
||||||
4. `.craft(**kwargs)` will not be called if validation failed. Should return
|
4. `.do_craft(**kwargs)` will not be called if validation failed. Should return
|
||||||
a list of the things crafted.
|
a list of the things crafted.
|
||||||
5. `.post_craft(crafting_result, **kwargs)` is always called, also if validation
|
5. `.post_craft(crafting_result, **kwargs)` is always called, also if validation
|
||||||
failed (`crafting_result` will then be falsy). It does any cleanup. By default
|
failed (`crafting_result` will then be falsy). It does any cleanup. By default
|
||||||
|
|
@ -819,7 +819,7 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
self.validated_tools = tools
|
self.validated_tools = tools
|
||||||
self.validated_consumables = consumables
|
self.validated_consumables = consumables
|
||||||
|
|
||||||
def craft(self, **kwargs):
|
def do_craft(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook to override. This will not be called if validation in `pre_craft`
|
Hook to override. This will not be called if validation in `pre_craft`
|
||||||
fails.
|
fails.
|
||||||
|
|
@ -847,7 +847,7 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
this method is to delete the inputs.
|
this method is to delete the inputs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
craft_result (list): The crafted result, provided by `self.craft()`.
|
craft_result (list): The crafted result, provided by `self.do_craft`.
|
||||||
**kwargs (any): Passed from `self.craft`.
|
**kwargs (any): Passed from `self.craft`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class _SwordSmithingBaseRecipe(CraftingRecipe):
|
||||||
"You work and work but you are not happy with the result. You need to start over."
|
"You work and work but you are not happy with the result. You need to start over."
|
||||||
)
|
)
|
||||||
|
|
||||||
def do_craft(self, **kwargs):
|
def craft(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Making a sword blade takes skill. Here we emulate this by introducing a
|
Making a sword blade takes skill. Here we emulate this by introducing a
|
||||||
random chance of failure (in a real game this could be a skill check
|
random chance of failure (in a real game this could be a skill check
|
||||||
|
|
@ -126,7 +126,7 @@ class _SwordSmithingBaseRecipe(CraftingRecipe):
|
||||||
if random.random() < 0.8:
|
if random.random() < 0.8:
|
||||||
# 80% chance of success. This will spawn the sword and show
|
# 80% chance of success. This will spawn the sword and show
|
||||||
# success-message.
|
# success-message.
|
||||||
return super().do_craft(**kwargs)
|
return super().craft(**kwargs)
|
||||||
else:
|
else:
|
||||||
# fail and show failed message
|
# fail and show failed message
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ class TestCraftingRecipeBase(TestCase):
|
||||||
"""Test craft hook, the main access method."""
|
"""Test craft hook, the main access method."""
|
||||||
|
|
||||||
expected_result = _TestMaterial("test_result")
|
expected_result = _TestMaterial("test_result")
|
||||||
self.recipe.craft = mock.MagicMock(return_value=expected_result)
|
self.recipe.do_craft = mock.MagicMock(return_value=expected_result)
|
||||||
|
|
||||||
self.assertTrue(self.recipe.allow_craft)
|
self.assertTrue(self.recipe.allow_craft)
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ class TestCraftingRecipeBase(TestCase):
|
||||||
|
|
||||||
# check result
|
# check result
|
||||||
self.assertEqual(result, expected_result)
|
self.assertEqual(result, expected_result)
|
||||||
self.recipe.craft.assert_called_with(kw1=1, kw2=2)
|
self.recipe.do_craft.assert_called_with(kw1=1, kw2=2)
|
||||||
|
|
||||||
# since allow_reuse is False, this usage should now be turned off
|
# since allow_reuse is False, this usage should now be turned off
|
||||||
self.assertFalse(self.recipe.allow_craft)
|
self.assertFalse(self.recipe.allow_craft)
|
||||||
|
|
@ -110,7 +110,7 @@ class TestCraftingRecipeBase(TestCase):
|
||||||
def test_craft_hook__fail(self):
|
def test_craft_hook__fail(self):
|
||||||
"""Test failing the call"""
|
"""Test failing the call"""
|
||||||
|
|
||||||
self.recipe.craft = mock.MagicMock(return_value=None)
|
self.recipe.do_craft = mock.MagicMock(return_value=None)
|
||||||
|
|
||||||
# trigger exception
|
# trigger exception
|
||||||
with self.assertRaises(crafting.CraftingError):
|
with self.assertRaises(crafting.CraftingError):
|
||||||
|
|
@ -213,7 +213,7 @@ class TestCraftingRecipe(TestCase):
|
||||||
self.assertIsNotNone(self.tool1.pk)
|
self.assertIsNotNone(self.tool1.pk)
|
||||||
self.assertIsNotNone(self.tool2.pk)
|
self.assertIsNotNone(self.tool2.pk)
|
||||||
|
|
||||||
def test_seed__succcess(self):
|
def test_seed__success(self):
|
||||||
"""Test seed helper classmethod"""
|
"""Test seed helper classmethod"""
|
||||||
|
|
||||||
# needed for other dbs to pass seed
|
# needed for other dbs to pass seed
|
||||||
|
|
|
||||||
|
|
@ -995,7 +995,8 @@ class AttributeHandler:
|
||||||
looked-after Attribute.
|
looked-after Attribute.
|
||||||
default_access (bool, optional): If no `attrread` lock is set on
|
default_access (bool, optional): If no `attrread` lock is set on
|
||||||
object, this determines if the lock should then be passed or not.
|
object, this determines if the lock should then be passed or not.
|
||||||
return_list (bool, optional):
|
return_list (bool, optional): Always return a list, also if there is only
|
||||||
|
one or zero matches found.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
result (any or list): One or more matches for keys and/or
|
result (any or list): One or more matches for keys and/or
|
||||||
|
|
|
||||||
|
|
@ -357,12 +357,14 @@ class EvenniaLogFile(logfile.LogFile):
|
||||||
_CHANNEL_LOG_NUM_TAIL_LINES = settings.CHANNEL_LOG_NUM_TAIL_LINES
|
_CHANNEL_LOG_NUM_TAIL_LINES = settings.CHANNEL_LOG_NUM_TAIL_LINES
|
||||||
num_lines_to_append = _CHANNEL_LOG_NUM_TAIL_LINES
|
num_lines_to_append = _CHANNEL_LOG_NUM_TAIL_LINES
|
||||||
|
|
||||||
def rotate(self):
|
def rotate(self, num_lines_to_append=None):
|
||||||
"""
|
"""
|
||||||
Rotates our log file and appends some number of lines from
|
Rotates our log file and appends some number of lines from
|
||||||
the previous log to the start of the new one.
|
the previous log to the start of the new one.
|
||||||
"""
|
"""
|
||||||
append_tail = self.num_lines_to_append > 0
|
append_tail = (num_lines_to_append
|
||||||
|
if num_lines_to_append is not None
|
||||||
|
else self.num_lines_to_append)
|
||||||
if not append_tail:
|
if not append_tail:
|
||||||
logfile.LogFile.rotate(self)
|
logfile.LogFile.rotate(self)
|
||||||
return
|
return
|
||||||
|
|
@ -472,6 +474,43 @@ def log_file(msg, filename="game.log"):
|
||||||
deferToThread(callback, filehandle, msg).addErrback(errback)
|
deferToThread(callback, filehandle, msg).addErrback(errback)
|
||||||
|
|
||||||
|
|
||||||
|
def log_file_exists(filename="game.log"):
|
||||||
|
"""
|
||||||
|
Determine if a log-file already exists.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): The filename (within the log-dir).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: If the log file exists or not.
|
||||||
|
|
||||||
|
"""
|
||||||
|
global _LOGDIR
|
||||||
|
if not _LOGDIR:
|
||||||
|
from django.conf import settings
|
||||||
|
_LOGDIR = settings.LOG_DIR
|
||||||
|
|
||||||
|
filename = os.path.join(_LOGDIR, filename)
|
||||||
|
return os.path.exists(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def rotate_log_file(filename="game.log", num_lines_to_append=None):
|
||||||
|
"""
|
||||||
|
Force-rotate a log-file, without
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): The log file, located in settings.LOG_DIR.
|
||||||
|
num_lines_to_append (int, optional): Include N number of
|
||||||
|
lines from previous file in new one. If `None`, use default.
|
||||||
|
Set to 0 to include no lines.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if log_file_exists(filename):
|
||||||
|
file_handle = _open_log_file(filename)
|
||||||
|
if file_handle:
|
||||||
|
file_handle.rotate(num_lines_to_append=num_lines_to_append)
|
||||||
|
|
||||||
|
|
||||||
def tail_log_file(filename, offset, nlines, callback=None):
|
def tail_log_file(filename, offset, nlines, callback=None):
|
||||||
"""
|
"""
|
||||||
Return the tail of the log file.
|
Return the tail of the log file.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue