From 7d30b337d9c3eeefdfd1b0f27d9a7029bf177e46 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 16 Apr 2011 22:26:22 +0000 Subject: [PATCH] Cleanups and bug fixes. Fixed the @unlink command and also made it overally more stable. Resolves issue 161. Added more string conversion routines to handle non-ascii variables being stored in an Attribute. Resolves issue 160. --- src/commands/cmdsethandler.py | 5 +- src/commands/default/building.py | 148 ++++-- src/commands/default/cmdset_default.py | 2 + src/commands/default/comms.py | 99 +++- src/commands/default/unimplemented/imc2.py | 170 +++---- src/commands/default/unimplemented/irc.py | 130 ----- .../default/unimplemented/objmanip.py | 263 ----------- src/comms/imc2.py | 444 ++++++++++++++++++ src/{imc2 => comms/imc2lib}/__init__.py | 0 .../imc2lib/imc2_ansi.py} | 7 +- .../imc2lib/imc2_listeners.py} | 7 +- .../imc2lib/imc2_packets.py} | 32 +- .../imc2lib/imc2_trackers.py} | 5 - src/comms/irc.py | 25 +- src/imc2/admin.py | 7 - src/imc2/connection.py | 201 -------- src/imc2/events.py | 87 ---- src/imc2/models.py | 21 - src/locks/lockhandler.py | 5 +- src/players/models.py | 2 +- src/server/server.py | 28 +- src/server/telnet.py | 52 +- src/server/webclient.py | 53 +-- src/settings_default.py | 85 ++-- src/typeclasses/models.py | 6 +- src/utils/reloads.py | 5 +- src/utils/utils.py | 32 +- 27 files changed, 873 insertions(+), 1048 deletions(-) delete mode 100644 src/commands/default/unimplemented/irc.py delete mode 100644 src/commands/default/unimplemented/objmanip.py create mode 100644 src/comms/imc2.py rename src/{imc2 => comms/imc2lib}/__init__.py (100%) rename src/{imc2/imc_ansi.py => comms/imc2lib/imc2_ansi.py} (93%) rename src/{imc2/reply_listener.py => comms/imc2lib/imc2_listeners.py} (75%) rename src/{imc2/packets.py => comms/imc2lib/imc2_packets.py} (94%) rename src/{imc2/trackers.py => comms/imc2lib/imc2_trackers.py} (92%) delete mode 100644 src/imc2/admin.py delete mode 100644 src/imc2/connection.py delete mode 100644 src/imc2/events.py delete mode 100644 src/imc2/models.py diff --git a/src/commands/cmdsethandler.py b/src/commands/cmdsethandler.py index 7bbe90a28b..19c4bf2a5a 100644 --- a/src/commands/cmdsethandler.py +++ b/src/commands/cmdsethandler.py @@ -66,6 +66,7 @@ the 'Fishing' set. Fishing from a boat? No problem! import traceback from src.utils import logger from src.commands.cmdset import CmdSet +from src.server.models import ServerConfig CACHED_CMDSETS = {} @@ -118,8 +119,8 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False): if errstring and not no_logging: print errstring logger.log_trace() - if emit_to_obj: - emit_to_obj.msg(errstring) + if emit_to_obj and not ServerConfig.objects.conf("server_starting_mode"): + object.__getattribute__(emit_to_obj, "msg")(errstring) raise # have to raise, or we will not see any errors in some situations! # classes diff --git a/src/commands/default/building.py b/src/commands/default/building.py index 3814a78f5c..f7abb91c11 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -371,7 +371,7 @@ class CmdCreate(ObjManipCommand): # create object (if not a valid typeclass, the default # object typeclass will automatically be used) - lockstring = "owner:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id) + lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id) obj = create.create_object(typeclass, name, caller, home=caller, aliases=aliases, locks=lockstring) if not obj: @@ -489,7 +489,7 @@ class CmdDesc(MuxCommand): obj = caller desc = self.args # storing the description - obj.db.desc = desc + obj.db.desc = desc caller.msg("The description was set on %s." % obj.key) @@ -607,7 +607,10 @@ class CmdDig(ObjManipCommand): new_room = create.create_object(typeclass, room["name"], aliases=room["aliases"]) new_room.locks.add(lockstring) - room_string = "Created room '%s' of type %s." % (new_room.name, typeclass) + alias_string = "" + if new_room.aliases: + alias_string = " (%s)" % ", ".join(new_room_aliases) + room_string = "Created room %s(%s)%s of type %s." % (new_room, new_room.dbref, alias_string, typeclass) exit_to_string = "" exit_back_string = "" @@ -637,9 +640,12 @@ class CmdDig(ObjManipCommand): aliases=to_exit["aliases"]) new_to_exit.destination = new_room new_to_exit.locks.add(lockstring) - exit_to_string = "\nCreated new Exit to new room: %s (aliases: %s)." - exit_to_string = exit_to_string % (new_to_exit.name, - new_to_exit.aliases) + alias_string = "" + if new_to_exit.aliases: + alias_string = " (%s)" % ", ".join(new_to_exit.aliases) + exit_to_string = "\nCreated Exit from %s to %s: %s(%s)%s." + exit_to_string = exit_to_string % (location.name, new_room.name, new_to_exit, + new_to_exit.dbref, alias_string) if len(self.rhs_objs) > 1: # Building the exit back to the current room @@ -666,9 +672,12 @@ class CmdDig(ObjManipCommand): aliases=back_exit["aliases"]) new_back_exit.destination = location new_back_exit.locks.add(lockstring) - exit_back_string = "\nExit back from new room: %s (aliases: %s)." - exit_back_string = exit_back_string % (new_back_exit.name, - new_back_exit.aliases) + alias_string = "" + if new_back_exit.aliases: + alias_string = " (%s)" % ", ".join(new_back_exit.aliases) + exit_back_string = "\nCreated Exit back from %s to %s: %s(%s)%s." + exit_back_string = exit_back_string % (new_room.name, location.name, + new_back_exit, new_back_exit.dbref, alias_string) caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string)) if new_room and 'teleport' in self.switches: caller.move_to(new_room) @@ -682,17 +691,16 @@ class CmdLink(MuxCommand): @link[/switches] = @link[/switches] = @link[/switches] - - Switches: - twoway - this is only useful when both - and are Exits. If so, a link back - from to will also be created. + + Switch: + twoway - connect two exits. For this to, BOTH + and must be exit objects. - - If is an exit, set its destination. For all other object types, this - command sets the object's Home. - The second form sets the destination/home to None and the third form inspects - the current value of destination/home on . + If is an exit, set its destination to . Two-way operation + instead sets the destination to the *locations* of the respective given + arguments. + The second form (a lone =) sets the destination to None (same as the @unlink command) + and the third form (without =) just shows the currently set destination. """ key = "@link" @@ -719,25 +727,25 @@ class CmdLink(MuxCommand): # this means a target name was given target = caller.search(self.rhs, global_search=True) if not target: - return - # if obj is an exit (has property destination), - # set that, otherwise set home. - if obj.destination: - obj.destination = target - if "twoway" in self.switches: - if target.destination: - target.destination = obj - string = "Link created %s <-> %s (two-way)." % (obj.name, target.name) - else: - string = "Cannot create two-way link to non-exit." - string += " Link created %s -> %s (one way)." % (obj.name, target.name) - else: - string = "Link created %s -> %s (one way)." % (obj.name, target.name) + return + + string = "" + if not obj.destination: + string += "Note: %s(%s) did not have a destination set before. Make sure you linked the right thing." % (obj.name,obj.dbref) + if "twoway" in self.switches: + if not (target.location and obj.location): + string = "To create a two-way link, %s and %s must both have a location" % (obj, target) + string += " (i.e. they cannot be rooms, but should be exits)." + self.caller.msg(string) + return + if not target.destination: + string += "\nNote: %s(%s) did not have a destination set before. Make sure you linked the right thing." % (target.name, target.dbref) + obj.destination = target.location + target.destination = obj.location + string += "\nLink created %s (in %s) <-> %s (in %s) (two-way)." % (obj.name, obj.location, target.name, target.location) else: - # obj is not an exit (has not property destination), - # so set home instead - obj.home = target - string = "Set %s's home to %s." % (obj.name, target.name) + obj.destination = target + string += "\nLink created %s -> %s (one way)." % (obj.name, target) elif self.rhs == None: # this means that no = was given (otherwise rhs @@ -745,20 +753,20 @@ class CmdLink(MuxCommand): # the home/destination on object dest = obj.destination if dest: - "%s is an exit to %s." % (obj.name, dest.name) + string = "%s is an exit to %s." % (obj.name, dest.name) else: - string = "%s has home %s." % (obj.name, obj.home) + string = "%s is not an exit. Its home location is %s." % obj.home + else: # We gave the command @link 'obj = ' which means we want to - # clear destination or set home to None. + # clear destination. if obj.destination: - del obj.destination - string = "Exit %s no longer links anywhere." % obj.name + obj.destination = None + string = "Former exit %s no longer links anywhere." % obj.name else: - del obj.home - string = "%s no longer has a home." % obj.name + string = "%s had no destination to unlink." % obj.name # give feedback - caller.msg(string) + caller.msg(string.strip()) class CmdUnLink(CmdLink): """ @@ -794,6 +802,54 @@ class CmdUnLink(CmdLink): # call the @link functionality super(CmdUnLink, self).func() +class CmdHome(CmdLink): + """ + @home - control an object's home location + + Usage: + @home [= home_location] + + The "home" location is a "safety" location for objects; they + will be moved there if their current location ceases to exist. All + objects should always have a home location for this reason. + It is also a convenient target of the "home" command. + + If no location is given, just view the object's home location. + """ + + key = "@home" + locks = "cmd:perm(@home) or perm(Builders)" + help_category = "Building" + + def func(self): + "implement the command" + if not self.args: + string = "Usage: @home [= home_location]" + self.caller.msg(string) + return + + obj = self.caller.search(self.lhs, global_search=True) + if not obj: + return + if not self.rhs: + # just view + home = obj.home + if not home: + string = "This object has no home location set!" + else: + string = "%s's home is set to %s(%s)." % (obj, home, home.dbref) + else: + # set a home location + new_home = self.caller.search(self.rhs, global_search=True) + if not new_home: + return + old_home = obj.home + obj.home = new_home + if old_home: + string = "%s's home location was changed from %s(%s) to %s(%s)." % (old_home, old_home.dbref, new_home, new_home.dbref) + else: + string = "%s' home location was set to %s(%s)." % (new_home, new_home.dbref) + self.caller.msg(string) class CmdListCmdSets(MuxCommand): """ @@ -1454,7 +1510,7 @@ class CmdExamine(ObjManipCommand): perms = [""] elif not perms: perms = [""] - string += "\n{wPlayer Perms/Locks{n: %s" % (", ".join(perms)) + string += "\n{wPlayer Perms{n: %s" % (", ".join(perms)) string += "\n{wTypeclass{n: %s (%s)" % (obj.typeclass, obj.typeclass_path) string += "\n{wLocation{n: %s" % obj.location diff --git a/src/commands/default/cmdset_default.py b/src/commands/default/cmdset_default.py index 7c20029947..cef12f2b0f 100644 --- a/src/commands/default/cmdset_default.py +++ b/src/commands/default/cmdset_default.py @@ -78,6 +78,7 @@ class DefaultCmdSet(CmdSet): self.add(building.CmdTypeclass()) self.add(building.CmdLock()) self.add(building.CmdScript()) + self.add(building.CmdHome()) # Comm commands self.add(comms.CmdAddCom()) @@ -88,6 +89,7 @@ class DefaultCmdSet(CmdSet): self.add(comms.CmdCdesc()) self.add(comms.CmdPage()) self.add(comms.CmdIRC2Chan()) + self.add(comms.CmdIMC2Chan()) # Batchprocessor commands self.add(batchprocess.CmdBatchCommands()) diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index affc238c6b..e3ae3e595f 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -3,7 +3,7 @@ Comsys command module. """ from django.conf import settings from src.comms.models import Channel, Msg, PlayerChannelConnection, ExternalChannelConnection -from src.comms import irc +from src.comms import irc, imc2 from src.comms.channelhandler import CHANNELHANDLER from src.utils import create, utils from src.commands.default.muxcommand import MuxCommand @@ -892,16 +892,20 @@ class CmdIRC2Chan(MuxCommand): except Exception: string = "IRC bot definition '%s' is not valid." % self.rhs self.caller.msg(string) - return + return if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: + chanmatch = find_channel(self.caller, channel, silent=True) + if chanmatch: + channel = chanmatch.key + ok = irc.delete_connection(irc_network, irc_port, irc_channel, irc_botname) if not ok: self.caller.msg("IRC connection/bot could not be removed, does it exist?") else: self.caller.msg("IRC connection destroyed.") return - + channel = find_channel(self.caller, channel) if not channel: return @@ -910,3 +914,92 @@ class CmdIRC2Chan(MuxCommand): self.caller.msg("This IRC connection already exists.") return self.caller.msg("Connection created. Starting IRC bot.") + +class CmdIMC2Chan(MuxCommand): + """ + imc2chan - link an evennia channel to imc2 + + Usage: + @imc2chan[/switches] = + + Switches: + /disconnect - this will delete the bot and remove the imc2 connection to the channel. + /remove - " + /list - show all imc2<->evennia mappings + + Example: + @imc2chan myimcchan = server02.mudbytes.net 9000 ievennia Gjds8372 LAKdf84e + + Connect an existing evennia channel to an IMC2 network and channel. You must have registered with the network + beforehand and obtained valid server- and client passwords. You will always connect using the name of your + mud, as defined by settings.SERVERNAME, so make sure this was the name you registered to the imc2 network. + + """ + + key = "@imc2chan" + locks = "cmd:serversetting(IMC2_ENABLED) and perm(Wizards)" + help_category = "Comms" + + def func(self): + "Setup the imc-channel mapping" + + if 'list' in self.switches: + # show all connections + connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='imc2_') + if connections: + cols = [["Evennia channel"], ["IMC channel"]] + for conn in connections: + cols[0].append(conn.channel.key) + cols[1].append(" ".join(conn.external_config.split('|'))) + ftable = utils.format_table(cols) + string = "" + for ir, row in enumerate(ftable): + if ir == 0: + string += "{w%s{n" % "".join(row) + else: + string += "\n" + "".join(row) + self.caller.msg(string) + else: + self.caller.msg("No connections found.") + return + + if not settings.IMC2_ENABLED: + string = """IMC2 is not enabled. You need to activate it in game/settings.py.""" + self.caller.msg(string) + return + if not self.args or not self.rhs: + string = "Usage: @imc2chan[/switches] = " + self.caller.msg(string) + return + channel = self.lhs + try: + imc2_network, imc2_port, imc2_channel, imc2_client_pwd, imc2_server_pwd = [part.strip() for part in self.rhs.split(None, 4)] + except Exception: + string = "IMC2 connnection definition '%s' is not valid." % self.rhs + self.caller.msg(string) + return + + # get the name to use for connecting + mudname = settings.SERVERNAME + + if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: + chanmatch = find_channel(self.caller, channel, silent=True) + if chanmatch: + channel = chanmatch.key + + ok = imc2.delete_connection(channel, imc2_network, imc2_port, imc2_channel, mudname) + if not ok: + self.caller.msg("IMC2 connection could not be removed, does it exist?") + else: + self.caller.msg("IMC2 connection destroyed.") + return + + channel = find_channel(self.caller, channel) + if not channel: + return + + ok = imc2.create_connection(channel, imc2_network, imc2_port, imc2_channel, mudname, imc2_client_pwd, imc2_server_pwd) + if not ok: + self.caller.msg("This IMC2 connection already exists.") + return + self.caller.msg("Connection created. Connecting to IMC2 server.") diff --git a/src/commands/default/unimplemented/imc2.py b/src/commands/default/unimplemented/imc2.py index a673acbfae..1b9807e937 100644 --- a/src/commands/default/unimplemented/imc2.py +++ b/src/commands/default/unimplemented/imc2.py @@ -109,103 +109,103 @@ def cmd_imclist(command): source_object.emit_to(retval) GLOBAL_CMD_TABLE.add_command("imclist", cmd_imclist, help_category="Comms") -def cmd_imcstatus(command): - """ - imcstatus +# def cmd_imcstatus(command): +# """ +# imcstatus - Usage: - imcstatus +# Usage: +# imcstatus - Shows some status information for your IMC2 connection. - """ - source_object = command.source_object - # This manages our game's plugged in services. - collection = command.session.server.service_collection - # Retrieve the IMC2 service. - service = collection.getServiceNamed('IMC2') +# Shows some status information for your IMC2 connection. +# """ +# source_object = command.source_object +# # This manages our game's plugged in services. +# collection = command.session.server.service_collection +# # Retrieve the IMC2 service. +# service = collection.getServiceNamed('IMC2') - if service.running == 1: - status_string = 'Running' - else: - status_string = 'Inactive' +# if service.running == 1: +# status_string = 'Running' +# else: +# status_string = 'Inactive' - # Build the output to emit to the player. - retval = '-' * 50 - retval += '\n\r' - retval += 'IMC Status\n\r' - retval += ' * MUD Name: %s\n\r' % (settings.IMC2_MUDNAME) - retval += ' * Status: %s\n\r' % (status_string) - retval += ' * Debugging Mode: %s\n\r' % (settings.IMC2_DEBUG) - retval += ' * IMC Network Address: %s\n\r' % (settings.IMC2_SERVER_ADDRESS) - retval += ' * IMC Network Port: %s\n\r' % (settings.IMC2_SERVER_PORT) - retval += '-' * 50 +# # Build the output to emit to the player. +# retval = '-' * 50 +# retval += '\n\r' +# retval += 'IMC Status\n\r' +# retval += ' * MUD Name: %s\n\r' % (settings.IMC2_MUDNAME) +# retval += ' * Status: %s\n\r' % (status_string) +# retval += ' * Debugging Mode: %s\n\r' % (settings.IMC2_DEBUG) +# retval += ' * IMC Network Address: %s\n\r' % (settings.IMC2_SERVER_ADDRESS) +# retval += ' * IMC Network Port: %s\n\r' % (settings.IMC2_SERVER_PORT) +# retval += '-' * 50 - source_object.emit_to(retval) -GLOBAL_CMD_TABLE.add_command("imcstatus", cmd_imcstatus, - priv_tuple=('imc2.admin_imc_channels',), help_category="Comms") +# source_object.emit_to(retval) +# GLOBAL_CMD_TABLE.add_command("imcstatus", cmd_imcstatus, +# priv_tuple=('imc2.admin_imc_channels',), help_category="Comms") -def cmd_IMC2chan(command): - """ - @imc2chan +# def cmd_IMC2chan(command): +# """ +# @imc2chan - Usage: - @imc2chan : +# Usage: +# @imc2chan : - Links an IMC channel to an existing evennia - channel. You can link as many existing - evennia channels as you like to the - IMC channel this way. Running the command with an - existing mapping will re-map the channels. +# Links an IMC channel to an existing evennia +# channel. You can link as many existing +# evennia channels as you like to the +# IMC channel this way. Running the command with an +# existing mapping will re-map the channels. - Use 'imcchanlist' to get a list of IMC channels and - servers. Note that both are case sensitive. - """ - source_object = command.source_object - if not settings.IMC2_ENABLED: - s = """IMC is not enabled. You need to activate it in game/settings.py.""" - source_object.emit_to(s) - return - args = command.command_argument - if not args or len(args.split()) != 2 : - source_object.emit_to("Usage: @imc2chan IMCServer:IMCchannel channel") - return - #identify the server-channel pair - imcdata, channel = args.split() - if not ":" in imcdata: - source_object.emit_to("You need to supply an IMC Server:Channel pair.") - return - imclist = IMC2_CHANLIST.get_channel_list() - imc_channels = filter(lambda c: c.name == imcdata, imclist) - if not imc_channels: - source_object.emit_to("IMC server and channel '%s' not found." % imcdata) - return - else: - imc_server_name, imc_channel_name = imcdata.split(":") +# Use 'imcchanlist' to get a list of IMC channels and +# servers. Note that both are case sensitive. +# """ +# source_object = command.source_object +# if not settings.IMC2_ENABLED: +# s = """IMC is not enabled. You need to activate it in game/settings.py.""" +# source_object.emit_to(s) +# return +# args = command.command_argument +# if not args or len(args.split()) != 2 : +# source_object.emit_to("Usage: @imc2chan IMCServer:IMCchannel channel") +# return +# #identify the server-channel pair +# imcdata, channel = args.split() +# if not ":" in imcdata: +# source_object.emit_to("You need to supply an IMC Server:Channel pair.") +# return +# imclist = IMC2_CHANLIST.get_channel_list() +# imc_channels = filter(lambda c: c.name == imcdata, imclist) +# if not imc_channels: +# source_object.emit_to("IMC server and channel '%s' not found." % imcdata) +# return +# else: +# imc_server_name, imc_channel_name = imcdata.split(":") - #find evennia channel - try: - chanobj = comsys.get_cobj_from_name(channel) - except CommChannel.DoesNotExist: - source_object.emit_to("Local channel '%s' not found (use real name, not alias)." % channel) - return +# #find evennia channel +# try: +# chanobj = comsys.get_cobj_from_name(channel) +# except CommChannel.DoesNotExist: +# source_object.emit_to("Local channel '%s' not found (use real name, not alias)." % channel) +# return - #create the mapping. - outstring = "" - mapping = IMC2ChannelMapping.objects.filter(channel__name=channel) - if mapping: - mapping = mapping[0] - outstring = "Replacing %s. New " % mapping - else: - mapping = IMC2ChannelMapping() +# #create the mapping. +# outstring = "" +# mapping = IMC2ChannelMapping.objects.filter(channel__name=channel) +# if mapping: +# mapping = mapping[0] +# outstring = "Replacing %s. New " % mapping +# else: +# mapping = IMC2ChannelMapping() - mapping.imc2_server_name = imc_server_name - mapping.imc2_channel_name = imc_channel_name - mapping.channel = chanobj - mapping.save() - outstring += "Mapping set: %s." % mapping - source_object.emit_to(outstring) +# mapping.imc2_server_name = imc_server_name +# mapping.imc2_channel_name = imc_channel_name +# mapping.channel = chanobj +# mapping.save() +# outstring += "Mapping set: %s." % mapping +# source_object.emit_to(outstring) -GLOBAL_CMD_TABLE.add_command("@imc2chan",cmd_IMC2chan, - priv_tuple=("imc2.admin_imc_channels",), help_category="Comms") +# GLOBAL_CMD_TABLE.add_command("@imc2chan",cmd_IMC2chan, +# priv_tuple=("imc2.admin_imc_channels",), help_category="Comms") diff --git a/src/commands/default/unimplemented/irc.py b/src/commands/default/unimplemented/irc.py deleted file mode 100644 index 406fbecbac..0000000000 --- a/src/commands/default/unimplemented/irc.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -IRC-related commands -""" -from twisted.application import internet -from django.conf import settings -from src.irc.connection import IRC_CHANNELS -from src.irc.models import IRCChannelMapping -from src import comsys -from src.cmdtable import GLOBAL_CMD_TABLE -from src.channels.models import CommChannel - -def cmd_IRC2chan(command): - """ - @irc2chan - link irc to ingame channel - - Usage: - @irc2chan <#IRCchannel> - - Links an IRC channel (including #) to an existing - evennia channel. You can link as many existing - evennia channels as you like to the - IRC channel this way. Running the command with an - existing mapping will re-map the channels. - - """ - source_object = command.source_object - if not settings.IRC_ENABLED: - s = """IRC is not enabled. You need to activate it in game/settings.py.""" - source_object.emit_to(s) - return - args = command.command_argument - if not args or len(args.split()) != 2 : - source_object.emit_to("Usage: @irc2chan IRCchannel channel") - return - irc_channel, channel = args.split() - if irc_channel not in [o.factory.channel for o in IRC_CHANNELS]: - source_object.emit_to("IRC channel '%s' not found." % irc_channel) - return - try: - chanobj = comsys.get_cobj_from_name(channel) - except CommChannel.DoesNotExist: - source_object.emit_to("Local channel '%s' not found (use real name, not alias)." % channel) - return - - #create the mapping. - outstring = "" - mapping = IRCChannelMapping.objects.filter(channel__name=channel) - if mapping: - mapping = mapping[0] - outstring = "Replacing %s. New " % mapping - else: - mapping = IRCChannelMapping() - - mapping.irc_server_name = settings.IRC_NETWORK - mapping.irc_channel_name = irc_channel - mapping.channel = chanobj - mapping.save() - outstring += "Mapping set: %s." % mapping - source_object.emit_to(outstring) - -GLOBAL_CMD_TABLE.add_command("@irc2chan",cmd_IRC2chan, - priv_tuple=("irc.admin_irc_channels",), - help_category="Comms") - -def cmd_IRCjoin(command): - """ - @ircjoin - join a new irc channel - - Usage: - @ircjoin <#IRCchannel> - - Attempts to connect a bot to a new IRC channel (don't forget that - IRC channels begin with a #). - The bot uses the connection details defined in the main settings. - - Observe that channels added using this command does not survive a reboot. - """ - - source_object = command.source_object - arg = command.command_argument - if not arg: - source_object.emit_to("Usage: @ircjoin #irc_channel") - return - channel = arg.strip() - if channel[0] != "#": channel = "#%s" % channel - - if not settings.IRC_ENABLED: - source_object.emit_to("IRC services are not active. You need to turn them on in preferences.") - return - - #direct creation of bot (do not add to services) - from src.irc.connection import connect_to_IRC - connect_to_IRC(settings.IRC_NETWORK, - settings.IRC_PORT, - channel, settings.IRC_NICKNAME) - -# ---below should be checked so as to add subequent IRC bots to Services. -# it adds just fine, but the bot does not connect. /Griatch -# from src.irc.connection import IRC_BotFactory -# from src.server import mud_service -# irc = internet.TCPClient(settings.IRC_NETWORK, -# settings.IRC_PORT, -# IRC_BotFactory(channel, -# settings.IRC_NETWORK, -# settings.IRC_NICKNAME)) -# irc.setName("%s:%s" % ("IRC",channel)) -# irc.setServiceParent(mud_service.service_collection) - -GLOBAL_CMD_TABLE.add_command("@ircjoin",cmd_IRCjoin, - priv_tuple=("irc.admin_irc_channels",), - help_category="Comms") - -def cmd_IRCchanlist(command): - """ - ircchanlist - - Usage: - ircchanlist - - Lists all externally available IRC channels. - """ - source_object = command.source_object - s = "Available IRC channels:" - for c in IRC_CHANNELS: - s += "\n %s \t(nick '%s') on %s" % (c.factory.channel, - c.factory.nickname, - c.factory.network,) - source_object.emit_to(s) -GLOBAL_CMD_TABLE.add_command("ircchanlist", cmd_IRCchanlist, - help_category="Comms") diff --git a/src/commands/default/unimplemented/objmanip.py b/src/commands/default/unimplemented/objmanip.py deleted file mode 100644 index 5f328299b3..0000000000 --- a/src/commands/default/unimplemented/objmanip.py +++ /dev/null @@ -1,263 +0,0 @@ -""" -These commands typically are to do with building or modifying Objects. -""" -from django.conf import settings -from src.permissions.permissions import has_perm, has_perm_string -from src.objects.models import ObjectDB, ObjAttribute -from game.gamesrc.commands.default.muxcommand import MuxCommand -from src.utils import create, utils - - -## -## def cmd_chown(command): -## """ -## @chown - change ownerships - -## Usage: -## @chown = - -## Changes the ownership of an object. The new owner specified must be a -## player object. -## """ -## caller = command.caller - -## if not command.command_argument: -## caller.msg("Usage: @chown = ") -## return - -## eq_args = command.command_argument.split('=', 1) -## target_name = eq_args[0] -## owner_name = eq_args[1] - -## if len(target_name) == 0: -## caller.msg("Change the ownership of what?") -## return - -## if len(eq_args) > 1: -## target_obj = caller.search_for_object(target_name) -## # Use search_for_object to handle duplicate/nonexistant results. -## if not target_obj: -## return - -## if not caller.controls_other(target_obj) and not caller.has_perm("objects.admin_ownership"): -## caller.msg(defines_global.NOCONTROL_MSG) -## return - -## owner_obj = caller.search_for_object(owner_name) -## # Use search_for_object to handle duplicate/nonexistant results. -## if not owner_obj: -## return -## if not owner_obj.is_player(): -## caller.msg("Only players may own objects.") -## return -## if target_obj.is_player(): -## caller.msg("You may not change the ownership of player objects.") -## return - -## target_obj.set_owner(owner_obj) -## caller.msg("%s now owns %s." % (owner_obj, target_obj)) -## else: -## # We haven't provided a target. -## caller.msg("Who should be the new owner of the object?") -## return -## GLOBAL_CMD_TABLE.add_command("@chown", cmd_chown, priv_tuple=("objects.modify_attributes", -## "objects.admin_ownership"), -## help_category="Building" ) - - - -#NOT VALID IN NEW SYSTEM! -## def cmd_lock(command): -## """ -## @lock - limit use of objects - -## Usage: -## @lock[/switch] [:type] [= [,key2,key3,...]] - -## Switches: -## add - add a lock (default) from object -## del - remove a lock from object -## list - view all locks on object (default) -## type: -## DefaultLock - the default lock type (default) -## UseLock - prevents usage of objects' commands -## EnterLock - blocking objects from entering the object - -## Locks an object for everyone except those matching the keys. -## The keys can be of the following types (and searched in this order): -## - a user #dbref (#2, #45 etc) -## - a Group name (Builder, Immortal etc, case sensitive) -## - a Permission string (genperms.get, etc) -## - a Function():return_value pair. (ex: alliance():Red). The -## function() is called on the locked object (if it exists) and -## if its return value matches the Key is passed. If no -## return_value is given, matches against True. -## - an Attribute:return_value pair (ex: key:yellow_key). The -## Attribute is the name of an attribute defined on the locked -## object. If this attribute has a value matching return_value, -## the lock is passed. If no return_value is given, -## attributes will be searched, requiring a True -## value. - -## If no keys at all are given, the object is locked for everyone. -## When the lock blocks a user, you may customize which error is given by -## storing error messages in an attribute. For DefaultLocks, UseLocks and -## EnterLocks, these attributes are called lock_msg, use_lock_msg and -## enter_lock_msg respectively. - -## [[lock_types]] - -## Lock types: - -## Name: Affects: Effect: -## ----------------------------------------------------------------------- -## DefaultLock: Exits: controls who may traverse the exit to -## its destination. -## Rooms: controls whether the player sees a failure -## message after the room description when -## looking at the room. -## Players/Things: controls who may 'get' the object. - -## UseLock: All but Exits: controls who may use commands defined on -## the locked object. - -## EnterLock: Players/Things: controls who may enter/teleport into -## the object. -## VisibleLock: Players/Things: controls if the object is visible to -## someone using the look command. - -## Fail messages echoed to the player are stored in the attributes 'lock_msg', -## 'use_lock_msg', 'enter_lock_msg' and 'visible_lock_msg' on the locked object -## in question. If no such message is stored, a default will be used (or none at -## all in some cases). -## """ - -## caller = command.caller -## arg = command.command_argument -## switches = command.command_switches - -## if not arg: -## caller.msg("Usage: @lock[/switch] [:type] [= [,key2,key3,...]]") -## return -## keys = "" -## #deal with all possible arguments. -## try: -## lside, keys = arg.split("=",1) -## except ValueError: -## lside = arg -## lside, keys = lside.strip(), keys.strip() -## try: -## obj_name, ltype = lside.split(":",1) -## except: -## obj_name = lside -## ltype = "DefaultLock" -## obj_name, ltype = obj_name.strip(), ltype.strip() - -## if ltype not in ["DefaultLock","UseLock","EnterLock","VisibleLock"]: -## caller.msg("Lock type '%s' not recognized." % ltype) -## return - -## obj = caller.search_for_object(obj_name) -## if not obj: -## return - -## obj_locks = obj.LOCKS - -## if "list" in switches: -## if not obj_locks: -## s = "There are no locks on %s." % obj.name -## else: -## s = "Locks on %s:" % obj.name -## s += obj_locks.show() -## caller.msg(s) -## return - -## # we are trying to change things. Check permissions. -## if not caller.controls_other(obj): -## caller.msg(defines_global.NOCONTROL_MSG) -## return - -## if "del" in switches: -## # clear a lock -## if obj_locks: -## if not obj_locks.has_type(ltype): -## caller.msg("No %s set on this object." % ltype) -## else: -## obj_locks.del_type(ltype) -## obj.LOCKS = obj_locks -## caller.msg("Cleared lock %s on %s." % (ltype, obj.name)) -## else: -## caller.msg("No %s set on this object." % ltype) -## return -## else: -## #try to add a lock -## if not obj_locks: -## obj_locks = locks.Locks() -## if not keys: -## #add an impassable lock -## obj_locks.add_type(ltype, locks.Key()) -## caller.msg("Added impassable '%s' lock to %s." % (ltype, obj.name)) -## else: -## keys = [k.strip() for k in keys.split(",")] -## obj_keys, group_keys, perm_keys = [], [], [] -## func_keys, attr_keys = [], [] -## allgroups = [g.name for g in Group.objects.all()] -## allperms = ["%s.%s" % (p.content_type.app_label, p.codename) -## for p in Permission.objects.all()] -## for key in keys: -## #differentiate different type of keys -## if Object.objects.is_dbref(key): -## # this is an object key, like #2, #6 etc -## obj_keys.append(key) -## elif key in allgroups: -## # a group key -## group_keys.append(key) -## elif key in allperms: -## # a permission string -## perm_keys.append(key) -## elif '()' in key: -## # a function()[:returnvalue] tuple. -## # Check if we also request a return value -## funcname, rvalue = [k.strip() for k in key.split('()',1)] -## if not funcname: -## funcname = "lock_func" -## rvalue = rvalue.lstrip(':') -## if not rvalue: -## rvalue = True -## # pack for later adding. -## func_keys.append((funcname, rvalue)) -## elif ':' in key: -## # an attribute[:returnvalue] tuple. -## attr_name, rvalue = [k.strip() for k in key.split(':',1)] -## # pack for later adding -## attr_keys.append((attr_name, rvalue)) -## else: -## caller.msg("Key '%s' is not recognized as a valid dbref, group or permission." % key) -## return -## # Create actual key objects from the respective lists -## keys = [] -## if obj_keys: -## keys.append(locks.ObjKey(obj_keys)) -## if group_keys: -## keys.append(locks.GroupKey(group_keys)) -## if perm_keys: -## keys.append(locks.PermKey(perm_keys)) -## if func_keys: -## keys.append(locks.FuncKey(func_keys, obj.dbref)) -## if attr_keys: -## keys.append(locks.AttrKey(attr_keys)) - -## #store the keys in the lock -## obj_locks.add_type(ltype, keys) -## kstring = " " -## for key in keys: -## kstring += " %s," % key -## kstring = kstring[:-1] -## caller.msg("Added lock '%s' to %s with keys%s." % (ltype, obj.name, kstring)) -## obj.LOCKS = obj_locks -## GLOBAL_CMD_TABLE.add_command("@lock", cmd_lock, priv_tuple=("objects.create",), help_category="Building") - - - - - diff --git a/src/comms/imc2.py b/src/comms/imc2.py new file mode 100644 index 0000000000..bfcd9b6920 --- /dev/null +++ b/src/comms/imc2.py @@ -0,0 +1,444 @@ +""" +IMC2 client module. Handles connecting to and communicating with an IMC2 server. +""" + +from time import time +from twisted.application import internet +from twisted.internet import protocol +from twisted.conch import telnet +from django.conf import settings + +from src.utils import logger, create, search, utils +from src.server.sessionhandler import SESSIONS +from src.scripts.scripts import Script +from src.comms.models import Channel, ExternalChannelConnection +from src.comms.imc2lib import imc2_packets as pck +from src.comms.imc2lib.imc2_trackers import IMC2MudList, IMC2ChanList +from src.comms.imc2lib.imc2_listeners import handle_whois_reply + + +# channel to send info to +INFOCHANNEL = Channel.objects.channel_search(settings.CHANNEL_MUDINFO[0]) +# all linked channel connection +IMC2_CHANNELS = [] +# IMC2 debug mode +IMC2_DEBUG = True +# Use this instance to keep track of the other games on the network. +IMC2_MUDLIST = IMC2MudList() +# Tracks the list of available channels on the network. +IMC2_CHANLIST = IMC2ChanList() + +# +# Helper method +# + +def msg_info(message): + """ + Send info to default info channel + """ + message = '[%s][IMC2]: %s' % (INFOCHANNEL[0].key, message) + try: + INFOCHANNEL[0].msg(message) + except AttributeError: + logger.log_infomsg("MUDinfo (imc2): %s" % message) + +# +# Regular scripts +# + +class Send_IsAlive(Script): + """ + Sends periodic keepalives to network neighbors. This lets the other + games know that our game is still up and connected to the network. Also + provides some useful information about the client game. + """ + def at_script_creation(self): + self.key = 'IMC2_Send_IsAlive' + self.interval = 900 + self.desc = "Send an IMC2 is-alive packet" + self.persistent = True + def at_repeat(self): + for channel in IMC2_CHANNELS: + channel.send_packet(pck.IMC2PacketIsAlive()) + def is_valid(self): + "Is only valid as long as there are channels to update" + return IMC2_CHANNELS + +class Send_Keepalive_Request(Script): + """ + Event: Sends a keepalive-request to connected games in order to see who + is connected. + """ + def at_script_creation(self): + self.key = "IMC2_Send_Keepalive_Request" + self.interval = 3500 + self.desc = "Send an IMC2 keepalive-request packet" + self.persistent = True + def at_repeat(self): + for channel in IMC2_CHANNELS: + channel.send_packet(pck.IMC2PacketKeepAliveRequest()) + def is_valid(self): + "Is only valid as long as there are channels to update" + return IMC2_CHANNELS + +class Prune_Inactive_Muds(Script): + """ + Prunes games that have not sent is-alive packets for a while. If + we haven't heard from them, they're probably not connected or don't + implement the protocol correctly. In either case, good riddance to them. + """ + def at_script_creation(self): + self.key = "IMC2_Prune_Inactive_Muds" + self.interval = 1800 + self.desc = "Check IMC2 list for inactive games" + self.persistent = True + self.inactive_threshold = 3599 + def at_repeat(self): + for name, mudinfo in IMC2_MUDLIST.mud_list.items(): + if time() - mudinfo.last_updated > self.inactive_threshold: + del IMC2_MUDLIST.mud_list[name] + def is_valid(self): + "Is only valid as long as there are channels to update" + return IMC2_CHANNELS + +class Sync_Server_Channel_List(Script): + """ + Re-syncs the network's channel list. This will + cause a cascade of reply packets of a certain type + from the network. These are handled by the protocol, + gradually updating the channel cache. + """ + def at_script_creation(self): + self.key = "IMC2_Sync_Server_Channel_List" + self.interval = 24 * 3600 # once every day + self.desc = "Re-sync IMC2 network channel list" + self.persistent = True + def at_repeat(self): + checked_networks = [] + for channel in self.IMC2_CHANNELS: + network = channel.external_config.split['|'][0] + if not network in checked_networks: + channel.send_packet(pkg.IMC2PacketIceRefresh()) + checked_networks.append(network) + +# +# IMC2 protocol +# + +class IMC2Protocol(telnet.StatefulTelnetProtocol): + """ + Provides the abstraction for the IMC2 protocol. Handles connection, + authentication, and all necessary packets. + """ + def __init__(self): + global IMC2_CHANNELS + IMC2_CHANNELS.append(self) + self.is_authenticated = False + self.auth_type = None + self.server_name = None + self.network_name = None + self.sequence = None + + + def connectionMade(self): + """ + Triggered after connecting to the IMC2 network. + """ + self.auth_type = "plaintext" + logger.log_infomsg("IMC2: Connected to network server.") + logger.log_infomsg("IMC2: Sending authentication packet.") + self.send_packet(pck.IMC2PacketAuthPlaintext()) + + def send_packet(self, packet): + """ + Given a sub-class of IMC2Packet, assemble the packet and send it + on its way to the IMC2 server. + + Evennia -> IMC2 + """ + if self.sequence: + # This gets incremented with every command. + self.sequence += 1 + packet.imc2_protocol = self + packet_str = utils.to_str(packet.assemble(self.factory.mudname, self.factory.client_pwd, self.factory.server_pwd)) + if IMC2_DEBUG: + logger.log_infomsg("IMC2: SENT> %s" % packet_str) + self.sendLine(packet_str) + + def _parse_auth_response(self, line): + """ + Parses the IMC2 network authentication packet. + """ + if self.auth_type == "plaintext": + # Plain text passwords. + # SERVER Sends: PW version= + + if IMC2_DEBUG: + logger.log_infomsg("IMC2: AUTH< %s" % line) + + line_split = line.split(' ') + pw_present = line_split[0] == 'PW' + autosetup_present = line_split[0] == 'autosetup' + + if "reject" in line_split: + auth_message = "IMC2 server rejected connection." + logger.log_infomsg(auth_message) + msg_info(auth_message) + return + + if pw_present: + self.server_name = line_split[1] + self.network_name = line_split[4] + elif autosetup_present: + logger.log_infomsg("IMC2: Autosetup response found.") + self.server_name = line_split[1] + self.network_name = line_split[3] + self.is_authenticated = True + self.sequence = int(time()) + + # Log to stdout and notify over MUDInfo. + auth_message = "Successfully authenticated to the '%s' network." % self.network_name + logger.log_infomsg('IMC2: %s' % auth_message) + msg_info(auth_message) + + # Ask to see what other MUDs are connected. + self.send_packet(pck.IMC2PacketKeepAliveRequest()) + # IMC2 protocol states that KeepAliveRequests should be followed + # up by the requester sending an IsAlive packet. + self.send_packet(pck.IMC2PacketIsAlive()) + # Get a listing of channels. + self.send_packet(pck.IMC2PacketIceRefresh()) + + def _msg_evennia(self, packet): + """ + Handle the sending of packet data to Evennia channel + (Message from IMC2 -> Evennia) + """ + conn_name = packet.optional_data.get('channel', None) + # If the packet lacks the 'echo' key, don't bother with it. + has_echo = packet.optional_data.get('echo', None) + if conn_name and has_echo: + # The second half of this is the channel name: Server:Channel + chan_name = conn_name.split(':', 1)[1] + key = "imc2_%s" % conn_name + # Look for matching IMC2 channel maps. + conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key) + if not conns: + return + + # Format the message to send to local channel. + message = '[%s] %s@%s: %s' % (self.factory.evennia_channel, packet.sender, packet.origin, packet.optional_data.get('text')) + + for conn in conns: + if conn.channel: + conn.to_channel(message) + + def _format_tell(self, packet): + """ + Handle tells over IMC2 by formatting the text properly + """ + return "%s@%s IMC tells: %s" % (packet.sender, packet.origin, + packet.optional_data.get('text', 'ERROR: No text provided.')) + + def lineReceived(self, line): + """ + Triggered when text is received from the IMC2 network. Figures out + what to do with the packet. + IMC2 -> Evennia + """ + if not self.is_authenticated: + self._parse_auth_response(line) + else: + if IMC2_DEBUG and not 'is-alive' in line: + # if IMC2_DEBUG mode is on, print the contents of the packet + # to stdout. + logger.log_infomsg("PACKET: %s" % line) + + # Parse the packet and encapsulate it for easy access + packet = pck.IMC2Packet(self.factory.mudname, packet_str=line) + + if IMC2_DEBUG and packet.packet_type not in ['is-alive', 'keepalive-request']: + # Print the parsed packet's __str__ representation. + # is-alive and keepalive-requests happen pretty frequently. + # Don't bore us with them in stdout. + logger.log_infomsg(str(packet)) + + + # Figure out what kind of packet we're dealing with and hand it + # off to the correct handler. + + if packet.packet_type == 'is-alive': + IMC2_MUDLIST.update_mud_from_packet(packet) + elif packet.packet_type == 'keepalive-request': + # Don't need to check the destination, we only receive these + # packets when they are intended for us. + self.send_packet(pck.IMC2PacketIsAlive()) + elif packet.packet_type == 'ice-msg-b': + self._msg_evennia(packet) + elif packet.packet_type == 'whois-reply': + handle_whois_reply(packet) + elif packet.packet_type == 'close-notify': + IMC2_MUDLIST.remove_mud_from_packet(packet) + elif packet.packet_type == 'ice-update': + IMC2_CHANLIST.update_channel_from_packet(packet) + elif packet.packet_type == 'ice-destroy': + IMC2_CHANLIST.remove_channel_from_packet(packet) + elif packet.packet_type == 'tell': + player = search.players(packet.target) + if not player: + return + player.msg(self._format_tell(packet)) + + def msg_imc2(self, message, from_obj=None): + """ + Called by Evennia to send a message through the imc2 connection + """ + conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key) + if not conns: + return + if from_obj: + if hasattr(from_obj, 'key'): + from_name = from_obj.key + else: + from_name = from_obj + else: + from_name = self.factory.mudname + # send the packet + self.send_packet(pck.IMC2PacketIceMsgBroadcasted(self.factory.network, self.factory.channel, + from_name, message)) + +class IMC2Factory(protocol.ClientFactory): + """ + Creates instances of the IMC2Protocol. Should really only ever create one + in our particular instance. Tied in via src/server.py. + """ + protocol = IMC2Protocol + + def __init__(self, key, channel, network, port, mudname, client_pwd, server_pwd, evennia_channel): + self.key = key + self.mudname = mudname + self.channel = channel + self.pretty_key = "%s:%s/%s (%s)" % (network, port, channel, mudname) + self.network = network + self.protocol_version = '2' + self.client_pwd = client_pwd + self.server_pwd = server_pwd + self.evennia_channel = evennia_channel + + + def clientConnectionFailed(self, connector, reason): + message = 'Connection failed: %s' % reason.getErrorMessage() + msg_info(message) + logger.log_errmsg('IMC2: %s' % message) + + def clientConnectionLost(self, connector, reason): + message = 'Connection lost: %s' % reason.getErrorMessage() + msg_info(message) + logger.log_errmsg('IMC2: %s' % message) + + +def build_connection_key(channel, imc2_network, imc2_port, imc2_channel, imc2_mudname): + "Build an id hash for the connection" + if hasattr(channel, 'key'): + channel = channel.key + return "imc2_%s:%s/%s(%s)<>%s" % (imc2_network, imc2_port, imc2_channel, imc2_mudname, channel) + +def build_service_key(key): + return "IMC2:%s" % key + + +def start_scripts(validate=False): + """ + Start all the needed scripts + """ + + if validate: + from src.utils import reloads + reloads.reload_scripts() + return + if not search.scripts("IMC2_Send_IsAlive"): + create.create_script(Send_IsAlive) + if not search.scripts("IMC2_Send_Keepalive_Request"): + create.create_script(Send_Keepalive_Request) + if not search.scripts("IMC2_Prune_Inactive_Muds"): + create.create_script(Prune_Inactive_Muds) + if not search.scripts("IMC2_Sync_Server_Channel_List"): + create.create_script(Sync_Server_Channel_List) + +def create_connection(channel, imc2_network, imc2_port, imc2_channel, imc2_mudname, imc2_client_pwd, imc2_server_pwd): + """ + This will create a new IMC2<->channel connection. + """ + if not type(channel) == Channel: + new_channel = Channel.objects.filter(db_key=channel) + if not new_channel: + logger.log_errmsg("Cannot attach IMC2<->Evennia: Evennia Channel '%s' not found" % channel) + return False + channel = new_channel[0] + key = build_connection_key(channel, imc2_network, imc2_port, imc2_channel, imc2_mudname) + + old_conns = ExternalChannelConnection.objects.filter(db_external_key=key) + if old_conns: + return False + config = "%s|%s|%s|%s|%s|%s" % (imc2_network, imc2_port, imc2_channel, imc2_mudname, imc2_client_pwd, imc2_server_pwd) + # how the channel will be able to contact this protocol + send_code = "from src.comms.imc2 import IMC2_CHANNELS\n" + send_code += "matched_imc2s = [imc2 for imc2 in IMC2_CHANNELS if imc2.factory.key == '%s']\n" % key + send_code += "[imc2.msg_imc2(message, from_obj=from_obj) for imc2 in matched_imc2s]\n" + conn = ExternalChannelConnection(db_channel=channel, db_external_key=key, db_external_send_code=send_code, + db_external_config=config) + conn.save() + + # connect + connect_to_imc2(conn) + # start scripts (if needed) + start_scripts() + return True + +def delete_connection(channel, imc2_network, imc2_port, imc2_channel, mudname): + "Destroy a connection" + if hasattr(channel, 'key'): + channel = channel.key + + key = build_connection_key(channel, imc2_network, imc2_port, imc2_channel, mudname) + service_key = build_service_key(key) + try: + conn = ExternalChannelConnection.objects.get(db_external_key=key) + except Exception: + return False + conn.delete() + + try: + service = SESSIONS.server.services.getServiceNamed(service_key) + except Exception: + return True + if service.running: + SESSIONS.server.services.removeService(service) + # validate scripts + start_scripts(validate=True) + return True + +def connect_to_imc2(connection): + "Create the imc instance and connect to the IMC2 network and channel." + # get config + key = utils.to_str(connection.external_key) + service_key = build_service_key(key) + imc2_network, imc2_port, imc2_channel, imc2_mudname, imc2_client_pwd, imc2_server_pwd = \ + [utils.to_str(conf) for conf in connection.external_config.split('|')] + # connect + imc = internet.TCPClient(imc2_network, int(imc2_port), IMC2Factory(key, imc2_channel, imc2_network, imc2_port, imc2_mudname, + imc2_client_pwd, imc2_server_pwd, connection.channel.key)) + imc.setName(service_key) + SESSIONS.server.services.addService(imc) + +def connect_all(): + """ + Activate all imc2 bots. + + Returns a list of (key, TCPClient) tuples for server to properly set services. + """ + connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='imc2_') + for connection in connections: + connect_to_imc2(connection) + if connections: + start_scripts() diff --git a/src/imc2/__init__.py b/src/comms/imc2lib/__init__.py similarity index 100% rename from src/imc2/__init__.py rename to src/comms/imc2lib/__init__.py diff --git a/src/imc2/imc_ansi.py b/src/comms/imc2lib/imc2_ansi.py similarity index 93% rename from src/imc2/imc_ansi.py rename to src/comms/imc2lib/imc2_ansi.py index 53694df31d..0e7d97778c 100644 --- a/src/imc2/imc_ansi.py +++ b/src/comms/imc2lib/imc2_ansi.py @@ -1,12 +1,13 @@ """ ANSI parser - this adds colour to text according to special markup strings. + +This is a IMC2 complacent version. """ -from src.utils import ansi -from ansi import BaseParser, ANSITable +from src.utils.ansi import ANSIParser, ANSITable -class IMCANSIParser(BaseParser): +class IMCANSIParser(ANSIParser): """ This parser is per the IMC2 specification. """ diff --git a/src/imc2/reply_listener.py b/src/comms/imc2lib/imc2_listeners.py similarity index 75% rename from src/imc2/reply_listener.py rename to src/comms/imc2lib/imc2_listeners.py index 0b6aefc70d..a7033dc2b8 100644 --- a/src/imc2/reply_listener.py +++ b/src/comms/imc2lib/imc2_listeners.py @@ -1,13 +1,12 @@ """ This module handles some of the -reply packets like whois-reply. """ -#TODO: This is deprecated! -from src.objects.models import Object -from src.imc2 import imc_ansi +from src.objects.models import ObjectDB +from src.comms.imc2lib import imc2_ansi def handle_whois_reply(packet): try: - pobject = Object.objects.get(id=packet.target) + pobject = ObjectDB.objects.get(id=packet.target) response_text = imc_ansi.parse_ansi(packet.optional_data.get('text', 'Unknown')) pobject.emit_to('Whois reply from %s: %s' % (packet.origin, diff --git a/src/imc2/packets.py b/src/comms/imc2lib/imc2_packets.py similarity index 94% rename from src/imc2/packets.py rename to src/comms/imc2lib/imc2_packets.py index 5ddf7c3cd2..f104160777 100644 --- a/src/imc2/packets.py +++ b/src/comms/imc2lib/imc2_packets.py @@ -1,13 +1,10 @@ """ IMC2 packets. These are pretty well documented at: http://www.mudbytes.net/index.php?a=articles&s=imc2_protocol + """ import shlex -if __name__ == "__main__": - class settings: - IMC2_MUDNAME = "BLAH" -else: - from django.conf import settings +from django.conf import settings class Lexxer(shlex.shlex): """ @@ -26,16 +23,18 @@ class IMC2Packet(object): Base IMC2 packet class. This is generally sub-classed, aside from using it to parse incoming packets from the IMC2 network server. """ - def __init__(self, packet_str=None): + def __init__(self, mudname=None, packet_str=None): """ Optionally, parse a packet and load it up. """ # The following fields are all according to the basic packet format of: # @ @ self.sender = None - self.origin = settings.IMC2_MUDNAME + if not mudname: + mudname = settings.SERVERNAME + self.origin = mudname self.sequence = None - self.route = settings.IMC2_MUDNAME + self.route = mudname self.packet_type = None self.target = None self.destination = None @@ -153,9 +152,11 @@ class IMC2Packet(object): # None value. Do something or other. return 'Unknown' - def assemble(self): + def assemble(self, mudname=None, client_pwd=None, server_pwd=None): """ Assembles the packet and returns the ready-to-send string. + Note that the arguments are not used, they are there for + consistency across all packets. """ self.sequence = self.imc2_protocol.sequence packet = "%s@%s %s %s %s %s@%s %s\n" % ( @@ -184,17 +185,13 @@ class IMC2PacketAuthPlaintext(object): client will be expected in SHA256-AUTH format if the server supports it. """ - def assemble(self): + def assemble(self, mudname=None, client_pwd=None, server_pwd=None): """ This is one of two strange packets, just assemble the packet manually and go. """ - return 'PW %s %s version=%s autosetup %s\n' %( - settings.IMC2_MUDNAME, - settings.IMC2_CLIENT_PW, - settings.IMC2_PROTOCOL_VERSION, - settings.IMC2_SERVER_PW) - + return 'PW %s %s version=2 autosetup %s\n' %(mudname, client_pwd, server_pwd) + class IMC2PacketKeepAliveRequest(IMC2Packet): """ Description: @@ -260,7 +257,7 @@ class IMC2PacketIsAlive(IMC2Packet): self.target = '*' self.destination = '*' self.optional_data = {'versionid': 'Evennia IMC2', - 'url': '"http://evennia.com"', + 'url': '"http://www.evennia.com"', 'host': 'test.com', 'port': '5555'} @@ -755,3 +752,4 @@ if __name__ == "__main__": packstr = "Kayle@MW 1234567 MW!Server02!Server01 ice-msg-b *@* channel=Server01:ichat text=\"*they're going woot\" emote=0 echo=1" packstr = "*@Lythelian 1234567 Lythelian!Server01 is-alive *@* versionid=\"Tim's LPC IMC2 client 30-Jan-05 / Dead Souls integrated\" networkname=Mudbytes url=http://dead-souls.net host=70.32.76.142 port=6666 sha256=0" print IMC2Packet(packstr) + diff --git a/src/imc2/trackers.py b/src/comms/imc2lib/imc2_trackers.py similarity index 92% rename from src/imc2/trackers.py rename to src/comms/imc2lib/imc2_trackers.py index 5c9230a775..f2ce2bf45c 100644 --- a/src/imc2/trackers.py +++ b/src/comms/imc2lib/imc2_trackers.py @@ -102,8 +102,3 @@ class IMC2ChanList(object): except KeyError: # No matching entry, no big deal. pass - -# Use this instance to keep track of the other games on the network. -IMC2_MUDLIST = IMC2MudList() -# Tracks the list of available channels on the network. -IMC2_CHANLIST = IMC2ChanList() \ No newline at end of file diff --git a/src/comms/irc.py b/src/comms/irc.py index e6e457f29a..4760fbfd36 100644 --- a/src/comms/irc.py +++ b/src/comms/irc.py @@ -36,12 +36,12 @@ class IRC_Bot(irc.IRCClient): nickname = property(_get_nickname) def signedOn(self): - global IRC_CHANNELS - self.join(self.factory.channel) - # This is the first point the protocol is instantiated. # add this protocol instance to the global list so we # can access it later to send data. + global IRC_CHANNELS + self.join(self.factory.channel) + IRC_CHANNELS.append(self) #msg_info("Client connecting to %s.'" % (self.factory.channel)) @@ -78,7 +78,7 @@ class IRC_Bot(irc.IRCClient): """ self.msg(utils.to_str(self.factory.channel), utils.to_str(msg)) -class Factory(protocol.ClientFactory): +class IRCbotFactory(protocol.ClientFactory): protocol = IRC_Bot def __init__(self, key, channel, network, port, nickname, evennia_channel): self.key = key @@ -101,9 +101,11 @@ class Factory(protocol.ClientFactory): msg_info(msg) logger.log_errmsg(msg) -def build_connection_key(irc_network, irc_port, irc_channel, irc_bot_nick): +def build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick): "Build an id hash for the connection" - return "irc_%s:%s%s(%s)" % (irc_network, irc_port, irc_channel, irc_bot_nick) + if hasattr(channel, 'key'): + channel = channel.key + return "irc_%s:%s%s(%s)<>%s" % (irc_network, irc_port, irc_channel, irc_bot_nick, channel) def build_service_key(key): return "IRCbot:%s" % key @@ -118,7 +120,7 @@ def create_connection(channel, irc_network, irc_port, irc_channel, irc_bot_nick) logger.log_errmsg("Cannot attach IRC<->Evennia: Evennia Channel '%s' not found" % channel) return False channel = new_channel[0] - key = build_connection_key(irc_network, irc_port, irc_channel, irc_bot_nick) + key = build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick) old_conns = ExternalChannelConnection.objects.filter(db_external_key=key) if old_conns: @@ -136,9 +138,12 @@ def create_connection(channel, irc_network, irc_port, irc_channel, irc_bot_nick) connect_to_irc(conn) return True -def delete_connection(irc_network, irc_port, irc_channel, irc_bot_nick): +def delete_connection(channel, irc_network, irc_port, irc_channel, irc_bot_nick): "Destroy a connection" - key = build_connection_key(irc_network, irc_port, irc_channel, irc_bot_nick) + if hasattr(channel, 'key'): + channel = channel.key + + key = build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick) service_key = build_service_key(key) try: conn = ExternalChannelConnection.objects.get(db_external_key=key) @@ -161,7 +166,7 @@ def connect_to_irc(connection): service_key = build_service_key(key) irc_network, irc_port, irc_channel, irc_bot_nick = [utils.to_str(conf) for conf in connection.external_config.split('|')] # connect - bot = internet.TCPClient(irc_network, int(irc_port), Factory(key, irc_channel, irc_network, irc_port, irc_bot_nick, + bot = internet.TCPClient(irc_network, int(irc_port), IRCbotFactory(key, irc_channel, irc_network, irc_port, irc_bot_nick, connection.channel.key)) bot.setName(service_key) SESSIONS.server.services.addService(bot) diff --git a/src/imc2/admin.py b/src/imc2/admin.py deleted file mode 100644 index 460e5d11be..0000000000 --- a/src/imc2/admin.py +++ /dev/null @@ -1,7 +0,0 @@ -from src.imc2.models import IMC2ChannelMapping -from django.contrib import admin - -class IMC2ChannelMappingAdmin(admin.ModelAdmin): - list_display = ('channel', 'imc2_server_name', - 'imc2_channel_name', 'is_enabled') -admin.site.register(IMC2ChannelMapping, IMC2ChannelMappingAdmin) \ No newline at end of file diff --git a/src/imc2/connection.py b/src/imc2/connection.py deleted file mode 100644 index 3ed7591883..0000000000 --- a/src/imc2/connection.py +++ /dev/null @@ -1,201 +0,0 @@ -""" -IMC2 client module. Handles connecting to and communicating with an IMC2 server. -""" -import telnetlib -from time import time -from twisted.internet.protocol import ClientFactory -from twisted.protocols.basic import LineReceiver -from twisted.internet import reactor, task -from twisted.conch.telnet import StatefulTelnetProtocol -from django.conf import settings - -from src.utils import logger -from src.server import sessionhandler -from src.imc2.packets import * -from src.imc2.trackers import * -from src.imc2 import reply_listener -from src.imc2.models import IMC2ChannelMapping -#from src import comsys - -# The active instance of IMC2Protocol. Set at server startup. -IMC2_PROTOCOL_INSTANCE = None - -def cemit_info(message): - """ - Channel emits info to the appropriate info channel. By default, this - is MUDInfo. - """ - comsys.send_cmessage(settings.COMMCHAN_IMC2_INFO, 'IMC: %s' % message, - from_external="IMC2") - -class IMC2Protocol(StatefulTelnetProtocol): - """ - Provides the abstraction for the IMC2 protocol. Handles connection, - authentication, and all necessary packets. - """ - def __init__(self): - message = "Client connecting to %s:%s..." % ( - settings.IMC2_SERVER_ADDRESS, - settings.IMC2_SERVER_PORT) - logger.log_infomsg('IMC2: %s' % message) - cemit_info(message) - global IMC2_PROTOCOL_INSTANCE - IMC2_PROTOCOL_INSTANCE = self - self.is_authenticated = False - self.auth_type = None - self.server_name = None - self.network_name = None - self.sequence = None - - def connectionMade(self): - """ - Triggered after connecting to the IMC2 network. - """ - logger.log_infomsg("IMC2: Connected to network server.") - self.auth_type = "plaintext" - logger.log_infomsg("IMC2: Sending authentication packet.") - self.send_packet(IMC2PacketAuthPlaintext()) - - def send_packet(self, packet): - """ - Given a sub-class of IMC2Packet, assemble the packet and send it - on its way. - """ - if self.sequence: - # This gets incremented with every command. - self.sequence += 1 - - packet.imc2_protocol = self - packet_str = str(packet.assemble()) - #logger.log_infomsg("IMC2: SENT> %s" % packet_str) - self.sendLine(packet_str) - - def _parse_auth_response(self, line): - """ - Parses the IMC2 network authentication packet. - """ - if self.auth_type == "plaintext": - """ - SERVER Sends: PW version= - """ - line_split = line.split(' ') - pw_present = line_split[0] == 'PW' - autosetup_present = line_split[0] == 'autosetup' - - if pw_present: - self.server_name = line_split[1] - self.network_name = line_split[4] - elif autosetup_present: - logger.log_infomsg("IMC2: Autosetup response found.") - self.server_name = line_split[1] - self.network_name = line_split[3] - self.is_authenticated = True - self.sequence = int(time()) - - # Log to stdout and notify over MUDInfo. - auth_message = "Successfully authenticated to the '%s' network." % self.network_name - logger.log_infomsg('IMC2: %s' % auth_message) - cemit_info(auth_message) - - # Ask to see what other MUDs are connected. - self.send_packet(IMC2PacketKeepAliveRequest()) - # IMC2 protocol states that KeepAliveRequests should be followed - # up by the requester sending an IsAlive packet. - self.send_packet(IMC2PacketIsAlive()) - # Get a listing of channels. - self.send_packet(IMC2PacketIceRefresh()) - - def _handle_channel_mappings(self, packet): - """ - Received a message. Look for an IMC2 channel mapping and - route it accordingly. - """ - chan_name = packet.optional_data.get('channel', None) - # If the packet lacks the 'echo' key, don't bother with it. - has_echo = packet.optional_data.get('echo', None) - if chan_name and has_echo: - # The second half of this is the channel name: Server:Channel - chan_name = chan_name.split(':', 1)[1] - try: - # Look for matching IMC2 channel maps. - mappings = IMC2ChannelMapping.objects.filter(imc2_channel_name=chan_name) - # Format the message to cemit to the local channel. - message = '%s@%s: %s' % (packet.sender, - packet.origin, - packet.optional_data.get('text')) - # Bombs away. - for mapping in mappings: - if mapping.channel: - comsys.send_cmessage(mapping.channel, message, - from_external="IMC2") - except IMC2ChannelMapping.DoesNotExist: - # No channel mapping found for this message, ignore it. - pass - - def lineReceived(self, line): - """ - Triggered when text is received from the IMC2 network. Figures out - what to do with the packet. - """ - if not self.is_authenticated: - self._parse_auth_response(line) - else: - if settings.IMC2_DEBUG and 'is-alive' not in line: - # if IMC2_DEBUG mode is on, print the contents of the packet - # to stdout. - logger.log_infomsg("PACKET: %s" % line) - - # Parse the packet and encapsulate it for easy access - packet = IMC2Packet(packet_str = line) - - if settings.IMC2_DEBUG and packet.packet_type not in ['is-alive', 'keepalive-request']: - # Print the parsed packet's __str__ representation. - # is-alive and keepalive-requests happen pretty frequently. - # Don't bore us with them in stdout. - logger.log_infomsg(packet) - - """ - Figure out what kind of packet we're dealing with and hand it - off to the correct handler. - """ - if packet.packet_type == 'is-alive': - IMC2_MUDLIST.update_mud_from_packet(packet) - elif packet.packet_type == 'keepalive-request': - # Don't need to check the destination, we only receive these - # packets when they are intended for us. - self.send_packet(IMC2PacketIsAlive()) - elif packet.packet_type == 'ice-msg-b': - self._handle_channel_mappings(packet) - elif packet.packet_type == 'whois-reply': - reply_listener.handle_whois_reply(packet) - elif packet.packet_type == 'close-notify': - IMC2_MUDLIST.remove_mud_from_packet(packet) - elif packet.packet_type == 'ice-update': - IMC2_CHANLIST.update_channel_from_packet(packet) - elif packet.packet_type == 'ice-destroy': - IMC2_CHANLIST.remove_channel_from_packet(packet) - elif packet.packet_type == 'tell': - sessions = sessionhandler.find_sessions_from_username(packet.target) - for session in sessions: - session.msg("%s@%s IMC tells: %s" % - (packet.sender, - packet.origin, - packet.optional_data.get('text', - 'ERROR: No text provided.'))) - -class IMC2ClientFactory(ClientFactory): - """ - Creates instances of the IMC2Protocol. Should really only ever create one - in our particular instance. Tied in via src/server.py. - """ - protocol = IMC2Protocol - - def clientConnectionFailed(self, connector, reason): - message = 'Connection failed: %s' % reason.getErrorMessage() - cemit_info(message) - logger.log_errmsg('IMC2: %s' % message) - - def clientConnectionLost(self, connector, reason): - message = 'Connection lost: %s' % reason.getErrorMessage() - cemit_info(message) - logger.log_errmsg('IMC2: %s' % message) diff --git a/src/imc2/events.py b/src/imc2/events.py deleted file mode 100644 index 42f4b645fc..0000000000 --- a/src/imc2/events.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -This module contains all IMC2 events that are triggered periodically. -Most of these are used to maintain the existing connection and keep various -lists/caches up to date. -""" -# TODO: This is deprecated! - -from time import time -#from src import events -#from src import scheduler -from src.imc2 import connection as imc2_conn -from src.imc2.packets import * -from src.imc2.trackers import IMC2_MUDLIST - -class IEvt_IMC2_Send_IsAlive(events.IntervalEvent): - """ - Event: Send periodic keepalives to network neighbors. This lets the other - games know that our game is still up and connected to the network. Also - provides some useful information about the client game. - """ - def __init__(self): - super(IEvt_IMC2_Send_IsAlive, self).__init__() - self.name = 'IEvt_IMC2_Send_IsAlive' - # Send keep-alive packets every 15 minutes. - self.interval = 900 - self.description = "Send an IMC2 is-alive packet." - - def event_function(self): - """ - This is the function that is fired every self.interval seconds. - """ - try: - imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(IMC2PacketIsAlive()) - except AttributeError: - #this will happen if we are not online in the first place - #(like during development) /Griatch - pass - -class IEvt_IMC2_Send_Keepalive_Request(events.IntervalEvent): - """ - Event: Sends a keepalive-request to connected games in order to see who - is connected. - """ - def __init__(self): - super(IEvt_IMC2_Send_Keepalive_Request, self).__init__() - self.name = 'IEvt_IMC2_Send_Keepalive_Request' - self.interval = 3500 - self.description = "Send an IMC2 keepalive-request packet." - - def event_function(self): - """ - This is the function that is fired every self.interval seconds. - """ - imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(IMC2PacketKeepAliveRequest()) - -class IEvt_IMC2_Prune_Inactive_Muds(events.IntervalEvent): - """ - Event: Prunes games that have not sent is-alive packets for a while. If - we haven't heard from them, they're probably not connected or don't - implement the protocol correctly. In either case, good riddance to them. - """ - def __init__(self): - super(IEvt_IMC2_Prune_Inactive_Muds, self).__init__() - self.name = 'IEvt_IMC2_Prune_Inactive_Muds' - # Check every 30 minutes. - self.interval = 1800 - self.description = "Check IMC2 list for inactive games." - # Threshold for game inactivity (in seconds). - self.inactive_thresh = 3599 - - def event_function(self): - """ - This is the function that is fired every self.interval seconds. - """ - for name, mudinfo in IMC2_MUDLIST.mud_list.items(): - # If we haven't heard from the game within our threshold time, - # we assume that they're dead. - if time() - mudinfo.last_updated > self.inactive_thresh: - del IMC2_MUDLIST.mud_list[name] - -def add_events(): - """ - Adds the IMC2 events to the scheduler. - """ - scheduler.add_event(IEvt_IMC2_Send_IsAlive()) - scheduler.add_event(IEvt_IMC2_Prune_Inactive_Muds()) - scheduler.add_event(IEvt_IMC2_Send_Keepalive_Request()) diff --git a/src/imc2/models.py b/src/imc2/models.py deleted file mode 100644 index 17bab9b293..0000000000 --- a/src/imc2/models.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.db import models -from django.conf import settings -from src.comms.models import Channel - -class IMC2ChannelMapping(models.Model): - """ - Each IMC2ChannelMapping object determines which in-game channel incoming - IMC2 messages are routed to. - """ - channel = models.ForeignKey(Channel) - imc2_server_name = models.CharField(max_length=78) - imc2_channel_name = models.CharField(max_length=78) - is_enabled = models.BooleanField(default=True) - - class Meta: - verbose_name = "IMC2 Channel mapping" - verbose_name_plural = "IMC2 Channel mappings" - #permissions = settings.PERM_IMC2 - - def __str__(self): - return "%s <-> %s" % (self.channel, self.imc2_channel_name) diff --git a/src/locks/lockhandler.py b/src/locks/lockhandler.py index 501c3ac0b2..5820dac5a3 100644 --- a/src/locks/lockhandler.py +++ b/src/locks/lockhandler.py @@ -169,7 +169,7 @@ class LockHandler(object): elif hasattr(self.obj, 'msg'): self.obj.msg(message) else: - logger.log_trace("%s: %s" % (self.obj, message)) + logger.log_errmsg("%s: %s" % (self.obj, message)) def _parse_lockstring(self, storage_lockstring): """ @@ -230,7 +230,8 @@ class LockHandler(object): def add(self, lockstring, log_obj=None): """ - Add a new, single lockstring on the form ':' + Add a new lockstring on the form ':'. Multiple + access types should be separated by semicolon (;). If log_obj is given, it will be fed error information. """ diff --git a/src/players/models.py b/src/players/models.py index 39f03e5327..a3abb3b3a7 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -250,7 +250,7 @@ class PlayerDB(TypedObject): from_obj.at_msg_send(outgoing_string, to_obj=self, data=data) except Exception: pass - if self.character: + if object.__getattribute__(self, "character"): if self.character.at_msg_receive(outgoing_string, from_obj=from_obj, data=data): for session in object.__getattribute__(self, 'sessions'): session.msg(outgoing_string, data) diff --git a/src/server/server.py b/src/server/server.py index 7bd68c08e1..27d0e0597d 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -174,6 +174,9 @@ class Evennia(object): # #------------------------------------------------------------ +# Tell the system the server is starting up; some things are not available yet +ServerConfig.objects.conf("server_starting_mode", True) + # twistd requires us to define the variable 'application' so it knows # what to execute from. application = service.Application('Evennia') @@ -225,24 +228,19 @@ if WEBSERVER_ENABLED: webserver.setName('EvenniaWebServer%s' % port) EVENNIA.services.addService(webserver) - -if IMC2_ENABLED: - - # IMC2 channel connections - - from src.imc2.connection import IMC2ClientFactory - from src.imc2 import events as imc2_events - imc2_factory = IMC2ClientFactory() - svc = internet.TCPClient(settings.IMC2_SERVER_ADDRESS, - settings.IMC2_SERVER_PORT, - imc2_factory) - svc.setName('IMC2') - EVENNIA.services.addService(svc) - imc2_events.add_events() - if IRC_ENABLED: # IRC channel connections from src.comms import irc irc.connect_all() + +if IMC2_ENABLED: + + # IMC2 channel connections + + from src.comms import imc2 + imc2.connect_all() + +# clear server startup mode +ServerConfig.objects.conf("server_starting_mode", delete=True) diff --git a/src/server/telnet.py b/src/server/telnet.py index 36f0d84911..52b74c8b47 100644 --- a/src/server/telnet.py +++ b/src/server/telnet.py @@ -10,7 +10,7 @@ sessions etc. from twisted.conch.telnet import StatefulTelnetProtocol from django.conf import settings from src.server import session -from src.utils import ansi, utils +from src.utils import ansi, utils, logger ENCODINGS = settings.ENCODINGS @@ -107,47 +107,21 @@ class TelnetProtocol(StatefulTelnetProtocol, session.Session): """ Data Evennia -> Player access hook. 'data' argument is ignored. """ - if self.encoding: - try: - string = utils.to_str(string, encoding=self.encoding) - self.lineSend(ansi.parse_ansi(string, strip_ansi=not self.telnet_markup)) - return - except Exception: - pass - # malformed/wrong encoding defined on player - try some defaults - for encoding in ENCODINGS: - try: - string = utils.to_str(string, encoding=encoding) - err = None - break - except Exception, e: - err = str(e) - continue - if err: - self.lineSend(err) - else: - self.lineSend(ansi.parse_ansi(string, strip_ansi=not self.telnet_markup)) + try: + string = utils.to_str(string, encoding=self.encoding) + except Exception, e: + self.lineSend(str(e)) + return + self.lineSend(ansi.parse_ansi(string, strip_ansi=not self.telnet_markup)) def at_data_in(self, string, data=None): """ Line from Player -> Evennia. 'data' argument is not used. """ - if self.encoding: - try: - string = utils.to_unicode(string, encoding=self.encoding) - self.execute_cmd(string) - return - except Exception, e: - err = str(e) - print err - # malformed/wrong encoding defined on player - try some defaults - for encoding in ENCODINGS: - try: - string = utils.to_unicode(string, encoding=encoding) - err = None - break - except Exception, e: - err = str(e) - continue - self.execute_cmd(string) + try: + string = utils.to_unicode(string, encoding=self.encoding) + self.execute_cmd(string) + return + except Exception, e: + logger.log_errmsg(str(e)) diff --git a/src/server/webclient.py b/src/server/webclient.py index 4cdf456c6c..f3fc147bf5 100644 --- a/src/server/webclient.py +++ b/src/server/webclient.py @@ -26,7 +26,7 @@ from django.utils import simplejson from django.utils.functional import Promise from django.utils.encoding import force_unicode from django.conf import settings -from src.utils import utils +from src.utils import utils, logger from src.utils.text2html import parse_html from src.server import session from src.server.sessionhandler import SESSIONS @@ -258,27 +258,13 @@ class WebClientSession(session.Session): pass # string handling is similar to telnet - if self.encoding: - try: - string = utils.to_str(string, encoding=self.encoding) - self.client.lineSend(self.suid, parse_html(string)) - return - except Exception: - pass - # malformed/wrong encoding defined on player - try some defaults - for encoding in ENCODINGS: - try: - string = utils.to_str(string, encoding=encoding) - err = None - break - except Exception, e: - err = str(e) - continue - if err: - self.client.lineSend(self.suid, err) - else: - #self.client.lineSend(self.suid, ansi.parse_ansi(string, strip_ansi=True)) + try: + string = utils.to_str(string, encoding=self.encoding) self.client.lineSend(self.suid, parse_html(string)) + return + except Exception, e: + logger.log_trace() + def at_data_in(self, string, data=None): """ Input from Player -> Evennia (called by client protocol). @@ -290,22 +276,9 @@ class WebClientSession(session.Session): pass # the string part is identical to telnet - if self.encoding: - try: - string = utils.to_unicode(string, encoding=self.encoding) - self.execute_cmd(string) - return - except Exception, e: - err = str(e) - print err - # malformed/wrong encoding defined on player - try some defaults - for encoding in ENCODINGS: - try: - string = utils.to_unicode(string, encoding=encoding) - err = None - break - except Exception, e: - err = str(e) - continue - self.execute_cmd(string) - + try: + string = utils.to_unicode(string, encoding=self.encoding) + self.execute_cmd(string) + return + except Exception, e: + logger.log_trace() diff --git a/src/settings_default.py b/src/settings_default.py index 1788770741..fa62d76e8d 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -19,8 +19,7 @@ import os # Evennia base server config ################################################### -# This is the name of your server and/or site. -# Can be anything. +# This is the name of your game. Make it catchy! SERVERNAME = "Evennia" # Activate telnet service TELNET_ENABLED = True @@ -95,11 +94,8 @@ IDLE_COMMAND = "idle" # given, this list is tried, in order, aborting on the first match. # Add sets for languages/regions your players are likely to use. # (see http://en.wikipedia.org/wiki/Character_encoding) -ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"] -# The module holding text strings for the connection screen. -# This module should contain one ore more variables -# with strings defining the look of the screen. -CONNECTION_SCREEN_MODULE = "game.gamesrc.world.connection_screens" +ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"] + ################################################### # Evennia Database config @@ -155,6 +151,10 @@ ALTERNATE_OBJECT_SEARCH_ERROR_HANDLER = "" # objects without using dbrefs). (If not set, uses # src.objects.object_search_funcs.object_multimatch_parser). ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER = "" +# The module holding text strings for the connection screen. +# This module should contain one or more variables +# with strings defining the look of the screen. +CONNECTION_SCREEN_MODULE = "game.gamesrc.world.connection_screens" ################################################### # Default command sets @@ -209,9 +209,9 @@ BASE_BATCHPROCESS_PATH = 'game.gamesrc.world' ################################################### # You don't actually have to use this, but it affects the routines in -# src.utils.gametime.py and allows for a convenient measure to determine the -# current in-game time. You can of course read "week", "month" etc -# as your own in-game time units as desired. +# src.utils.gametime.py and allows for a convenient measure to +# determine the current in-game time. You can of course read "week", +# "month" etc as your own in-game time units as desired. #The time factor dictates if the game world runs faster (timefactor>1) # or slower (timefactor<1) than the real world. @@ -229,9 +229,9 @@ TIME_MONTH_PER_YEAR = 12 ################################################### # In-Game access ################################################### -# The access hiearchy, in climbing order. A higher -# permission in the hierarchy includes access of all -# levels below it. + +# The access hiearchy, in climbing order. A higher permission in the +# hierarchy includes access of all levels below it. PERMISSION_HIERARCHY = ("Players","PlayerHelpers","Builders", "Wizards", "Immortals") # The default permission given to all new players PERMISSION_PLAYER_DEFAULT = "Players" @@ -242,6 +242,7 @@ LOCK_FUNC_MODULES = ("src.locks.lockfuncs",) ################################################### # In-game Channels created from server start ################################################### + # Defines a dict with one key for each from-start # channel. Each key points to a tuple containing # (name, aliases, description, locks) @@ -262,47 +263,29 @@ CHANNEL_CONNECTINFO = ("MUDconnections", ('connections, mud_conns'), # External Channel connections ################################################### +# Note: You do *not* have to make your MUD open to +# the public to use the external connections, they +# operate as long as you have an internet connection, +# just like stand-alone chat clients. + # Evennia can connect to external IRC channels and # echo what is said on the channel to IRC and vice # versa. Obs - make sure the IRC network allows bots. -# If disabled, the default @irc2chan command won't be -# available in-game. -IRC_ENABLED = True - -# OBS: IMC is not implemented at this point! - -# IMC (Inter-MUD communication) allows for an evennia chat channel -# that connects to people on other MUDs also using the IMC. Your -# evennia server do *not* have to be open to the public to use IMC; it -# works as a stand-alone chat client. Evennia's IMC2 client was -# developed against MudByte's network. You must register your MUD on -# the network before you can use it, go to -# http://www.mudbytes.net/imc2-intermud-join-network. Choose 'Other -# unsupported IMC2 version' from the choices and and enter your -# information there. You have to enter the same 'short mud name', -# 'client password' and 'server password' as you define in this file. -# The Evennia discussion channel is on server02.mudbytes.net:9000. - -# Change to True if you want IMC active at all. -IMC2_ENABLED = False -# Which channel to tie to the imc2 connection -COMMCHAN_IMC2_INFO = 'MUDInfo' -# The hostname/ip address of your IMC2 server of choice. -IMC2_SERVER_ADDRESS = 'server02.mudbytes.net' -#IMC2_SERVER_ADDRESS = None -# The port to connect to on your IMC2 server. -IMC2_SERVER_PORT = 9000 -#IMC2_SERVER_PORT = None -# This is your game's IMC2 name on the network (e.g. "MyMUD"). -IMC2_MUDNAME = None -# Your IMC2 client-side password. Used to authenticate with your network. -IMC2_CLIENT_PW = None -# Your IMC2 server-side password. Used to verify your network's identity. -IMC2_SERVER_PW = None -# Emit additional debugging info to log. -IMC2_DEBUG = False -# This isn't something you should generally change. -IMC2_PROTOCOL_VERSION = '2' +# When enabled, command @irc2chan will be available in-game +IRC_ENABLED = False +# IMC (Inter-MUD communication) allows to connect an Evennia channel +# to an IMC2 server. This lets them talk to people on other MUDs also +# using IMC. Evennia's IMC2 client was developed against MudByte's +# network. You must register your MUD on the network before you can +# use it, go to http://www.mudbytes.net/imc2-intermud-join-network. +# Choose 'Other unsupported IMC2 version' from the choices and and +# enter your information there. You should enter the same 'short mud +# name' as your SERVERNAME above. When enabled, the command @imc2chan +# becomes available in-game. It will take 'client password' and +# 'server password' as chosen when registering on mudbytes. The +# Evennia discussion channel 'ievennia' is on +# server02.mudbytes.net:9000. +IMC2_ENABLED = False # OBS! DON'T CHANGE - IMC2 is not implemented yet! ################################################### diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 59194abbc0..4b576464c0 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -34,7 +34,7 @@ from django.contrib.contenttypes.models import ContentType from src.utils.idmapper.models import SharedMemoryModel from src.typeclasses import managers from src.locks.lockhandler import LockHandler -from src.utils import logger +from src.utils import logger, utils from src.utils.utils import is_iter, has_parent PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] @@ -187,13 +187,13 @@ class Attribute(SharedMemoryModel): Getter. Allows for value = self.value. """ try: - return self.validate_data(pickle.loads(str(self.db_value))) + return utils.to_unicode(self.validate_data(pickle.loads(utils.to_str(self.db_value)))) except pickle.UnpicklingError: return self.db_value #@value.setter def value_set(self, new_value): "Setter. Allows for self.value = value" - self.db_value = pickle.dumps(self.validate_data(new_value)) + self.db_value = utils.to_unicode(pickle.dumps(utils.to_str(self.validate_data(new_value)))) self.save() #@value.deleter def value_del(self): diff --git a/src/utils/reloads.py b/src/utils/reloads.py index 0938fe006a..4d13c0ac02 100644 --- a/src/utils/reloads.py +++ b/src/utils/reloads.py @@ -48,9 +48,8 @@ def start_reload_loop(): "default callback" cemit_info(" Asynchronous server reload finished.\n" + '-'*50) def at_err(e): - "error callback" - string = "%s\n reload: Asynchronous reset loop exited with an error." % e - string += "\n This might be harmless. Wait a moment then reload again to see if the problem persists." + "error callback" + string = "Reload: Asynchronous reset exited with an error:\n{r%s{n" % e.getErrorMessage() cemit_info(string) utils.run_async(run_loop, at_return, at_err) diff --git a/src/utils/utils.py b/src/utils/utils.py index 8d69922bc2..123f8514f3 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -12,6 +12,8 @@ from twisted.internet import threads from django.conf import settings from src.utils import ansi +ENCODINGS = settings.ENCODINGS + def is_iter(iterable): """ Checks if an object behaves iterably. However, @@ -225,29 +227,39 @@ def to_unicode(obj, encoding='utf-8'): needs to encode it back to utf-8 before writing to disk or printing. """ - if isinstance(obj, basestring) \ - and not isinstance(obj, unicode): + if isinstance(obj, basestring) and not isinstance(obj, unicode): try: obj = unicode(obj, encoding) + return obj except UnicodeDecodeError: - raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding)) - return obj + for alt_encoding in ENCODINGS: + try: + obj = unicode(obj, alt_encoding) + return obj + except UnicodeDecodeError: + pass + raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding)) + return obj def to_str(obj, encoding='utf-8'): """ - This encodes a unicode string - back to byte-representation, + This encodes a unicode string back to byte-representation, for printing, writing to disk etc. """ - if isinstance(obj, basestring) \ - and isinstance(obj, unicode): + if isinstance(obj, basestring) and isinstance(obj, unicode): try: obj = obj.encode(encoding) + return obj except UnicodeEncodeError: - raise Exception("Error: Unicode could not encode unicode string '%s'(%s) to a bytestring. " % (obj, encoding)) + for alt_encoding in ENCODINGS: + try: + obj = obj.encode(encoding) + return obj + except UnicodeEncodeError: + pass + raise Exception("Error: Unicode could not encode unicode string '%s'(%s) to a bytestring. " % (obj, encoding)) return obj - def validate_email_address(emailaddress): """ Checks if an email address is syntactically correct.