Useful default typeclass for channels added. Handles poses and provides interfaces for external messages and internal alike.

This commit is contained in:
Kelketek 2013-09-29 13:11:10 -05:00
parent 851e6d00cc
commit 48bcb9d0ba
7 changed files with 267 additions and 107 deletions

View file

@ -151,8 +151,9 @@ class CmdDelCom(MuxPlayerCommand):
for nick in [nick for nick in caller.nicks.get(category="channel")
if nick.db_data.lower() == chkey]:
nick.delete()
channel.disconnect_from(player)
self.msg("You stop listening to channel '%s'. Eventual aliases were removed." % channel.key)
disconnect = channel.disconnect_from(player)
if disconnect:
self.msg("You stop listening to channel '%s'. Eventual aliases were removed." % channel.key)
return
else:
# we are removing a channel nick

View file

@ -77,20 +77,8 @@ class ChannelCommand(command.Command):
string = "You are not permitted to send to channel '%s'."
self.msg(string % channelkey)
return
msg = "[%s] %s: %s" % (channel.key, caller.name, msg)
# we can't use the utils.create function to make the Msg,
# since that creates an import recursive loop.
try:
sender = caller.player
except AttributeError:
# this could happen if a player is calling directly.
sender = caller.dbobj
msgobj = Msg(db_message=msg)
msgobj.save()
msgobj.senders = sender
msgobj.channels = channel
# send new message object to channel
channel.msg(msgobj, senders=sender, online=True)
channel.msg(msg, senders=self.caller, online=True)
class ChannelHandler(object):
"""

View file

@ -3,7 +3,10 @@ Default Typeclass for Comms.
See objects.objects for more information on Typeclassing.
"""
from src.comms import Msg, TempMsg, ChannelDB
from src.typeclasses.typeclass import TypeClass
from src.utils import logger
from src.utils.utils import make_iter
class Comm(TypeClass):
@ -14,8 +17,214 @@ class Comm(TypeClass):
def __init__(self, dbobj):
super(Comm, self).__init__(dbobj)
def format_message(self, msg):
def channel_prefix(self, msg=None, emit=False):
"""
Takes a Msg (see models.Msg), and derives the output display for it on
the channel.
How the channel should prefix itself for users. Return a string.
"""
return '[%s] ' % self.key
def format_senders(self, senders=None):
"""
Function used to format a list of sender names.
This function exists separately so that external sources can use
it to format source names in the same manner as normal object/player
names.
"""
if not senders:
return ''
return ', '.join(senders)
def pose_transform(self, msg, sender_string):
"""
Detects if the sender is posing, and modifies the message accordingly.
"""
pose = False
message = msg.message
message_start = message.lstrip()
if message_start.startswith((':', ';')):
pose = True
message = message[1:]
if not message.startswith((':', "'", ',')):
if not message.startswith(' '):
message = ' ' + message
if pose:
return '%s%s' % (sender_string, message)
else:
return '%s: %s' % (sender_string, message)
def format_external(self, msg, senders, emit=False):
"""
Used for formatting external messages. This is needed as a separate
operation because the senders of external messages may not be in-game
objects/players, and so cannot have things like custom user
preferences.
senders should be a list of strings, each containing a sender.
msg should contain the body of the message to be sent.
"""
if not senders:
emit = True
if emit:
return msg.message
senders = ', '.join(senders)
return self.pose_transform(msg, senders)
def format_message(self, msg, emit=False):
"""
Formats a message body for display.
If emit is True, it means the message is intended to be posted detached
from an identity.
"""
# We don't want to count things like external sources as senders for
# the purpose of constructing the message string.
senders = [sender for sender in msg.senders if hasattr(sender, 'key')]
if not senders:
emit = True
if emit:
return msg.message
else:
senders = [sender.key for sender in msg.senders]
senders = ', '.join(senders)
return self.pose_transform(msg, senders)
def message_transform(self, msg, emit=False, prefix=True,
sender_strings=None, external=False):
"""
Generates the formatted string sent to listeners on a channel.
"""
if sender_strings or external:
body = self.format_external(msg, sender_strings, emit=emit)
else:
body = self.format_message(msg, emit=emit)
if prefix:
body = "%s%s" % (self.channel_prefix(msg, emit=emit), body)
msg.message = body
return msg
def at_channel_create(self):
"""
Run at channel creation.
"""
def pre_join_channel(self, joiner):
"""
Run right before a channel is joined. If this returns a false value,
channel joining is aborted.
"""
def post_join_channel(self, joiner):
"""
Run right after an object or player joins a channel.
"""
return True
def pre_leave_channel(self, leaver):
"""
Run right before a user leaves a channel. If this returns a false
value, leaving the channel will be aborted.
"""
return True
def post_leave_channel(self, leaver):
"""
Run right after an object or player leaves a channel.
"""
def pre_send_message(self, msg):
"""
Run before a message is sent to the channel.
This should return the message object, after any transformations.
If the message is to be discarded, return a false value.
"""
return msg
def post_send_message(self, msg):
"""
Run after a message is sent to the channel.
"""
def at_init(self):
"""
This is always called whenever this channel is initiated --
that is, whenever it its typeclass is cached from memory. This
happens on-demand first time the channel is used or activated
in some way after being created but also after each server
restart or reload.
"""
def distribute_message(self, msg, online=False):
"""
Method for grabbing all listeners that a message should be sent to on
this channel, and sending them a message.
"""
# get all players connected to this channel and send to them
for conn in ChannelDB.objects.get_all_connections(self, online=online):
try:
conn.player.msg(msg.message, from_obj=msg.senders)
except AttributeError:
try:
conn.to_external(msg.message, senders=msg.senders, from_channel=self)
except Exception:
logger.log_trace("Cannot send msg to connection '%s'" % conn)
def msg(self, msgobj, header=None, senders=None, sender_strings=None,
persistent=True, online=False, emit=False, external=False):
"""
Send the given message to all players connected to channel. Note that
no permission-checking is done here; it is assumed to have been
done before calling this method. The optional keywords are not used if persistent is False.
msgobj - a Msg/TempMsg instance or a message string. If one of the former, the remaining
keywords will be ignored. If a string, this will either be sent as-is (if persistent=False) or
it will be used together with header and senders keywords to create a Msg instance on the fly.
senders - an object, player or a list of objects or players. Optional if persistent=False.
sender_strings - Name strings of senders. Used for external connections where the sender
is not a player or object. When this is defined, external will be assumed.
external - Treat this message agnostic of its sender.
persistent (bool) - ignored if msgobj is a Msg or TempMsg. If True, a Msg will be created, using
header and senders keywords. If False, other keywords will be ignored.
online (bool) - If this is set true, only messages people who are online. Otherwise, messages all players
connected. This can make things faster, but may not trigger listeners on players that are offline.
emit (bool) - Signals to the message formatter that this message is not to be directly associated with a name.
"""
if senders:
senders = make_iter(senders)
else:
senders = []
if isinstance(msgobj, basestring):
# given msgobj is a string
msg = msgobj
if persistent and self.db.keep_log:
msgobj = Msg()
msgobj.save()
else:
# Use TempMsg, so this message is not stored.
msgobj = TempMsg()
msgobj.header = header
msgobj.message = msg
msgobj.channels = [self.dbobj] # add this channel
if not msgobj.senders:
msgobj.senders = senders
msgobj = self.pre_send_message(msgobj)
if not msgobj:
return False
msgobj = self.message_transform(msgobj, emit=emit,
sender_strings=sender_strings,
external=external)
self.distribute_message(msgobj, online=online)
self.post_send_message(msgobj)
return True
def tempmsg(self, message, header=None, senders=None):
"""
A wrapper for sending non-persistent messages.
"""
self.msg(message, senders=senders, header=header, persistent=False)

View file

@ -52,9 +52,10 @@ class IRC_Bot(irc.IRCClient):
msg_info(msg)
logger.log_infomsg(msg)
def privmsg(self, user, irc_channel, msg):
"Someone has written something in irc channel. Echo it to the evennia channel"
def get_mesg_info(self, user, irc_channel, msg):
"""
Get basic information about a message posted in IRC.
"""
#find irc->evennia channel mappings
conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
if not conns:
@ -65,28 +66,31 @@ class IRC_Bot(irc.IRCClient):
user.strip()
else:
user = _("Unknown")
msg = "[%s] %s@%s: %s" % (self.factory.evennia_channel, user, irc_channel, msg.strip())
msg = msg.strip()
sender_strings = ["%s@%s" % (user, irc_channel)]
return conns, msg, sender_strings
def privmsg(self, user, irc_channel, msg):
"Someone has written something in irc channel. Echo it to the evennia channel"
conns, msg, sender_strings = self.get_mesg_info(user, irc_channel, msg)
#logger.log_infomsg("<IRC: " + msg)
for conn in conns:
if conn.channel:
conn.to_channel(msg)
conn.to_channel(msg, sender_strings=sender_strings)
def action(self, user, irc_channel, msg):
"Someone has performed an action, e.g. using /me <pose>"
#find irc->evennia channel mappings
conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
if not conns:
return
#format message:
user = user.split("!")[0]
if user:
user.strip()
else:
user = _("Unknown")
msg = "[%s] *%s@%s %s*" % (self.factory.evennia_channel, user, irc_channel, msg.strip())
conns, msg, sender_strings = self.get_mesg_info(user, irc_channel, msg)
# Transform this into a pose.
msg = ':' + msg
#logger.log_infomsg("<IRC: " + msg)
for conn in conns:
if conn.channel:
conn.to_channel(msg)
conn.to_channel(msg, sender_strings=sender_strings)
def msg_irc(self, msg, senders=None):
"""

View file

@ -321,7 +321,8 @@ class ChannelManager(models.Manager):
unique_online_users = set(sess.uid for sess in session_list if sess.logged_in)
online_players = (sess.get_player() for sess in session_list if sess.uid in unique_online_users)
for player in online_players:
players.extend(PlayerChannelConnection.objects.filter(db_player=player.dbobj, db_channel=channel))
players.extend(PlayerChannelConnection.objects.filter(
db_player=player.dbobj, db_channel=channel.dbobj))
else:
players.extend(PlayerChannelConnection.objects.get_all_connections(channel))

View file

@ -102,6 +102,7 @@ class Msg(SharedMemoryModel):
def __init__(self, *args, **kwargs):
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
self.extra_senders = []
class Meta:
"Define Django meta options"
@ -120,7 +121,9 @@ class Msg(SharedMemoryModel):
def __senders_get(self):
"Getter. Allows for value = self.sender"
return [hasattr(o, "typeclass") and o.typeclass or o for o in
list(self.db_sender_players.all()) + list(self.db_sender_objects.all())]
list(self.db_sender_players.all()) +
list(self.db_sender_objects.all()) +
self.extra_senders]
#@sender.setter
def __senders_set(self, value):
"Setter. Allows for self.sender = value"
@ -130,6 +133,10 @@ class Msg(SharedMemoryModel):
self.db_sender_players.add(obj)
elif typ == 'object':
self.db_sender_objects.add(obj)
elif typ == 'external':
self.db_sender_external = "1"
self.extra_senders.append(obj)
print "I ran!"
elif isinstance(typ, basestring):
self.db_sender_external = obj
elif not obj:
@ -143,6 +150,7 @@ class Msg(SharedMemoryModel):
self.db_sender_players.clear()
self.db_sender_objects.clear()
self.db_sender_external = ""
self.extra_senders = []
self.save()
senders = property(__senders_get, __senders_set, __senders_del)
@ -154,8 +162,11 @@ class Msg(SharedMemoryModel):
self.db_sender_players.remove(obj)
elif typ == 'object':
self.db_sender_objects.remove(obj)
elif isinstance(obj, basestring) and self.db_sender_external == obj:
self.db_sender_external = ""
elif typ == 'external':
self.extra_senders = [receiver for receiver in
self.extra_senders if receiver != obj]
elif isinstance(obj, basestring):
self.db_sender_external = obj
else:
raise ValueError(obj)
self.save()
@ -185,6 +196,7 @@ class Msg(SharedMemoryModel):
"Deleter. Clears all receivers"
self.db_receivers_players.clear()
self.db_receivers_objects.clear()
self.extra_senders = []
self.save()
receivers = property(__receivers_get, __receivers_set, __receivers_del)
@ -241,22 +253,6 @@ class Msg(SharedMemoryModel):
self.save()
hide_from = property(__hide_from_get, __hide_from_set, __hide_from_del)
# lock_storage property (wraps db_lock_storage)
#@property
#def __lock_storage_get(self):
# "Getter. Allows for value = self.lock_storage"
# return self.db_lock_storage
##@nick.setter
#def __lock_storage_set(self, value):
# """Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
# self.db_lock_storage = value
# self.save()
##@nick.deleter
#def __lock_storage_del(self):
# "Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
# logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
#lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del)
#
# Msg class methods
#
@ -385,77 +381,36 @@ class ChannelDB(TypedObject):
# do the check
return PlayerChannelConnection.objects.has_player_connection(player, self)
def msg(self, msgobj, header=None, senders=None, persistent=True, online=False):
"""
Send the given message to all players connected to channel. Note that
no permission-checking is done here; it is assumed to have been
done before calling this method. The optional keywords are not used if persistent is False.
msgobj - a Msg/TempMsg instance or a message string. If one of the former, the remaining
keywords will be ignored. If a string, this will either be sent as-is (if persistent=False) or
it will be used together with header and senders keywords to create a Msg instance on the fly.
senders (object, player or a list of objects or players) - ignored if msgobj is a Msg or TempMsg, or if
persistent=False.
persistent (bool) - ignored if msgobj is a Msg or TempMsg. If True, a Msg will be created, using
header and senders keywords. If False, other keywords will be ignored.
online (bool) - If this is set true, only messages people who are online. Otherwise, messages all players
connected. This can make things faster, but may not trigger listeners on players that are offline.
"""
if isinstance(msgobj, basestring):
# given msgobj is a string
if persistent:
msg = msgobj
msgobj = Msg()
msgobj.save()
if senders:
msgobj.senders = make_iter(senders)
msgobj.header = header
msgobj.message = msg
msgobj.channels = [self] # add this channel
else:
# just use the msg as-is
msg = msgobj
else:
# already in a Msg/TempMsg
msg = msgobj.message
# get all players connected to this channel and send to them
for conn in ChannelDB.objects.get_all_connections(self, online=online):
try:
conn.player.msg(msg, from_obj=senders)
except AttributeError:
try:
conn.to_external(msg, senders=senders, from_channel=self)
except Exception:
logger.log_trace("Cannot send msg to connection '%s'" % conn)
return True
def tempmsg(self, message, header=None, senders=None):
"""
A wrapper for sending non-persistent messages.
"""
self.msg(message, senders=senders, header=header, persistent=False)
def connect_to(self, player):
"Connect the user to this channel"
self.typeclass.pre_join_channel(player)
if not self.access(player, 'listen'):
return False
connect = self.typeclass.pre_join_channel(player)
if not connect:
return False
player = player.dbobj
conn = PlayerChannelConnection.objects.create_connection(player, self)
if conn:
self.typeclass.post_join_channel(player)
return True
return False
def disconnect_from(self, player):
"Disconnect user from this channel."
disconnect = self.typeclass.pre_leave_channel(self, player)
if not disconnect:
return False
PlayerChannelConnection.objects.break_connection(player, self)
self.typeclass.post_leave_channel(self, player)
return True
def delete(self):
"Clean out all connections to this channel and delete it."
for connection in ChannelDB.objects.get_all_connections(self):
connection.delete()
super(ChannelDB, self).delete()
def access(self, accessing_obj, access_type='listen', default=False):
"""
Determines if another object has permission to access.
@ -637,17 +592,18 @@ class ExternalChannelConnection(SharedMemoryModel):
# methods
#
def to_channel(self, message, from_obj=None):
def to_channel(self, message, *args, **kwargs):
"Send external -> channel"
if not from_obj:
if 'from_obj' in kwargs and kwargs.pop('from_obj'):
from_obj = self.external_key
self.channel.msg(message, senders=[self])
self.channel.msg(message, senders=[self], *args, **kwargs)
def to_external(self, message, senders=None, from_channel=None):
"Send channel -> external"
# make sure we are not echoing back our own message to ourselves
# (this would result in a nasty infinite loop)
print senders
if self in make_iter(senders):#.external_key:
return

View file

@ -384,6 +384,7 @@ def create_channel(key, aliases=None, desc=None,
new_channel.locks.add(locks)
new_channel.save()
_channelhandler.CHANNELHANDLER.add_channel(new_channel)
new_channel.at_channel_create()
return new_channel
channel = create_channel