mirror of
https://github.com/evennia/evennia.git
synced 2026-03-29 03:57:17 +02:00
Adding a new central channel command
This commit is contained in:
parent
ef39fa301b
commit
5f7d695360
2 changed files with 433 additions and 22 deletions
|
|
@ -16,6 +16,7 @@ from evennia.accounts import bots
|
|||
from evennia.comms.channelhandler import CHANNELHANDLER
|
||||
from evennia.locks.lockhandler import LockException
|
||||
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
|
||||
|
||||
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
|
||||
__all__ = (
|
||||
"CmdChannel",
|
||||
"CmdAddCom",
|
||||
"CmdDelCom",
|
||||
"CmdAllCom",
|
||||
|
|
@ -44,31 +46,386 @@ __all__ = (
|
|||
)
|
||||
_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
|
||||
some error handling.
|
||||
Helper function for searching for a single channel with some error
|
||||
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 noaliases:
|
||||
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
|
||||
return []
|
||||
elif len(channels) > 1:
|
||||
matches = ", ".join(["%s(%s)" % (chan.key, chan.id) for chan in channels])
|
||||
if not silent:
|
||||
caller.msg("Multiple channels match (be more specific): \n%s" % matches)
|
||||
return None
|
||||
return channels[0]
|
||||
return list(channels)
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -124,6 +124,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
def mutelist(self):
|
||||
return self.db.mute_list or []
|
||||
|
||||
@property
|
||||
def banlist(self):
|
||||
return self.db.ban_list or []
|
||||
|
||||
@property
|
||||
def wholist(self):
|
||||
subs = self.subscriptions.all()
|
||||
|
|
@ -152,6 +156,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
||||
Returns:
|
||||
bool: True if muting was successful, False if we were already
|
||||
muted.
|
||||
|
||||
"""
|
||||
mutelist = self.mutelist
|
||||
if subscriber not in mutelist:
|
||||
|
|
@ -162,7 +170,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
|
@ -170,11 +178,57 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
||||
Returns:
|
||||
bool: True if unmuting was successful, False if we were already
|
||||
unmuted.
|
||||
|
||||
"""
|
||||
mutelist = self.mutelist
|
||||
if subscriber in mutelist:
|
||||
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 False
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue