Adding a new central channel command

This commit is contained in:
Griatch 2021-04-13 23:56:53 +02:00
parent 25e7e5a500
commit 2776aa2e20
2 changed files with 433 additions and 22 deletions

View file

@ -16,6 +16,7 @@ from evennia.accounts import bots
from evennia.comms.channelhandler import CHANNELHANDLER 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, evtable from evennia.utils import create, logger, utils, evtable
from evennia.utils.logger import tail_log_file
from evennia.utils.utils import make_iter, class_from_module from evennia.utils.utils import make_iter, class_from_module
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -25,6 +26,7 @@ CHANNEL_DEFAULT_TYPECLASS = class_from_module(
# limit symbol import for API # limit symbol import for API
__all__ = ( __all__ = (
"CmdChannel",
"CmdAddCom", "CmdAddCom",
"CmdDelCom", "CmdDelCom",
"CmdAllCom", "CmdAllCom",
@ -44,31 +46,386 @@ __all__ = (
) )
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
# helper functions to make it easier to override the main CmdChannel
# command and to keep the legacy addcom etc commands around.
def find_channel(caller, channelname, silent=False, noaliases=False): def search_channel(caller, channelname, exact=False):
""" """
Helper function for searching for a single channel with Helper function for searching for a single channel with some error
some error handling. handling.
Args:
channelname (str): Name, alias #dbref or partial name/alias to search
for.
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
will always be returned.
Returns:
list: A list of zero, one or more channels found.
Notes:
No permission checks will be done here.
""" """
channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname) # first see if this is a personal alias
channelname = caller.nicks.get(key=channelname, category="channel") or channelname
# always try the exact match first.
channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname, exact=True)
if not channels and not exact:
# try fuzzy matching as well
channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname, exact=exact)
# check permissions
channels = [channel for channel in channels
if channel.access(caller, 'listen') or channel.access(caller, 'control')]
if not channels: if not channels:
if not noaliases: return []
channels = [
chan
for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels()
if channelname in chan.aliases.all()
]
if channels:
return channels[0]
if not silent:
caller.msg("Channel '%s' not found." % channelname)
return None
elif len(channels) > 1: elif len(channels) > 1:
matches = ", ".join(["%s(%s)" % (chan.key, chan.id) for chan in channels]) return list(channels)
if not silent: return [channels[0]]
caller.msg("Multiple channels match (be more specific): \n%s" % matches)
return None
return channels[0] def msg_channel(caller, channel, message, **kwargs):
"""
Send a message to a given channel. At this point
any permissions should already be done.
Args:
caller (Object or Account): The entity sending the message.
channel (Channel): The channel to send to.
message (str): The message to send.
**kwargs: Unused by default. These kwargs will be passed into
all channel messaging hooks for custom overriding.
"""
channel.msg(message, senders=caller, **kwargs)
def get_channel_history(caller, channel, start_index=0):
"""
View a channel's history.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to access.
message (str): The message to send.
**kwargs: Unused by default. These kwargs will be passed into
all channel messaging hooks for custom overriding.
"""
log_file = channel.attributes.get(
"log_file", default=channel.log_file.format(channelkey=channel.key))
def send_msg(lines):
return caller.msg(
"".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines)
)
# asynchronously tail the log file
tail_log_file(log_file, start_index, 20, callback=send_msg)
def sub_to_channel(caller, channel):
"""
Subscribe to a channel. Note that all permissions should
be checked before this step.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to access.
Returns:
bool, str: True, None if connection failed. If False,
the second part is an error string.
"""
if channel.has_connection(caller):
return False, f"Already listening to channel {channel.key}."
result = channel.connect(caller)
return result, "" if result else f"Were not allowed to subscribe to channel {channel.key}"
def unsub_to_channel(caller, channel):
"""
Un-Subscribe to a channel. Note that all permissions should
be checked before this step.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to unsub from.
Returns:
bool, str: True, None if un-connection succeeded. If False,
the second part is an error string.
"""
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()
result = channel.disconnect(caller)
return result, "" if result else f"Could not unsubscribe from channel {channel.key}"
def add_alias(caller, channel, alias):
"""
Add a new alias for the user to use with this channel.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to alias.
alias (str): The personal alias to use for this channel.
"""
caller.nicks.add(alias, channel.key, category="channel")
def remove_alias(caller, alias):
"""
Remove an alias from a given channel.
Args:
caller (Object or Account): The entity performing the action.
alias (str, optional): The alias to remove, or `None` for all.
Returns:
bool, str: True, None if removal succeeded. If False,
the second part is an error string.
"""
channame = caller.nicks.get(key=alias, category="channel")
if channame:
caller.nicks.remove(key=alias, category="channel")
return True, ""
return False, "No such alias was defined."
def mute_channel(caller, channel):
"""
Temporarily mute a channel.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to alias.
Returns:
bool, str: True, None if muting successful. If False,
the second part is an error string.
"""
if channel.mute(caller):
return True, ""
return False, f"Channel {channel.key} was already muted."
def unmute_channel(caller, channel):
"""
Unmute a channel.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to alias.
Returns:
bool, str: True, None if unmuting successful. If False,
the second part is an error string.
"""
if channel.unmute(caller):
return True, ""
return False, f"Channel {channel.key} was already unmuted."
def create_channel(caller, name, description, aliases=None):
"""
Create a new channel. Its name must not previously exist
(users can alias as needed). Will also connect to the
new channel.
Args:
caller (Object or Account): The entity performing the action.
name (str): The new channel name/key.
description (str): This is used in listings.
aliases (list): A list of strings - alternative aliases for the channel
(not to be confused with per-user aliases; these are available for
everyone).
Returns:
channel, str: new_channel, "" if creation successful. If False,
the second part is an error string.
"""
if CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(name, exact=True):
return False, f"Channel {name} already exists."
# set up the new channel
lockstring = "send:all();listen:all();control:id(%s)" % caller.id
new_chan = create.create_channel(name, aliases=aliases,desc=description, locks=lockstring)
new_chan.connect(caller)
return new_chan, ""
def destroy_channel(caller, channel, message=None):
"""
Destroy an existing channel. Access should be checked before
calling this function.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to alias.
message (str, optional): Final message to send onto the channel
before destroying it. If not given, a default message is
used. Set to the empty string for no message.
"""
channel_key = channel.key
if message is None:
message = (f"|rChannel {channel_key} is being destroyed. "
"Make sure to clean any channel aliases.|n")
if message:
channel.msg(message, senders=caller, bypass_mute=True)
channel.delete()
logger.log_sec(
"Channel {} was deleted by {}".format(channel_key, caller)
)
def set_lock(caller, channel, lockstring):
"""
Set a lockstring on a channel.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to operate on.
lockstring (str): A lockstring on the form 'type:lockfunc();...'
Returns:
bool, str: True, None if setting lock was successful. If False,
the second part is an error string.
"""
try:
channel.locks.add(lockstring)
except LockException as err:
return False, err
return True, ""
def set_desc(caller, channel, description):
"""
Set a channel description. This is shown in listings etc.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to operate on.
description (str): A short description of the channel.
Returns:
bool, str: True, None if setting lock was successful. If False,
the second part is an error string.
"""
channel.db.desc = description
def boot_user(caller, channel, target, quiet=False, reason=""):
"""
Boot a user from a channel, with optional reason. This will
also remove all their aliases for this channel.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to operate on.
target (Object or Account): The entity to boot.
quiet (bool, optional): Whether or not to announce to channel.
reason (str, optional): A reason for the boot.
Returns:
bool, str: True, None if setting lock was successful. If False,
the second part is an error string.
"""
if not channel.subscriptions.has(target):
return False, f"{target} is not connected to channel {channel.key}."
# find all of target's nicks linked to this channel and delete them
for nick in [
nick
for nick in target.nicks.get(category="channel") or []
if nick.value[3].lower() == channel.key
]:
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}")
if not quiet:
channel.msg(f"{target.key} was booted from channel by {caller.key}.{reason}")
logger.log_sec(f"Channel Boot: {target} (Channel: {channel}, "
f"Reason: {reason}, Caller: {caller}")
def ban_user(caller, channel, target, quiet=False, reason=""):
"""
Ban a user from a channel, by locking them out. This will also
boot them, if they are currently connected.
Args:
caller (Object or Account): The entity performing the action.
channel (Channel): The channel to operate on.
target (Object or Account): The entity to ban
quiet (bool, optional): Whether or not to announce to channel.
reason (str, optional): A reason for the ban
"""
result, err = boot_user(caller, channel, target, quiet=quiet, reason=reason)
class CmdChannel(COMMAND_DEFAULT_CLASS):
"""
Talk on and manage in-game channels.
Usage:
channel channelname [= <msg>]
channel/history channelname [= index]
channel/sub channelname [= alias]
channel/unsub channelname[,channelname, ...]
channel/alias channelname = alias
channel/unalias channelname = alias
channel/mute channelname[,channelname,...]
channel/unmute channelname[,channelname,...]
channel/create channelname [= description]
channel/destroy channelname [: reason]
channel/lock channelname = lockstring
channel/desc channelname = description
channel/boot[/quiet] channelname = subscribername [: reason]
channel/who channelname
channel/list
channels
This handles all operations on channels.
"""
key = "channel"
aliases = ["chan", "channels"]
locks = "cmd: not pperm(channel_banned)"
switch_options = (
"history", "sub", "unsub", "mute", "alias", "unalias", "create",
"destroy", "desc", "boot", "who")
def parse(self):
super().parse()
self.channelnames = self.lhslist
def func(self):
pass
class CmdAddCom(COMMAND_DEFAULT_CLASS): class CmdAddCom(COMMAND_DEFAULT_CLASS):

View file

@ -124,6 +124,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
def mutelist(self): def mutelist(self):
return self.db.mute_list or [] return self.db.mute_list or []
@property
def banlist(self):
return self.db.ban_list or []
@property @property
def wholist(self): def wholist(self):
subs = self.subscriptions.all() subs = self.subscriptions.all()
@ -152,6 +156,10 @@ 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).
Returns:
bool: True if muting was successful, False if we were already
muted.
""" """
mutelist = self.mutelist mutelist = self.mutelist
if subscriber not in mutelist: if subscriber not in mutelist:
@ -162,7 +170,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
def unmute(self, subscriber, **kwargs): def unmute(self, subscriber, **kwargs):
""" """
Removes an entity to the list of muted subscribers. A muted subscriber Removes an entity from the list of muted subscribers. A muted subscriber
will no longer see channel messages, but may use channel commands. will no longer see channel messages, but may use channel commands.
Args: Args:
@ -170,11 +178,57 @@ 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).
Returns:
bool: True if unmuting was successful, False if we were already
unmuted.
""" """
mutelist = self.mutelist mutelist = self.mutelist
if subscriber in mutelist: if subscriber in mutelist:
mutelist.remove(subscriber) mutelist.remove(subscriber)
self.db.mute_list = mutelist return True
return False
def ban(self, target, **kwargs):
"""
Ban a given user from connecting to the channel. This will not stop
users already connected, so the user must be booted for this to take
effect.
Args:
target (Object or Account): The entity to unmute. This need not
be a subscriber.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
bool: True if banning was successful, False if target was already
banned.
"""
banlist = self.banlist
if target not in banlist:
banlist.append(target)
return True
return False
def unban(self, target, **kwargs):
"""
Un-Ban a given user. This will not reconnect them - they will still
have to reconnect and set up aliases anew.
Args:
target (Object or Account): The entity to unmute. This need not
be a subscriber.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
bool: True if unbanning was successful, False if target was not
previously banned.
"""
banlist = self.banlist
if target not in banlist:
banlist.append(target)
return True return True
return False return False