From 97991a2238b98c6df0b9d4c505376bc0fcc40ad6 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 27 Feb 2014 16:07:39 +0100 Subject: [PATCH] Implemented RSS feed reader along with updated rss2chan command. --- src/commands/default/cmdset_player.py | 2 +- src/commands/default/comms.py | 185 +++++++------ src/players/bots.py | 17 +- src/server/portal/irc.py | 17 +- src/server/portal/portalsessionhandler.py | 3 +- src/server/portal/rss.py | 322 +++++++++++++--------- src/server/server.py | 23 +- src/typeclasses/models.py | 10 +- 8 files changed, 327 insertions(+), 252 deletions(-) diff --git a/src/commands/default/cmdset_player.py b/src/commands/default/cmdset_player.py index ba4a2f1922..81e1485f3c 100644 --- a/src/commands/default/cmdset_player.py +++ b/src/commands/default/cmdset_player.py @@ -68,7 +68,7 @@ class PlayerCmdSet(CmdSet): self.add(comms.CmdCdesc()) self.add(comms.CmdPage()) self.add(comms.CmdIRC2Chan()) + self.add(comms.CmdRSS2Chan()) #self.add(comms.CmdIMC2Chan()) #self.add(comms.CmdIMCInfo()) #self.add(comms.CmdIMCTell()) - #self.add(comms.CmdRSS2Chan()) diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index a7aef78be8..3b66f4ce47 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -21,8 +21,8 @@ from src.commands.default.muxcommand import MuxCommand, MuxPlayerCommand __all__ = ("CmdAddCom", "CmdDelCom", "CmdAllCom", "CmdChannels", "CmdCdestroy", "CmdCBoot", "CmdCemit", "CmdCWho", "CmdChannelCreate", "CmdClock", "CmdCdesc", - "CmdPage", "CmdIRC2Chan")#, "CmdIMC2Chan", "CmdIMCInfo", - #"CmdIMCTell", "CmdRSS2Chan") + "CmdPage", "CmdIRC2Chan", "CmdRSS2Chan")#, "CmdIMC2Chan", "CmdIMCInfo", + #"CmdIMCTell") def find_channel(caller, channelname, silent=False, noaliases=False): @@ -798,7 +798,7 @@ class CmdIRC2Chan(MuxCommand): if 'list' in self.switches: # show all connections - ircbots = [bot.typeclass for bot in PlayerDB.objects.filter(db_is_bot=True)] + ircbots = [bot.typeclass for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")] if ircbots: from src.utils.evtable import EvTable table = EvTable("{wdbid{n", "{wbotname{n", "{wev-channel{n", "{wirc-channel{n", border="cells", maxwidth=78) @@ -849,7 +849,7 @@ class CmdIRC2Chan(MuxCommand): # re-use an existing bot bot = bot[0].typeclass if not bot.is_bot: - self.msg("Player '%s', which is not a bot, already exists." % botname) + self.msg("Player '%s' already exists and is not a bot." % botname) return else: bot = create.create_player(botname, None, None, typeclass=bots.IRCBot) @@ -857,6 +857,101 @@ class CmdIRC2Chan(MuxCommand): irc_network=irc_network, irc_port=irc_port) self.msg("Connection created. Starting IRC bot.") +# RSS connection +class CmdRSS2Chan(MuxCommand): + """ + link an evennia channel to an external RSS feed + + Usage: + @rss2chan[/switches] = + + Switches: + /disconnect - this will stop the feed and remove the connection to the + channel. + /remove - " + /list - show all rss->evennia mappings + + Example: + @rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic + + This creates an RSS reader that connects to a given RSS feed url. Updates + will be echoed as a title and news link to the given channel. The rate of + updating is set with the RSS_UPDATE_INTERVAL variable in settings (default + is every 10 minutes). + + When disconnecting you need to supply both the channel and url again so as + to identify the connection uniquely. + """ + + key = "@rss2chan" + locks = "cmd:serversetting(RSS_ENABLED) and pperm(Immortals)" + help_category = "Comms" + + def func(self): + "Setup the rss-channel mapping" + + # checking we have all we need + if not settings.RSS_ENABLED: + string = """RSS is not enabled. You need to activate it in game/settings.py.""" + self.msg(string) + return + try: + import feedparser + feedparser # to avoid checker error of not being used + except ImportError: + string = ("RSS requires python-feedparser (https://pypi.python.org/pypi/feedparser). " + "Install before continuing.") + self.msg(string) + return + + if 'list' in self.switches: + # show all connections + rssbots = [bot.typeclass for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")] + if rssbots: + from src.utils.evtable import EvTable + table = EvTable("{wdbid{n", "{wupdate rate{n", "{wev-channel", "{wRSS feed URL{n", border="cells", maxwidth=78) + for rssbot in rssbots: + table.add_row(rssbot.id, rssbot.db.rss_rate, rssbot.db.ev_channel, rssbot.db.rss_url) + self.caller.msg(table) + else: + self.msg("No rss bots found.") + return + + if('disconnect' in self.switches or 'remove' in self.switches or + 'delete' in self.switches): + botname = "rssbot-%s" % self.lhs + matches = PlayerDB.objects.filter(db_is_bot=True, db_key=botname) + if not matches: + # try dbref match + matches = PlayerDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#")) + if matches: + matches[0].delete() + self.msg("RSS connection destroyed.") + else: + self.msg("RSS connection/bot could not be removed, does it exist?") + return + + if not self.args or not self.rhs: + string = "Usage: @rss2chan[/switches] = " + self.msg(string) + return + channel = self.lhs + url = self.rhs + + botname = "rssbot-%s" % url + # create a new bot + bot = PlayerDB.objects.filter(username__iexact=botname) + if bot: + # re-use existing bot + bot = bot[0].typeclass + if not bot.is_bot: + self.msg("Player '%s' already exists and is not a bot." % botname) + return + else: + bot = create.create_player(botname, None, None, typeclass=bots.RSSBot) + bot.start(ev_channel=channel, rss_url=url, rss_rate=10) + self.msg("RSS reporter created. Fetching RSS.") + #class CmdIMC2Chan(MuxCommand): # """ @@ -1077,85 +1172,3 @@ class CmdIRC2Chan(MuxCommand): # self.msg("You paged {c%s@%s{n (over IMC): '%s'." % (target, destination, message)) # # -## RSS connection -#class CmdRSS2Chan(MuxCommand): -# """ -# link an evennia channel to an external RSS feed -# -# Usage: -# @rss2chan[/switches] = -# -# Switches: -# /disconnect - this will stop the feed and remove the connection to the -# channel. -# /remove - " -# /list - show all rss->evennia mappings -# -# Example: -# @rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic -# -# This creates an RSS reader that connects to a given RSS feed url. Updates -# will be echoed as a title and news link to the given channel. The rate of -# updating is set with the RSS_UPDATE_INTERVAL variable in settings (default -# is every 10 minutes). -# -# When disconnecting you need to supply both the channel and url again so as -# to identify the connection uniquely. -# """ -# -# key = "@rss2chan" -# locks = "cmd:serversetting(RSS_ENABLED) and pperm(Immortals)" -# help_category = "Comms" -# -# def func(self): -# "Setup the rss-channel mapping" -# -# if not settings.RSS_ENABLED: -# string = """RSS is not enabled. You need to activate it in game/settings.py.""" -# self.msg(string) -# return -# -# if 'list' in self.switches: -# # show all connections -# connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='rss_') -# if connections: -# table = prettytable.PrettyTable(["Evennia channel", "RSS url"]) -# for conn in connections: -# table.add_row([conn.channel.key, conn.external_config.split('|')[0]]) -# string = "{wConnections to RSS:{n\n%s" % table -# self.msg(string) -# else: -# self.msg("No connections found.") -# return -# -# if not self.args or not self.rhs: -# string = "Usage: @rss2chan[/switches] = " -# self.msg(string) -# return -# channel = self.lhs -# url = self.rhs -# -# 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 = rss.delete_connection(channel, url) -# if not ok: -# self.msg("RSS connection/reader could not be removed, does it exist?") -# else: -# self.msg("RSS connection destroyed.") -# return -# -# channel = find_channel(self.caller, channel) -# if not channel: -# return -# interval = settings.RSS_UPDATE_INTERVAL -# if not interval: -# interval = 10*60 -# ok = rss.create_connection(channel, url, interval) -# if not ok: -# self.msg("This RSS connection already exists.") -# return -# self.msg("Connection created. Starting RSS reader.") diff --git a/src/players/bots.py b/src/players/bots.py index f926031b3f..3d567761ca 100644 --- a/src/players/bots.py +++ b/src/players/bots.py @@ -59,10 +59,10 @@ class BotStarter(Script): class CmdBotListen(Command): """ - This is a catch-all command that absorbs - all input coming into the bot through its - session and pipes it into its execute_cmd - method. + This is a command that absorbs input + aimed specifically at the bot. The session + must prepend its data with bot_data_in for + this to trigger. """ key = "bot_data_in" def func(self): @@ -199,7 +199,7 @@ class RSSBot(Bot): An RSS relayer. The RSS protocol itself runs a ticker to update its feed at regular intervals. """ - def start(self, ev_channel=None, rss_url=None, rss_update_rate=None): + def start(self, ev_channel=None, rss_url=None, rss_rate=None): """ Start by telling the portal to start a new RSS session @@ -220,19 +220,20 @@ class RSSBot(Bot): self.db.ev_channel = channel if rss_url: self.db.rss_url = rss_url - if rss_update_rate: - self.db.rss_update_rate = rss_update_rate + if rss_rate: + self.db.rss_rate = rss_rate # instruct the server and portal to create a new session with # the stored configuration configdict = {"uid": self.dbid, "url": self.db.rss_url, - "rate": self.db.rss_update_rate} + "rate": self.db.rss_rate} _SESSIONS.start_bot_session("src.server.portal.rss.RSSBotFactory", configdict) def execute_cmd(self, text=None, sessid=None): """ Echo RSS input to connected channel """ + print "execute_cmd rss:", text if not self.ndb.ev_channel and self.db.ev_channel: # cache channel lookup self.ndb.ev_channel = self.db.ev_channel diff --git a/src/server/portal/irc.py b/src/server/portal/irc.py index 1dba478f29..396ba0dba9 100644 --- a/src/server/portal/irc.py +++ b/src/server/portal/irc.py @@ -36,9 +36,10 @@ class IRCBot(irc.IRCClient, Session): self.join(self.channel) self.stopping = False self.factory.bot = self - self.init_session("ircbot", self.network, self.factory.sessionhandler) + address = "%s@%s" % (self.channel, self.network) + self.init_session("ircbot", address, self.factory.sessionhandler) # we link back to our bot and log in - self.uid = self.factory.uid + self.uid = int(self.factory.uid) self.logged_in = True self.factory.sessionhandler.connect(self) logger.log_infomsg("IRC bot '%s' connected to %s at %s:%s." % (self.nickname, self.channel, @@ -86,13 +87,14 @@ class IRCBotFactory(protocol.ReconnectingClientFactory): factor = 1.5 maxDelay = 60 - def __init__(self, uid=None, botname=None, channel=None, network=None, port=None): + def __init__(self, sessionhandler, uid=None, botname=None, channel=None, network=None, port=None): "Storing some important protocol properties" - self.uid = int(uid) + self.sessionhandler = sessionhandler + self.uid = uid self.nickname = str(botname) self.channel = str(channel) self.network = str(network) - self.port = int(port) + self.port = port self.bot = None def buildProtocol(self, addr): @@ -118,8 +120,9 @@ class IRCBotFactory(protocol.ReconnectingClientFactory): def start(self): "Connect session to sessionhandler" - service = internet.TCPClient(self.network, self.port, self) - self.sessionhandler.portal.services.addService(service) + if self.port: + service = internet.TCPClient(self.network, int(self.port), self) + self.sessionhandler.portal.services.addService(service) # diff --git a/src/server/portal/portalsessionhandler.py b/src/server/portal/portalsessionhandler.py index d09732f3c6..ba792ccaf0 100644 --- a/src/server/portal/portalsessionhandler.py +++ b/src/server/portal/portalsessionhandler.py @@ -92,8 +92,7 @@ class PortalSessionHandler(SessionHandler): cls = _MOD_IMPORT(path, clsname) if not cls: raise RuntimeError("ServerConnect: protocol factory '%s' not found." % protocol_path) - protocol = cls(**config) - protocol.sessionhandler = self + protocol = cls(self, **config) protocol.start() def server_disconnect(self, sessid, reason=""): diff --git a/src/server/portal/rss.py b/src/server/portal/rss.py index 212c800185..20fb22869c 100644 --- a/src/server/portal/rss.py +++ b/src/server/portal/rss.py @@ -7,11 +7,11 @@ to the channel whenever the feed updates. """ import re -from twisted.internet import task +from twisted.internet import task, threads from django.conf import settings from src.comms.models import ExternalChannelConnection, ChannelDB +from src.server.session import Session from src.utils import logger, utils -from src.scripts.models import ScriptDB RSS_ENABLED = settings.RSS_ENABLED RSS_UPDATE_INTERVAL = settings.RSS_UPDATE_INTERVAL @@ -21,17 +21,6 @@ RETAG = re.compile(r'<[^>]*?>') # holds rss readers they can be shut down at will. RSS_READERS = {} - -def msg_info(message): - """ - Send info to default info channel - """ - message = '[%s][RSS]: %s' % (INFOCHANNEL[0].key, message) - try: - INFOCHANNEL[0].msg(message) - except AttributeError: - logger.log_infomsg("MUDinfo (rss): %s" % message) - if RSS_ENABLED: try: import feedparser @@ -39,129 +28,208 @@ if RSS_ENABLED: raise ImportError("RSS requires python-feedparser to be installed. Install or set RSS_ENABLED=False.") -class RSSReader(object): +class RSSReader(Session): """ - Reader script used to connect to each individual RSS feed + A simple RSS reader using universal feedparser """ - def __init__(self, key, url, interval): - """ - The reader needs an rss url and It also needs an interval - for how often it is to check for new updates (defaults - to 10 minutes) - """ - self.key = key + def __init__(self, factory, url, rate): self.url = url - self.interval = interval - self.entries = {} # stored feeds - self.task = None - # first we do is to load the feed so we don't resend - # old entries whenever the reader starts. - self.update_feed() - # start runner - self.start() + self.rate = rate + self.factory = factory + self.old_entries = {} - def update_feed(self): - "Read the url for new updated data and determine what's new." + def get_new(self): + """Returns list of new items.""" feed = feedparser.parse(self.url) - new = [] - for entry in (e for e in feed['entries'] if e['id'] not in self.entries): - txt = "[RSS] %s: %s" % (RETAG.sub("", entry['title']), - entry['link'].replace('\n','').encode('utf-8')) - self.entries[entry['id']] = txt - new.append(txt) - return new + new_entries = [] + for entry in feed['entries']: + idval = entry['id'] + entry.get("updated", "") + if idval not in self.old_entries: + self.old_entries[idval] = entry + new_entries.append(entry) + return new_entries - def update(self): - """ - Called every self.interval seconds - tries to get new feed entries, - and if so, uses the appropriate ExternalChannelConnection to send the - data to subscribing channels. - """ - new = self.update_feed() - if not new: - return - conns = ExternalChannelConnection.objects.filter(db_external_key=self.key) - for conn in (conn for conn in conns if conn.channel): - for txt in new: - conn.to_channel("%s:%s" % (conn.channel.key, txt)) + def disconnect(self, reason=None): + "Disconnect from feed" + if self.factory.task and self.factory.task.running: + self.factory.task.stop() + self.sessionhandler.disconnect(self) + + def _callback(self, new_entries, init): + "Called when RSS returns (threaded)" + if not init: + # for initialization we just ignore old entries + for entry in reversed(new_entries): + self.data_in("bot_data_in " + entry) + + def data_in(self, text=None, **kwargs): + "Data RSS -> Server" + self.sessionhandler.data_in(self, text=text, **kwargs) + + def _errback(self, fail): + "Report error" + print "RSS feed error: %s" % fail.value + + def update(self, init=False): + "Request feed" + return threads.deferToThread(self.get_new).addCallback(self._callback, init).addErrback(self._errback) + +class RSSBotFactory(object): + """ + Initializes new bots + """ + + def __init__(self, sessionhandler, uid=None, url=None, rate=None): + "Initialize" + self.sessionhandler = sessionhandler + self.url = url + self.rate = rate + self.uid = uid + self.bot = RSSReader(self, url, rate) + self.task = None def start(self): """ - Starting the update task and store a reference in the - global variable so it can be found and shut down later. + Called by portalsessionhandler """ - global RSS_READERS - self.task = task.LoopingCall(self.update) - self.task.start(self.interval, now=False) - RSS_READERS[self.key] = self + def errback(fail): + print fail.value + # set up session and connect it to sessionhandler + self.bot.init_session("rssbot", self.url, self.sessionhandler) + self.bot.uid = self.uid + self.bot.logged_in = True + self.sessionhandler.connect(self.bot) + # start repeater task + #self.bot.update(init=True) + self.bot.update(init=True) + self.task = task.LoopingCall(self.bot.update) + if self.rate: + self.task.start(self.rate, now=False).addErrback(errback) -def build_connection_key(channel, url): - "This is used to id the connection" - if hasattr(channel, 'key'): - channel = channel.key - return "rss_%s>%s" % (url, channel) - - -def create_connection(channel, url, interval): - """ - This will create a new RSS->channel connection - """ - if not type(channel) == ChannelDB: - new_channel = ChannelDB.objects.filter(db_key=channel) - if not new_channel: - logger.log_errmsg("Cannot attach RSS->Evennia: Evennia Channel '%s' not found." % channel) - return False - channel = new_channel[0] - key = build_connection_key(channel, url) - old_conns = ExternalChannelConnection.objects.filter(db_external_key=key) - if old_conns: - return False - config = "%s|%i" % (url, interval) - # There is no sendback from evennia to the rss, so we need not define - # any sendback code. - conn = ExternalChannelConnection(db_channel=channel, - db_external_key=key, - db_external_config=config) - conn.save() - - connect_to_rss(conn) - return True - - -def delete_connection(channel, url): - """ - Delete rss connection between channel and url - """ - key = build_connection_key(channel, url) - try: - conn = ExternalChannelConnection.objects.get(db_external_key=key) - except Exception: - return False - conn.delete() - reader = RSS_READERS.get(key, None) - if reader and reader.task: - reader.task.stop() - return True - - -def connect_to_rss(connection): - """ - Create the parser instance and connect to RSS feed and channel - """ - global RSS_READERS - key = utils.to_str(connection.external_key) - url, interval = [utils.to_str(conf) for conf in connection.external_config.split('|')] - # Create reader (this starts the running task and stores a reference in RSS_TASKS) - RSSReader(key, url, int(interval)) - - -def connect_all(): - """ - Activate all rss feed parsers - """ - if not RSS_ENABLED: - return - for connection in ExternalChannelConnection.objects.filter(db_external_key__startswith="rss_"): - print "connecting RSS: %s" % connection - connect_to_rss(connection) +#class RSSReader(object): +# """ +# Reader script used to connect to each individual RSS feed +# """ +# def __init__(self, key, url, interval): +# """ +# The reader needs an rss url and It also needs an interval +# for how often it is to check for new updates (defaults +# to 10 minutes) +# """ +# self.key = key +# self.url = url +# self.interval = interval +# self.entries = {} # stored feeds +# self.task = None +# # first we do is to load the feed so we don't resend +# # old entries whenever the reader starts. +# self.update_feed() +# # start runner +# self.start() +# +# def update_feed(self): +# "Read the url for new updated data and determine what's new." +# feed = feedparser.parse(self.url) +# new = [] +# for entry in (e for e in feed['entries'] if e['id'] not in self.entries): +# txt = "[RSS] %s: %s" % (RETAG.sub("", entry['title']), +# entry['link'].replace('\n','').encode('utf-8')) +# self.entries[entry['id']] = txt +# new.append(txt) +# return new +# +# def update(self): +# """ +# Called every self.interval seconds - tries to get new feed entries, +# and if so, uses the appropriate ExternalChannelConnection to send the +# data to subscribing channels. +# """ +# new = self.update_feed() +# if not new: +# return +# conns = ExternalChannelConnection.objects.filter(db_external_key=self.key) +# for conn in (conn for conn in conns if conn.channel): +# for txt in new: +# conn.to_channel("%s:%s" % (conn.channel.key, txt)) +# +# def start(self): +# """ +# Starting the update task and store a reference in the +# global variable so it can be found and shut down later. +# """ +# global RSS_READERS +# self.task = task.LoopingCall(self.update) +# self.task.start(self.interval, now=False) +# RSS_READERS[self.key] = self +# +# +#def build_connection_key(channel, url): +# "This is used to id the connection" +# if hasattr(channel, 'key'): +# channel = channel.key +# return "rss_%s>%s" % (url, channel) +# +# +#def create_connection(channel, url, interval): +# """ +# This will create a new RSS->channel connection +# """ +# if not type(channel) == ChannelDB: +# new_channel = ChannelDB.objects.filter(db_key=channel) +# if not new_channel: +# logger.log_errmsg("Cannot attach RSS->Evennia: Evennia Channel '%s' not found." % channel) +# return False +# channel = new_channel[0] +# key = build_connection_key(channel, url) +# old_conns = ExternalChannelConnection.objects.filter(db_external_key=key) +# if old_conns: +# return False +# config = "%s|%i" % (url, interval) +# # There is no sendback from evennia to the rss, so we need not define +# # any sendback code. +# conn = ExternalChannelConnection(db_channel=channel, +# db_external_key=key, +# db_external_config=config) +# conn.save() +# +# connect_to_rss(conn) +# return True +# +# +#def delete_connection(channel, url): +# """ +# Delete rss connection between channel and url +# """ +# key = build_connection_key(channel, url) +# try: +# conn = ExternalChannelConnection.objects.get(db_external_key=key) +# except Exception: +# return False +# conn.delete() +# reader = RSS_READERS.get(key, None) +# if reader and reader.task: +# reader.task.stop() +# return True +# +# +#def connect_to_rss(connection): +# """ +# Create the parser instance and connect to RSS feed and channel +# """ +# global RSS_READERS +# key = utils.to_str(connection.external_key) +# url, interval = [utils.to_str(conf) for conf in connection.external_config.split('|')] +# # Create reader (this starts the running task and stores a reference in RSS_TASKS) +# RSSReader(key, url, int(interval)) +# +# +#def connect_all(): +# """ +# Activate all rss feed parsers +# """ +# if not RSS_ENABLED: +# return +# for connection in ExternalChannelConnection.objects.filter(db_external_key__startswith="rss_"): +# print "connecting RSS: %s" % connection +# connect_to_rss(connection) diff --git a/src/server/server.py b/src/server/server.py index fb8a9a0222..0078022cf2 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -423,32 +423,21 @@ if WEBSERVER_ENABLED: print " webserver: %s" % serverport +ENABLED = [] if IRC_ENABLED: - # IRC channel connections - - print ' irc enabled' - - from src.comms import irc - irc.connect_all() + ENABLED.append('irc') if IMC2_ENABLED: - # IMC2 channel connections - - print ' imc2 enabled' - - from src.comms import imc2 - imc2.connect_all() + ENABLED.append('imc2') if RSS_ENABLED: - # RSS feed channel connections + ENABLED.append('rss') - print ' rss enabled' - - from src.comms import rss - rss.connect_all() +if ENABLED: + print " " + ", ".join(ENABLED) + " enabled." for plugin_module in SERVER_SERVICES_PLUGIN_MODULES: # external plugin protocols diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index b6a81f12a6..f2cec2aa87 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -236,14 +236,14 @@ class AttributeHandler(object): def get(self, key=None, category=None, default=None, return_obj=False, strattr=False, raise_exception=False, accessing_obj=None, - default_access=True): + default_access=True, not_found_none=False): """ Returns the value of the given Attribute or list of Attributes. strattr will cause the string-only value field instead of the normal pickled field data. Use to get back values from Attributes added with the strattr keyword. If return_obj=True, return the matching Attribute object - instead. Returns None if no matches (or [ ] if key was a list + instead. Returns default if no matches (or [ ] if key was a list with no matches). If raise_exception=True, failure to find a match will raise AttributeError instead. @@ -278,6 +278,8 @@ class AttributeHandler(object): ret = ret if return_obj else [attr.strvalue for attr in ret if attr] else: ret = ret if return_obj else [attr.value for attr in ret if attr] + if not ret: + return ret if len(key) > 1 else default return ret[0] if len(ret)==1 else ret def add(self, key, value, category=None, lockstring="", @@ -399,10 +401,10 @@ class NickHandler(AttributeHandler): raw_string obj_nicks, player_nicks = [], [] for category in make_iter(categories): - obj_nicks.extend(make_iter(self.get(category=category, return_obj=True))) + obj_nicks.extend([n for n in make_iter(self.get(category=category, return_obj=True)) if n]) if include_player and self.obj.has_player: for category in make_iter(categories): - player_nicks.extend(make_iter(self.obj.player.nicks.get(category=category, return_obj=True))) + player_nicks.extend([n for n in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) if n]) for nick in obj_nicks + player_nicks: # make a case-insensitive match here match = re.match(re.escape(nick.db_key), raw_string, re.IGNORECASE)