From 48bcb9d0ba2e9ad6929dc6a1339180a7dfd25165 Mon Sep 17 00:00:00 2001 From: Kelketek Date: Sun, 29 Sep 2013 13:11:10 -0500 Subject: [PATCH] Useful default typeclass for channels added. Handles poses and provides interfaces for external messages and internal alike. --- src/commands/default/comms.py | 5 +- src/comms/channelhandler.py | 16 +-- src/comms/comms.py | 215 +++++++++++++++++++++++++++++++++- src/comms/irc.py | 30 +++-- src/comms/managers.py | 3 +- src/comms/models.py | 104 +++++----------- src/utils/create.py | 1 + 7 files changed, 267 insertions(+), 107 deletions(-) diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index 5491d9c1ec..b36d89e750 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -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 diff --git a/src/comms/channelhandler.py b/src/comms/channelhandler.py index 3369df7a5e..48b4cc884e 100644 --- a/src/comms/channelhandler.py +++ b/src/comms/channelhandler.py @@ -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): """ diff --git a/src/comms/comms.py b/src/comms/comms.py index 3a571b665f..179e08d1fb 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -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) + diff --git a/src/comms/irc.py b/src/comms/irc.py index 6c32aec5a5..6915a60e30 100644 --- a/src/comms/irc.py +++ b/src/comms/irc.py @@ -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("" #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(" 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 diff --git a/src/utils/create.py b/src/utils/create.py index 15fe65c02e..5751f0fa54 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -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