"""
The channel handler, accessed from this module as CHANNEL_HANDLER is a
singleton that handles the stored set of channels and how they are
represented against the cmdhandler.
If there is a channel named 'newbie', we want to be able to just write
newbie Hello!
For this to work, 'newbie', the name of the channel, must be
identified by the cmdhandler as a command name. The channelhandler
stores all channels as custom 'commands' that the cmdhandler can
import and look through.
> Warning - channel names take precedence over command names, so make
sure to not pick clashing channel names.
Unless deleting a channel you normally don't need to bother about the
channelhandler at all - the create_channel method handles the update.
To delete a channel cleanly, delete the channel object, then call
update() on the channelhandler. Or use Channel.objects.delete() which
does this for you.
"""
from django.conf import settings
from evennia.commands import cmdset
from evennia.utils.logger import tail_log_file
from evennia.utils.utils import class_from_module
from django.utils.translation import gettext as _
# we must late-import these since any overloads are likely to
# themselves be using these classes leading to a circular import.
_CHANNEL_HANDLER_CLASS = None
_CHANNEL_COMMAND_CLASS = None
_CHANNELDB = None
_COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
[docs]class ChannelCommand(_COMMAND_DEFAULT_CLASS):
"""
{channelkey} channel
{channeldesc}
Usage:
{lower_channelkey} <message>
{lower_channelkey}/history [start]
{lower_channelkey} off - mutes the channel
{lower_channelkey} on - unmutes the channel
Switch:
history: View 20 previous messages, either from the end or
from <start> number of messages from the end.
Example:
{lower_channelkey} Hello World!
{lower_channelkey}/history
{lower_channelkey}/history 30
"""
# ^note that channeldesc and lower_channelkey will be filled
# automatically by ChannelHandler
# this flag is what identifies this cmd as a channel cmd
# and branches off to the system send-to-channel command
# (which is customizable by admin)
is_channel = True
key = "general"
help_category = "Channel Names"
obj = None
arg_regex = r"\s.*?|/history.*?"
[docs] def parse(self):
"""
Simple parser
"""
# cmdhandler sends channame:msg here.
channelname, msg = self.args.split(":", 1)
self.history_start = None
if msg.startswith("/history"):
arg = msg[8:]
try:
self.history_start = int(arg) if arg else 0
except ValueError:
# if no valid number was given, ignore it
pass
self.args = (channelname.strip(), msg.strip())
[docs] def func(self):
"""
Create a new message and send it to channel, using
the already formatted input.
"""
global _CHANNELDB
if not _CHANNELDB:
from evennia.comms.models import ChannelDB as _CHANNELDB
channelkey, msg = self.args
caller = self.caller
if not msg:
self.msg(_("Say what?"))
return
channel = _CHANNELDB.objects.get_channel(channelkey)
if not channel:
self.msg(_("Channel '%s' not found.") % channelkey)
return
if not channel.has_connection(caller):
string = _("You are not connected to channel '%s'.")
self.msg(string % channelkey)
return
if not channel.access(caller, "send"):
string = _("You are not permitted to send to channel '%s'.")
self.msg(string % channelkey)
return
if msg == "on":
caller = caller if not hasattr(caller, "account") else caller.account
unmuted = channel.unmute(caller)
if unmuted:
self.msg(_("You start listening to %s.") % channel)
return
self.msg(_("You were already listening to %s.") % channel)
return
if msg == "off":
caller = caller if not hasattr(caller, "account") else caller.account
muted = channel.mute(caller)
if muted:
self.msg(_("You stop listening to %s.") % channel)
return
self.msg(_("You were already not listening to %s.") % channel)
return
if self.history_start is not None:
# Try to view history
log_file = channel.attributes.get("log_file", default="channel_%s.log" % channel.key)
def send_msg(lines):
return self.msg(
"".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines)
)
tail_log_file(log_file, self.history_start, 20, callback=send_msg)
else:
caller = caller if not hasattr(caller, "account") else caller.account
if caller in channel.mutelist:
self.msg(_("You currently have %s muted.") % channel)
return
channel.msg(msg, senders=self.caller, online=True)
[docs] def get_extra_info(self, caller, **kwargs):
"""
Let users know that this command is for communicating on a channel.
Args:
caller (TypedObject): A Character or Account who has entered an ambiguous command.
Returns:
A string with identifying information to disambiguate the object, conventionally with a preceding space.
"""
return _(" (channel)")
[docs]class ChannelHandler(object):
"""
The ChannelHandler manages all active in-game channels and
dynamically creates channel commands for users so that they can
just give the channel's key or alias to write to it. Whenever a
new channel is created in the database, the update() method on
this handler must be called to sync it with the database (this is
done automatically if creating the channel with
evennia.create_channel())
"""
[docs] def __init__(self):
"""
Initializes the channel handler's internal state.
"""
self._cached_channel_cmds = {}
self._cached_cmdsets = {}
self._cached_channels = {}
def __str__(self):
"""
Returns the string representation of the handler
"""
return ", ".join(str(cmd) for cmd in self._cached_channel_cmds)
[docs] def clear(self):
"""
Reset the cache storage.
"""
self._cached_channel_cmds = {}
self._cached_cmdsets = {}
self._cached_channels = {}
[docs] def add(self, channel):
"""
Add an individual channel to the handler. This is called
whenever a new channel is created.
Args:
channel (Channel): The channel to add.
Notes:
To remove a channel, simply delete the channel object and
run self.update on the handler. This should usually be
handled automatically by one of the deletion methos of
the Channel itself.
"""
global _CHANNEL_COMMAND_CLASS
if not _CHANNEL_COMMAND_CLASS:
_CHANNEL_COMMAND_CLASS = class_from_module(settings.CHANNEL_COMMAND_CLASS)
# map the channel to a searchable command
cmd = _CHANNEL_COMMAND_CLASS(
key=channel.key.strip().lower(),
aliases=channel.aliases.all(),
locks="cmd:all();%s" % channel.locks,
help_category="Channel names",
obj=channel,
is_channel=True,
)
# format the help entry
key = channel.key
cmd.__doc__ = cmd.__doc__.format(
channelkey=key,
lower_channelkey=key.strip().lower(),
channeldesc=channel.attributes.get("desc", default="").strip(),
)
self._cached_channel_cmds[channel] = cmd
self._cached_channels[key] = channel
self._cached_cmdsets = {}
add_channel = add # legacy alias
[docs] def remove(self, channel):
"""
Remove channel from channelhandler. This will also delete it.
Args:
channel (Channel): Channel to remove/delete.
"""
if channel.pk:
channel.delete()
self.update()
[docs] def update(self):
"""
Updates the handler completely, including removing old removed
Channel objects. This must be called after deleting a Channel.
"""
global _CHANNELDB
if not _CHANNELDB:
from evennia.comms.models import ChannelDB as _CHANNELDB
self._cached_channel_cmds = {}
self._cached_cmdsets = {}
self._cached_channels = {}
for channel in _CHANNELDB.objects.get_all_channels():
self.add(channel)
[docs] def get(self, channelname=None):
"""
Get a channel from the handler, or all channels
Args:
channelame (str, optional): Channel key, case insensitive.
Returns
channels (list): The matching channels in a list, or all
channels in the handler.
"""
if channelname:
channel = self._cached_channels.get(channelname.lower(), None)
return [channel] if channel else []
return list(self._cached_channels.values())
[docs] def get_cmdset(self, source_object):
"""
Retrieve cmdset for channels this source_object has
access to send to.
Args:
source_object (Object): An object subscribing to one
or more channels.
Returns:
cmdsets (list): The Channel-Cmdsets `source_object` has
access to.
"""
if source_object in self._cached_cmdsets:
return self._cached_cmdsets[source_object]
else:
# create a new cmdset holding all viable channels
chan_cmdset = None
chan_cmds = [
channelcmd
for channel, channelcmd in self._cached_channel_cmds.items()
if channel.subscriptions.has(source_object)
and channelcmd.access(source_object, "send")
]
if chan_cmds:
chan_cmdset = cmdset.CmdSet()
chan_cmdset.key = "ChannelCmdSet"
chan_cmdset.priority = 101
chan_cmdset.duplicates = True
for cmd in chan_cmds:
chan_cmdset.add(cmd)
self._cached_cmdsets[source_object] = chan_cmdset
return chan_cmdset
# set up the singleton
CHANNEL_HANDLER = class_from_module(settings.CHANNEL_HANDLER_CLASS)()
CHANNELHANDLER = CHANNEL_HANDLER # legacy