From e17996d85800bdbdb57bb2af5567d04f95293bfa Mon Sep 17 00:00:00 2001 From: nemunaire Date: Sun, 9 Nov 2014 14:11:54 +0100 Subject: [PATCH] PEP8 clean --- bot.py | 9 +- channel.py | 67 ++++++- consumer.py | 12 +- exception.py | 7 +- hooks/__init__.py | 2 + hooks/manager.py | 1 + hooks/messagehook.py | 7 +- message/__init__.py | 10 +- message/printer/IRC.py | 1 + message/printer/__init__.py | 1 + message/visitor.py | 1 + nemubot.py | 10 +- prompt/__init__.py | 12 +- prompt/builtins.py | 54 +++--- server/IRC.py | 29 ++- server/__init__.py | 7 +- server/socket.py | 14 +- tools/__init__.py | 4 +- tools/countdown.py | 21 ++- tools/date.py | 49 +++-- tools/web.py | 33 +++- xmlparser/__init__.py | 89 ++++----- xmlparser/node.py | 357 ++++++++++++++++++------------------ 23 files changed, 481 insertions(+), 316 deletions(-) diff --git a/bot.py b/bot.py index b041e6c..1c16278 100644 --- a/bot.py +++ b/bot.py @@ -36,11 +36,13 @@ from networkbot import NetworkBot logger = logging.getLogger("nemubot.bot") + class Bot(threading.Thread): """Class containing the bot context and ensuring key goals""" - def __init__(self, ip="127.0.0.1", modules_paths=list(), data_path="./datas/"): + def __init__(self, ip="127.0.0.1", modules_paths=list(), + data_path="./datas/"): """Initialize the bot context Keyword arguments: @@ -71,6 +73,7 @@ class Bot(threading.Thread): # Own hooks self.hooks = HooksManager() + def in_ping(msg): if re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", msg.message, re.I) is not None: return msg.respond("pong") @@ -477,9 +480,9 @@ def reload(): prompt.reload() import server - rl,wl,xl = server._rlist,server._wlist,server._xlist + rl, wl, xl = server._rlist, server._wlist, server._xlist imp.reload(server) - server._rlist,server._wlist,server._xlist = rl,wl,xl + server._rlist, server._wlist, server._xlist = rl, wl, xl server.reload() diff --git a/channel.py b/channel.py index fb570a2..4223370 100644 --- a/channel.py +++ b/channel.py @@ -18,8 +18,19 @@ import logging + class Channel: + + """A chat room""" + def __init__(self, name, password=None): + """Initialize the channel + + Arguments: + name -- the channel name + password -- the optional password use to join it + """ + self.name = name self.password = password self.people = dict() @@ -27,6 +38,13 @@ class Channel: self.logger = logging.getLogger("nemubot.channel." + name) def treat(self, cmd, msg): + """Treat a incoming IRC command + + Arguments: + cmd -- the command + msg -- the whole message + """ + if cmd == "353": self.parse353(msg) elif cmd == "332": @@ -42,18 +60,35 @@ class Channel: elif cmd == "TOPIC": self.topic = self.text - def join(self, nick, level = 0): - """Someone join the channel""" + def join(self, nick, level=0): + """Someone join the channel + + Argument: + nick -- nickname of the user joining the channel + level -- authorization level of the user + """ + self.logger.debug("%s join", nick) self.people[nick] = level def chtopic(self, newtopic): - """Send command to change the topic""" + """Send command to change the topic + + Arguments: + newtopic -- the new topic of the channel + """ + self.srv.send_msg(self.name, newtopic, "TOPIC") self.topic = newtopic def nick(self, oldnick, newnick): - """Someone change his nick""" + """Someone change his nick + + Arguments: + oldnick -- the previous nick of the user + newnick -- the new nick of the user + """ + if oldnick in self.people: self.logger.debug("%s switch nick to %s on", oldnick, newnick) lvl = self.people[oldnick] @@ -61,12 +96,22 @@ class Channel: self.people[newnick] = lvl def part(self, nick): - """Someone leave the channel""" + """Someone leave the channel + + Argument: + nick -- name of the user that leave + """ + if nick in self.people: self.logger.debug("%s has left", nick) del self.people[nick] def mode(self, msg): + """Channel or user mode change + + Argument: + msg -- the whole message + """ if msg.text[0] == "-k": self.password = "" elif msg.text[0] == "+k": @@ -88,9 +133,21 @@ class Channel: self.people[msg.nick] &= ~1 def parse332(self, msg): + """Parse RPL_TOPIC message + + Argument: + msg -- the whole message + """ + self.topic = msg.text def parse353(self, msg): + """Parse RPL_ENDOFWHO message + + Argument: + msg -- the whole message + """ + for p in msg.text: p = p.decode() if p[0] == "@": diff --git a/consumer.py b/consumer.py index 9cf2638..1aeb01c 100644 --- a/consumer.py +++ b/consumer.py @@ -23,6 +23,7 @@ import threading logger = logging.getLogger("nemubot.consumer") + class MessageConsumer: """Store a message before treating""" @@ -77,7 +78,7 @@ class MessageConsumer: new_msg.append(res) msg = None break - elif res is None or res == False: + elif res is None or res is False: msg = None break if msg is not None: @@ -134,7 +135,7 @@ class MessageConsumer: new_msg.append(res) msg = None break - elif res is None or res == False: + elif res is None or res is False: msg = None break else: @@ -160,7 +161,8 @@ class MessageConsumer: if self.responses is not None and len(self.responses) > 0: self.post_treat(context.hooks) except: - logger.exception("Error occurred during the processing of the %s: %s", type(self.msgs[0]).__name__, self.msgs[0]) + logger.exception("Error occurred during the processing of the %s: " + "%s", type(self.msgs[0]).__name__, self.msgs[0]) for res in self.responses: to_server = None @@ -180,6 +182,7 @@ class MessageConsumer: # Sent the message only if treat_post authorize it to_server.send_response(res) + class EventConsumer: """Store a event before treating""" def __init__(self, evt, timeout=20): @@ -198,7 +201,8 @@ class EventConsumer: context.add_event(self.evt, eid=self.evt.id) # Or remove reference of this event - elif hasattr(self.evt, "module_src") and self.evt.module_src is not None: + elif (hasattr(self.evt, "module_src") and + self.evt.module_src is not None): self.evt.module_src.REGISTERED_EVENTS.remove(self.evt.id) diff --git a/exception.py b/exception.py index 92229bf..ccbe362 100644 --- a/exception.py +++ b/exception.py @@ -18,6 +18,7 @@ from message import TextMessage, DirectAsk + class IRCException(Exception): def __init__(self, message, personnal=True): @@ -27,6 +28,8 @@ class IRCException(Exception): def fill_response(self, msg): if self.personnal: - return DirectAsk(msg.frm, self.message, server=msg.server, to=msg.to_response) + return DirectAsk(msg.frm, self.message, + server=msg.server, to=msg.to_response) else: - return TextMessage(self.message, server=msg.server, to=msg.to_response) + return TextMessage(self.message, + server=msg.server, to=msg.to_response) diff --git a/hooks/__init__.py b/hooks/__init__.py index 7c1e841..288ea3a 100644 --- a/hooks/__init__.py +++ b/hooks/__init__.py @@ -20,6 +20,7 @@ import imp from exception import IRCException + def call_game(call, *args, **kargs): """TODO""" l = list() @@ -70,6 +71,7 @@ from hooks.messagehook import MessageHook last_registered = [] + def hook(store, *args, **kargs): """Function used as a decorator for module loading""" def sec(call): diff --git a/hooks/manager.py b/hooks/manager.py index 6db0989..2331d68 100644 --- a/hooks/manager.py +++ b/hooks/manager.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . + class HooksManager: """Class to manage hooks""" diff --git a/hooks/messagehook.py b/hooks/messagehook.py index 1ab09c6..e0600c1 100644 --- a/hooks/messagehook.py +++ b/hooks/messagehook.py @@ -22,6 +22,7 @@ from exception import IRCException import hooks import message + class MessageHook(hooks.AbstractHook): """Class storing hook information, specialized for a generic Message""" @@ -52,9 +53,9 @@ class MessageHook(hooks.AbstractHook): def is_matching(self, strcmp, receivers=list(), server=None): """Test if the current hook correspond to the message""" - if (server is None or self.server is None or self.server == server - ) and ((self.name is None or strcmp == self.name) and ( - self.regexp is None or re.match(self.regexp, strcmp))): + if ((server is None or self.server is None or self.server == server) + and ((self.name is None or strcmp == self.name) and ( + self.regexp is None or re.match(self.regexp, strcmp)))): if receivers and self.channels: for receiver in receivers: diff --git a/message/__init__.py b/message/__init__.py index f3a107a..67ce5f7 100644 --- a/message/__init__.py +++ b/message/__init__.py @@ -19,6 +19,7 @@ from datetime import datetime, timezone import imp + class AbstractMessage: """This class represents an abstract message""" @@ -37,10 +38,12 @@ class AbstractMessage: self.server = server self.date = datetime.now(timezone.utc) if date is None else date self.to = to if to is not None else list() - self._to_response = to_response if to_response is None or isinstance(to_response, list) else [ to_response ] - self.frm = frm # None allowed when it designate this bot + self._to_response = (to_response if (to_response is None or + isinstance(to_response, list)) + else [ to_response ]) + self.frm = frm # None allowed when it designate this bot - self.frm_owner = False # Filled later, in consumer + self.frm_owner = False # Filled later, in consumer @property @@ -129,7 +132,6 @@ class DirectAsk(TextMessage): self.designated = designated - def respond(self, message): return DirectAsk(self.frm, message, diff --git a/message/printer/IRC.py b/message/printer/IRC.py index 83155ac..1b4670f 100644 --- a/message/printer/IRC.py +++ b/message/printer/IRC.py @@ -19,6 +19,7 @@ from message import TextMessage from message.visitor import AbstractVisitor + class IRC(AbstractVisitor): def __init__(self): diff --git a/message/printer/__init__.py b/message/printer/__init__.py index e9bf5a5..215b464 100644 --- a/message/printer/__init__.py +++ b/message/printer/__init__.py @@ -18,6 +18,7 @@ import imp + def reload(): import message.printer.IRC imp.reload(message.printer.IRC) diff --git a/message/visitor.py b/message/visitor.py index 7328254..1041a45 100644 --- a/message/visitor.py +++ b/message/visitor.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . + class AbstractVisitor: def visit(self, obj): diff --git a/nemubot.py b/nemubot.py index f3a29cf..1b2121f 100755 --- a/nemubot.py +++ b/nemubot.py @@ -31,7 +31,8 @@ if __name__ == "__main__": # Setup loggin interface logger = logging.getLogger("nemubot") - formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s') + formatter = logging.Formatter( + '%(asctime)s %(name)s %(levelname)s %(message)s') ch = logging.StreamHandler() ch.setFormatter(formatter) @@ -52,7 +53,7 @@ if __name__ == "__main__": # Register the hook for futur import sys.meta_path.append(importer.ModuleFinder(context, prmpt)) - #Add modules dir path + # Add modules dir path if os.path.isdir("./modules/"): context.add_modules_path( os.path.realpath(os.path.abspath("./modules/"))) @@ -82,8 +83,9 @@ if __name__ == "__main__": bot.__version__) context.start() except: - logger.exception("\033[1;31mUnable to reload the prompt due to errors.\033[0" - "m Fix them before trying to reload the prompt.") + logger.exception("\033[1;31mUnable to reload the prompt due to " + "errors.\033[0m Fix them before trying to reload " + "the prompt.") context.quit() print("\nWaiting for other threads shuts down...") diff --git a/prompt/__init__.py b/prompt/__init__.py index 0233e9c..d0ad4b8 100644 --- a/prompt/__init__.py +++ b/prompt/__init__.py @@ -25,7 +25,9 @@ import traceback from . import builtins + class Prompt: + def __init__(self, hc=dict(), hl=dict()): self.selectedServer = None @@ -56,8 +58,8 @@ class Prompt: return ret except: exc_type, exc_value, exc_traceback = sys.exc_info() - sys.stderr.write (traceback.format_exception_only( - exc_type, exc_value)[0]) + sys.stderr.write(traceback.format_exception_only(exc_type, + exc_value)[0]) return ret def exec_cmd(self, toks, context): @@ -65,7 +67,7 @@ class Prompt: if toks[0] in builtins.CAPS: return builtins.CAPS[toks[0]](toks, context, self) elif toks[0] in self.HOOKS_CAPS: - (f,d) = self.HOOKS_CAPS[toks[0]] + f, d = self.HOOKS_CAPS[toks[0]] return f(d, toks, context, self) else: print("Unknown command: `%s'" % toks[0]) @@ -90,7 +92,8 @@ class Prompt: ret = self.exec_cmd(toks, context) except: exc_type, exc_value, exc_traceback = sys.exc_info() - traceback.print_exception(exc_type, exc_value, exc_traceback) + traceback.print_exception(exc_type, exc_value, + exc_traceback) except KeyboardInterrupt: print("") except EOFError: @@ -102,6 +105,7 @@ class Prompt: def hotswap(prompt): return Prompt(prompt.HOOKS_CAPS, prompt.HOOKS_LIST) + def reload(): import prompt.builtins imp.reload(prompt.builtins) diff --git a/prompt/builtins.py b/prompt/builtins.py index 2af2e18..f0e5c68 100644 --- a/prompt/builtins.py +++ b/prompt/builtins.py @@ -25,6 +25,13 @@ logger = logging.getLogger("nemubot.prompt.builtins") from server.IRC import IRC as IRCServer import xmlparser + +def get_boolean(d, k): + return (k in d and + mod["autoload"].lower() != "false" and + mod["autoload"].lower() != "off") + + def end(toks, context, prompt): """Quit the prompt for reload or exit""" if toks[0] == "refresh": @@ -52,7 +59,7 @@ def liste(toks, context, prompt): if len(context.modules) == 0: print (" > No module loaded") elif l in prompt.HOOKS_LIST: - (f,d) = prompt.HOOKS_LIST[l] + f, d = prompt.HOOKS_LIST[l] f(d, context, prompt) else: print (" Unknown list `%s'" % l) @@ -65,8 +72,7 @@ def load_file(filename, context): config = xmlparser.parse_file(filename) # This is a true nemubot configuration file, load it! - if (config.getName() == "botconfig" - or config.getName() == "nemubotconfig"): + if config.getName() == "nemubotconfig": # Preset each server in this file for server in config.getNodes("server"): opts = { @@ -78,7 +84,8 @@ def load_file(filename, context): } # Optional keyword arguments - for optional_opt in [ "port", "username", "realname", "password", "encoding", "caps" ]: + for optional_opt in [ "port", "username", "realname", + "password", "encoding", "caps" ]: if server.hasAttribute(optional_opt): opts[optional_opt] = server[optional_opt] elif optional_opt in config: @@ -94,11 +101,14 @@ def load_file(filename, context): if server.hasNode("channel"): opts["channels"] = list() for chn in server.getNodes("channel"): - opts["channels"].append((chn["name"], chn["password"]) if chn["password"] is not None else chn["name"]) + opts["channels"].append((chn["name"], chn["password"]) + if chn["password"] is not None + else chn["name"]) # Server/client capabilities if "caps" in server or "caps" in config: - capsl = (server["caps"] if server.hasAttribute("caps") else config["caps"]).lower() + capsl = (server["caps"] if server.hasAttribute("caps") + else config["caps"]).lower() if capsl == "no" or capsl == "off" or capsl == "false": opts["caps"] = None else: @@ -110,14 +120,14 @@ def load_file(filename, context): if "protocol" not in server or server["protocol"] == "irc": srvcls = IRCServer else: - raise Exception("Unhandled protocol '%s'" % server["protocol"]) + raise Exception("Unhandled protocol '%s'" % + server["protocol"]) # Initialize the server srv = srvcls(**opts) # Add the server in the context - if context.add_server(srv, - "autoconnect" in server and server["autoconnect"].lower() != "false"): + if context.add_server(srv, get_boolean(server, "autoconnect")): print("Server '%s' successfully added." % srv.id) else: print("Can't add server '%s'." % srv.id) @@ -125,7 +135,7 @@ def load_file(filename, context): # Load module and their configuration for mod in config.getNodes("module"): context.modules_configuration[mod["name"]] = mod - if not mod.hasAttribute("autoload") or (mod["autoload"].lower() != "false" and mod["autoload"].lower() != "off"): + if get_boolean(mod, "autoload"): __import__(mod["name"]) # Load files asked by the configuration file @@ -155,8 +165,8 @@ def load(toks, context, prompt): def select(toks, context, prompt): """Select the current server""" - if (len(toks) == 2 and toks[1] != "None" - and toks[1] != "nemubot" and toks[1] != "none"): + if (len(toks) == 2 and toks[1] != "None" and + toks[1] != "nemubot" and toks[1] != "none"): if toks[1] in context.servers: prompt.selectedServer = context.servers[toks[1]] else: @@ -197,15 +207,15 @@ def debug(toks, context, prompt): print ("Not enough arguments. `debug' takes a module name.") -#Register build-ins +# Register build-ins CAPS = { - 'quit': end, #Disconnect all server and quit - 'exit': end, #Alias for quit - 'reset': end, #Reload the prompt - 'refresh': end, #Reload the prompt but save modules - 'load': load, #Load a servers or module configuration file - 'unload': unload, #Unload a module and remove it from the list - 'select': select, #Select a server - 'list': liste, #Show lists - 'debug': debug, #Pass a module in debug mode + 'quit': end, # Disconnect all server and quit + 'exit': end, # Alias for quit + 'reset': end, # Reload the prompt + 'refresh': end, # Reload the prompt but save modules + 'load': load, # Load a servers or module configuration file + 'unload': unload, # Unload a module and remove it from the list + 'select': select, # Select a server + 'list': liste, # Show lists + 'debug': debug, # Pass a module in debug mode } diff --git a/server/IRC.py b/server/IRC.py index 9700a23..bbe6334 100644 --- a/server/IRC.py +++ b/server/IRC.py @@ -28,8 +28,11 @@ from message.printer.IRC import IRC as IRCPrinter from server.socket import SocketServer import tools + class IRC(SocketServer): + """Concrete implementation of a connexion to an IRC server""" + def __init__(self, owner, nick="nemubot", host="localhost", port=6667, ssl=False, username=None, password=None, realname="Nemubot", encoding="utf-8", caps=None, channels=list(), @@ -155,7 +158,8 @@ class IRC(SocketServer): # Respond to JOIN def _on_join(msg): - if len(msg.params) == 0: return + if len(msg.params) == 0: + return for chname in msg.decode(msg.params[0]).split(","): # Register the channel @@ -164,7 +168,8 @@ class IRC(SocketServer): self.hookscmd["JOIN"] = _on_join # Respond to PART def _on_part(msg): - if len(msg.params) != 1 and len(msg.params) != 2: return + if len(msg.params) != 1 and len(msg.params) != 2: + return for chname in msg.params[0].split(b","): if chname in self.channels: @@ -175,7 +180,8 @@ class IRC(SocketServer): self.hookscmd["PART"] = _on_part # Respond to 331/RPL_NOTOPIC,332/RPL_TOPIC,TOPIC def _on_topic(msg): - if len(msg.params) != 1 and len(msg.params) != 2: return + if len(msg.params) != 1 and len(msg.params) != 2: + return if msg.params[0] in self.channels: if len(msg.params) == 1 or len(msg.params[1]) == 0: self.channels[msg.params[0]].topic = None @@ -186,8 +192,10 @@ class IRC(SocketServer): self.hookscmd["TOPIC"] = _on_topic # Respond to 353/RPL_NAMREPLY def _on_353(msg): - if len(msg.params) == 3: msg.params.pop(0) # 353: like RFC 1459 - if len(msg.params) != 2: return + if len(msg.params) == 3: + msg.params.pop(0) # 353: like RFC 1459 + if len(msg.params) != 2: + return if msg.params[0] in self.channels: for nk in msg.decode(msg.params[1]).split(" "): res = re.match("^(?P[^a-zA-Z[\]\\`_^{|}])(?P[a-zA-Z[\]\\`_^{|}][a-zA-Z0-9[\]\\`_^{|}-]*)$") @@ -196,7 +204,8 @@ class IRC(SocketServer): # Respond to INVITE def _on_invite(msg): - if len(msg.params) != 2: return + if len(msg.params) != 2: + return self.write("JOIN " + msg.decode(msg.params[1])) self.hookscmd["INVITE"] = _on_invite @@ -209,7 +218,8 @@ class IRC(SocketServer): # Handle CTCP requests def _on_ctcp(msg): - if len(msg.params) != 2 or not msg.is_ctcp: return + if len(msg.params) != 2 or not msg.is_ctcp: + return cmds = msg.decode(msg.params[1][1:len(msg.params[1])-1]).split(' ') if cmds[0] in self.ctcp_capabilities: res = self.ctcp_capabilities[cmds[0]](msg, cmds) @@ -353,9 +363,10 @@ class IRCMessage: client -- export as a client-side string if true """ - res = ";".join(["@%s=%s" % (k,v if not isinstance(v, datetime) else v.strftime("%Y-%m-%dT%H:%M:%S.%fZ")) for k, v in self.tags.items()]) + res = ";".join(["@%s=%s" % (k, v if not isinstance(v, datetime) else v.strftime("%Y-%m-%dT%H:%M:%S.%fZ")) for k, v in self.tags.items()]) - if not client: res += " :%s!%s@%s" % (self.nick, self.user, self.host) + if not client: + res += " :%s!%s@%s" % (self.nick, self.user, self.host) res += " " + self.cmd diff --git a/server/__init__.py b/server/__init__.py index 11b9854..3ae04f2 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -27,6 +27,7 @@ _rlist = [] _wlist = [] _xlist = [] + # Extends from IOBase in order to be compatible with select function class AbstractServer(io.IOBase): @@ -62,7 +63,8 @@ class AbstractServer(io.IOBase): def open(self): - """Generic open function that register the server un _rlist in case of successful _open""" + """Generic open function that register the server un _rlist in case + of successful _open""" self.logger.info("Opening connection to %s", self.id) if not hasattr(self, "_open") or self._open(): _rlist.append(self) @@ -70,7 +72,8 @@ class AbstractServer(io.IOBase): def close(self): - """Generic close function that register the server un _{r,w,x}list in case of successful _close""" + """Generic close function that register the server un _{r,w,x}list in + case of successful _close""" self.logger.info("Closing connection to %s", self.id) if not hasattr(self, "_close") or self._close(): if self in _rlist: diff --git a/server/socket.py b/server/socket.py index 33f7944..bacfdac 100644 --- a/server/socket.py +++ b/server/socket.py @@ -21,8 +21,11 @@ import socket from server import AbstractServer + class SocketServer(AbstractServer): + """Concrete implementation of a socket connexion (can be wrapped with TLS)""" + def __init__(self, host, port, ssl=False): AbstractServer.__init__(self) self.host = host @@ -55,12 +58,13 @@ class SocketServer(AbstractServer): self.socket = ctx.wrap_socket(self.socket) try: - self.socket.connect((self.host, self.port)) #Connect to server + self.socket.connect((self.host, self.port)) # Connect to server self.logger.info("Connected to %s:%d", self.host, self.port) except socket.error as e: self.socket = None self.logger.critical("Unable to connect to %s:%d: %s", - self.host, self.port, os.strerror(e.errno)) + self.host, self.port, + os.strerror(e.errno)) return False return True @@ -81,7 +85,8 @@ class SocketServer(AbstractServer): # Write def _write(self, cnt): - if not self.connected: return + if not self.connected: + return self.socket.send(cnt) @@ -96,7 +101,8 @@ class SocketServer(AbstractServer): # Read def read(self): - if not self.connected: return + if not self.connected: + return raw = self.socket.recv(1024) temp = (self.readbuffer + raw).split(b'\r\n') diff --git a/tools/__init__.py b/tools/__init__.py index fe39e49..78b9d0c 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -18,14 +18,16 @@ import imp + def intToIP(n): ip = "" - for i in range(0,4): + for i in range(0, 4): mod = n % 256 ip = "%d.%s" % (mod, ip) n = (n - mod) / 256 return ip[:len(ip) - 1] + def ipToInt(ip): sum = 0 for b in ip.split("."): diff --git a/tools/countdown.py b/tools/countdown.py index b0c7c71..4f66b21 100644 --- a/tools/countdown.py +++ b/tools/countdown.py @@ -19,6 +19,7 @@ from datetime import datetime, timezone import time + def countdown(delta, resolution=5): sec = delta.seconds hours, remainder = divmod(sec, 3600) @@ -31,7 +32,7 @@ def countdown(delta, resolution=5): if resolution > 0 and (force or an > 0): force = True - sentence += " %i an"%(an) + sentence += " %i an" % an if an > 1: sentence += "s" @@ -42,7 +43,7 @@ def countdown(delta, resolution=5): if resolution > 1 and (force or days > 0): force = True - sentence += " %i jour"%(days) + sentence += " %i jour" % days if days > 1: sentence += "s" @@ -53,7 +54,7 @@ def countdown(delta, resolution=5): if resolution > 2 and (force or hours > 0): force = True - sentence += " %i heure"%(hours) + sentence += " %i heure" % hours if hours > 1: sentence += "s" if resolution > 4: @@ -63,7 +64,7 @@ def countdown(delta, resolution=5): if resolution > 3 and (force or minutes > 0): force = True - sentence += " %i minute"%(minutes) + sentence += " %i minute" % minutes if minutes > 1: sentence += "s" if resolution > 4: @@ -71,20 +72,21 @@ def countdown(delta, resolution=5): if resolution > 4 and (force or seconds > 0): force = True - sentence += " %i seconde"%(seconds) + sentence += " %i seconde" % seconds if seconds > 1: sentence += "s" return sentence[1:] def countdown_format(date, msg_before, msg_after, tz=None): - """Replace in a text %s by a sentence incidated the remaining time before/after an event""" - if tz != None: + """Replace in a text %s by a sentence incidated the remaining time + before/after an event""" + if tz is not None: oldtz = os.environ['TZ'] os.environ['TZ'] = tz time.tzset() - #Calculate time before the date + # Calculate time before the date try: if datetime.now(timezone.utc) > date: sentence_c = msg_after @@ -100,8 +102,7 @@ def countdown_format(date, msg_before, msg_after, tz=None): sentence_c = msg_before delta = date - datetime.now() - - if tz != None: + if tz is not None: os.environ['TZ'] = oldtz return sentence_c % countdown(delta) diff --git a/tools/date.py b/tools/date.py index 15b694e..f15ed95 100644 --- a/tools/date.py +++ b/tools/date.py @@ -29,24 +29,37 @@ xtrdt = re.compile(r'''^.*? (?P[0-9]{1,4}) .+? (?:[^0-9]*[m\":][^0-9]*(?P[0-9]{1,2}))?)?)?.*? $''', re.X) + def extractDate(msg): """Parse a message to extract a time and date""" result = xtrdt.match(msg.lower()) if result is not None: day = result.group("day") month = result.group("month") - if month == "janvier" or month == "january" or month == "januar": month = 1 - elif month == "fevrier" or month == "février" or month == "february": month = 2 - elif month == "mars" or month == "march": month = 3 - elif month == "avril" or month == "april": month = 4 - elif month == "mai" or month == "may" or month == "maï": month = 5 - elif month == "juin" or month == "juni" or month == "junni": month = 6 - elif month == "juillet" or month == "jully" or month == "july": month = 7 - elif month == "aout" or month == "août" or month == "august": month = 8 - elif month == "september" or month == "septembre": month = 9 - elif month == "october" or month == "october" or month == "oktober": month = 10 - elif month == "november" or month == "novembre": month = 11 - elif month == "december" or month == "decembre" or month == "décembre": month = 12 + if month == "janvier" or month == "january" or month == "januar": + month = 1 + elif month == "fevrier" or month == "février" or month == "february": + month = 2 + elif month == "mars" or month == "march": + month = 3 + elif month == "avril" or month == "april": + month = 4 + elif month == "mai" or month == "may" or month == "maï": + month = 5 + elif month == "juin" or month == "juni" or month == "junni": + month = 6 + elif month == "juillet" or month == "jully" or month == "july": + month = 7 + elif month == "aout" or month == "août" or month == "august": + month = 8 + elif month == "september" or month == "septembre": + month = 9 + elif month == "october" or month == "october" or month == "oktober": + month = 10 + elif month == "november" or month == "novembre": + month = 11 + elif month == "december" or month == "decembre" or month == "décembre": + month = 12 year = result.group("year") @@ -57,9 +70,12 @@ def extractDate(msg): minute = result.group("minute") second = result.group("second") - if year is None: year = date.today().year - if hour is None: hour = 0 - if minute is None: minute = 0 + if year is None: + year = date.today().year + if hour is None: + hour = 0 + if minute is None: + minute = 0 if second is None: second = 1 else: @@ -68,6 +84,7 @@ def extractDate(msg): minute = int(minute) + 1 second = 0 - return datetime(int(year), int(month), int(day), int(hour), int(minute), int(second)) + return datetime(int(year), int(month), int(day), + int(hour), int(minute), int(second)) else: return None diff --git a/tools/web.py b/tools/web.py index 020a616..a9e59fa 100644 --- a/tools/web.py +++ b/tools/web.py @@ -28,31 +28,39 @@ from urllib.request import urlopen from exception import IRCException import xmlparser + def isURL(url): """Return True if the URL can be parsed""" o = urlparse(url) return o.scheme == "" and o.netloc == "" and o.path == "" + def getScheme(url): """Return the protocol of a given URL""" o = urlparse(url) return o.scheme + def getHost(url): """Return the domain of a given URL""" return urlparse(url).netloc + def getPort(url): """Return the port of a given URL""" return urlparse(url).port + def getPath(url): """Return the page request of a given URL""" return urlparse(url).path + def getUser(url): """Return the page request of a given URL""" return urlparse(url).username + + def getPassword(url): """Return the page request of a given URL""" return urlparse(url).password @@ -67,16 +75,19 @@ def getURLContent(url, timeout=15): o = urlparse("http://" + url) if o.scheme == "http": - conn = http.client.HTTPConnection(o.netloc, port=o.port, timeout=timeout) + conn = http.client.HTTPConnection(o.netloc, port=o.port, + timeout=timeout) elif o.scheme == "https": - conn = http.client.HTTPSConnection(o.netloc, port=o.port, timeout=timeout) + conn = http.client.HTTPSConnection(o.netloc, port=o.port, + timeout=timeout) elif o.scheme is None or o.scheme == "": conn = http.client.HTTPConnection(o.netloc, port=80, timeout=timeout) else: return None try: if o.query != '': - conn.request("GET", o.path + "?" + o.query, None, {"User-agent": "Nemubot v3"}) + conn.request("GET", o.path + "?" + o.query, + None, {"User-agent": "Nemubot v3"}) else: conn.request("GET", o.path, None, {"User-agent": "Nemubot v3"}) except socket.timeout: @@ -115,10 +126,14 @@ def getURLContent(url, timeout=15): if res.status == http.client.OK or res.status == http.client.SEE_OTHER: return data.decode(charset) - elif (res.status == http.client.FOUND or res.status == http.client.MOVED_PERMANENTLY) and res.getheader("Location") != url: + elif ((res.status == http.client.FOUND or + res.status == http.client.MOVED_PERMANENTLY) and + res.getheader("Location") != url): return getURLContent(res.getheader("Location"), timeout) else: - raise IRCException("A HTTP error occurs: %d - %s" % (res.status, http.client.responses[res.status])) + raise IRCException("A HTTP error occurs: %d - %s" % + (res.status, http.client.responses[res.status])) + def getXML(url, timeout=15): """Get content page and return XML parsed content""" @@ -128,6 +143,7 @@ def getXML(url, timeout=15): else: return xmlparser.parse_string(cnt.encode()) + def getJSON(url, timeout=15): """Get content page and return JSON content""" cnt = getURLContent(url, timeout) @@ -136,6 +152,7 @@ def getJSON(url, timeout=15): else: return json.loads(cnt.decode()) + # Other utils def htmlentitydecode(s): @@ -143,7 +160,11 @@ def htmlentitydecode(s): return re.sub('&(%s);' % '|'.join(name2codepoint), lambda m: chr(name2codepoint[m.group(1)]), s) + def striphtml(data): """Remove HTML tags from text""" p = re.compile(r'<.*?>') - return htmlentitydecode(p.sub('', data).replace("(", "/(").replace(")", ")/").replace(""", "\"")) + return htmlentitydecode(p.sub('', data) + .replace("(", "/(") + .replace(")", ")/") + .replace(""", "\"")) diff --git a/xmlparser/__init__.py b/xmlparser/__init__.py index 8dd33d6..722c6bf 100644 --- a/xmlparser/__init__.py +++ b/xmlparser/__init__.py @@ -25,56 +25,59 @@ from . import node as module_state logger = logging.getLogger("nemubot.xmlparser") + class ModuleStatesFile(xml.sax.ContentHandler): - def startDocument(self): - self.root = None - self.stack = list() + def startDocument(self): + self.root = None + self.stack = list() - def startElement(self, name, attrs): - cur = module_state.ModuleState(name) + def startElement(self, name, attrs): + cur = module_state.ModuleState(name) - for name in attrs.keys(): - cur.setAttribute(name, attrs.getValue(name)) + for name in attrs.keys(): + cur.setAttribute(name, attrs.getValue(name)) - self.stack.append(cur) + self.stack.append(cur) - def characters(self, content): - self.stack[len(self.stack)-1].content += content + def characters(self, content): + self.stack[len(self.stack)-1].content += content + + def endElement(self, name): + child = self.stack.pop() + size = len(self.stack) + if size > 0: + self.stack[size - 1].content = self.stack[size - 1].content.strip() + self.stack[size - 1].addChild(child) + else: + self.root = child - def endElement(self, name): - child = self.stack.pop() - size = len(self.stack) - if size > 0: - self.stack[size - 1].content = self.stack[size - 1].content.strip() - self.stack[size - 1].addChild(child) - else: - self.root = child def parse_file(filename): - parser = xml.sax.make_parser() - mod = ModuleStatesFile() - parser.setContentHandler(mod) - try: - parser.parse(open(filename, "r")) - return mod.root - except IOError: - logger.exception("error occurs during XML parsing of %s", filename) - return module_state.ModuleState("nemubotstate") - except: - logger.exception("error occurs during XML parsing of %s", filename) - if mod.root is None: - return module_state.ModuleState("nemubotstate") - else: - return mod.root + parser = xml.sax.make_parser() + mod = ModuleStatesFile() + parser.setContentHandler(mod) + try: + parser.parse(open(filename, "r")) + return mod.root + except IOError: + logger.exception("error occurs during XML parsing of %s", filename) + return module_state.ModuleState("nemubotstate") + except: + logger.exception("error occurs during XML parsing of %s", filename) + if mod.root is None: + return module_state.ModuleState("nemubotstate") + else: + return mod.root + def parse_string(string): - mod = ModuleStatesFile() - try: - xml.sax.parseString(string, mod) - return mod.root - except: - logger.exception("error occurs during XML parsing") - if mod.root is None: - return module_state.ModuleState("nemubotstate") - else: - return mod.root + mod = ModuleStatesFile() + try: + xml.sax.parseString(string, mod) + return mod.root + except: + logger.exception("error occurs during XML parsing") + if mod.root is None: + return module_state.ModuleState("nemubotstate") + else: + return mod.root diff --git a/xmlparser/node.py b/xmlparser/node.py index 34e7d0e..6f7f91c 100644 --- a/xmlparser/node.py +++ b/xmlparser/node.py @@ -9,197 +9,206 @@ import traceback logger = logging.getLogger("nemubot.xmlparser.node") + class ModuleState: - """Tiny tree representation of an XML file""" + """Tiny tree representation of an XML file""" - def __init__(self, name): - self.name = name - self.content = "" - self.attributes = dict() - self.childs = list() - self.index = dict() - self.index_fieldname = None - self.index_tagname = None + def __init__(self, name): + self.name = name + self.content = "" + self.attributes = dict() + self.childs = list() + self.index = dict() + self.index_fieldname = None + self.index_tagname = None - def getName(self): - """Get the name of the current node""" - return self.name + def getName(self): + """Get the name of the current node""" + return self.name - def display(self, level = 0): - ret = "" - out = list() - for k in self.attributes: - out.append("%s : %s" % (k, self.attributes[k])) - ret += "%s%s { %s } = '%s'\n" % (' ' * level, self.name, ' ; '.join(out), self.content) - for c in self.childs: - ret += c.display(level + 2) - return ret + def display(self, level = 0): + ret = "" + out = list() + for k in self.attributes: + out.append("%s : %s" % (k, self.attributes[k])) + ret += "%s%s { %s } = '%s'\n" % (' ' * level, self.name, + ' ; '.join(out), self.content) + for c in self.childs: + ret += c.display(level + 2) + return ret - def __str__(self): - return self.display() + def __str__(self): + return self.display() - def __getitem__(self, i): - """Return the attribute asked""" - return self.getAttribute(i) + def __getitem__(self, i): + """Return the attribute asked""" + return self.getAttribute(i) - def __setitem__(self, i, c): - """Set the attribute""" - return self.setAttribute(i, c) + def __setitem__(self, i, c): + """Set the attribute""" + return self.setAttribute(i, c) - def getAttribute(self, name): - """Get the asked argument or return None if doesn't exist""" - if name in self.attributes: - return self.attributes[name] - else: - return None + def getAttribute(self, name): + """Get the asked argument or return None if doesn't exist""" + if name in self.attributes: + return self.attributes[name] + else: + return None - def getDate(self, name=None): - """Get the asked argument and return it as a date""" - if name is None: - source = self.content - elif name in self.attributes.keys(): - source = self.attributes[name] - else: + def getDate(self, name=None): + """Get the asked argument and return it as a date""" + if name is None: + source = self.content + elif name in self.attributes.keys(): + source = self.attributes[name] + else: + return None + + if isinstance(source, datetime): + return source + else: + try: + return datetime.fromtimestamp(float(source), timezone.utc) + except ValueError: + while True: + try: + return datetime.fromtimestamp(time.mktime( + time.strptime(source[:19], "%Y-%m-%d %H:%M:%S")), + timezone.utc) + except ImportError: + pass + + def getInt(self, name=None): + """Get the asked argument and return it as an integer""" + if name is None: + source = self.content + elif name in self.attributes.keys(): + source = self.attributes[name] + else: + return None + + return int(float(source)) + + def getBool(self, name=None): + """Get the asked argument and return it as an integer""" + if name is None: + source = self.content + elif name in self.attributes.keys(): + source = self.attributes[name] + else: + return False + + return (isinstance(source, bool) and source) or source == "True" + + def tmpIndex(self, fieldname="name", tagname=None): + index = dict() + for child in self.childs: + if ((tagname is None or tagname == child.name) and + child.hasAttribute(fieldname)): + index[child[fieldname]] = child + return index + + def setIndex(self, fieldname="name", tagname=None): + """Defines an hash table to accelerate childs search. + You have just to define a common attribute""" + self.index = self.tmpIndex(fieldname, tagname) + self.index_fieldname = fieldname + self.index_tagname = tagname + + def __contains__(self, i): + """Return true if i is found in the index""" + if self.index: + return i in self.index + else: + return self.hasAttribute(i) + + def hasAttribute(self, name): + """DOM like method""" + return (name in self.attributes) + + def setAttribute(self, name, value): + """DOM like method""" + if (isinstance(value, datetime) or isinstance(value, str) or + isinstance(value, int) or isinstance(value, float)): + self.attributes[name] = value + else: + raise TypeError("attributes must be primary type " + "or datetime (here %s)" % type(value)) + + def getContent(self): + return self.content + + def getChilds(self): + """Return a full list of direct child of this node""" + return self.childs + + def getNode(self, tagname): + """Get a unique node (or the last one) with the given tagname""" + ret = None + for child in self.childs: + if tagname is None or tagname == child.name: + ret = child + return ret + + def getFirstNode(self, tagname): + """Get a unique node (or the last one) with the given tagname""" + for child in self.childs: + if tagname is None or tagname == child.name: + return child return None - if isinstance(source, datetime): - return source - else: - try: - return datetime.fromtimestamp(float(source), timezone.utc) - except ValueError: - while True: - try: - return datetime.fromtimestamp(time.mktime( - time.strptime(source[:19], "%Y-%m-%d %H:%M:%S")), timezone.utc) - except ImportError: - pass + def getNodes(self, tagname): + """Get all direct childs that have the given tagname""" + for child in self.childs: + if tagname is None or tagname == child.name: + yield child - def getInt(self, name=None): - """Get the asked argument and return it as an integer""" - if name is None: - source = self.content - elif name in self.attributes.keys(): - source = self.attributes[name] - else: - return None - - return int(float(source)) - - def getBool(self, name=None): - """Get the asked argument and return it as an integer""" - if name is None: - source = self.content - elif name in self.attributes.keys(): - source = self.attributes[name] - else: + def hasNode(self, tagname): + """Return True if at least one node with the given tagname exists""" + for child in self.childs: + if tagname is None or tagname == child.name: + return True return False - return (isinstance(source, bool) and source) or source == "True" + def addChild(self, child): + """Add a child to this node""" + self.childs.append(child) + if self.index_fieldname is not None: + self.setIndex(self.index_fieldname, self.index_tagname) - def tmpIndex(self, fieldname="name", tagname=None): - index = dict() - for child in self.childs: - if (tagname is None or tagname == child.name) and child.hasAttribute(fieldname): - index[child[fieldname]] = child - return index + def delChild(self, child): + """Remove the given child from this node""" + self.childs.remove(child) + if self.index_fieldname is not None: + self.setIndex(self.index_fieldname, self.index_tagname) - def setIndex(self, fieldname="name", tagname=None): - """Defines an hash table to accelerate childs search. You have just to define a common attribute""" - self.index = self.tmpIndex(fieldname, tagname) - self.index_fieldname = fieldname - self.index_tagname = tagname + def save_node(self, gen): + """Serialize this node as a XML node""" + attribs = {} + for att in self.attributes.keys(): + if att[0] != "_": # Don't save attribute starting by _ + if isinstance(self.attributes[att], datetime): + attribs[att] = str(time.mktime( + self.attributes[att].timetuple())) + else: + attribs[att] = str(self.attributes[att]) + attrs = xml.sax.xmlreader.AttributesImpl(attribs) - def __contains__(self, i): - """Return true if i is found in the index""" - if self.index: - return i in self.index - else: - return self.hasAttribute(i) + try: + gen.startElement(self.name, attrs) - def hasAttribute(self, name): - """DOM like method""" - return (name in self.attributes) + for child in self.childs: + child.save_node(gen) - def setAttribute(self, name, value): - """DOM like method""" - if isinstance(value, datetime) or isinstance(value, str) or isinstance(value, int) or isinstance(value, float): - self.attributes[name] = value - else: - raise TypeError("attributes must be primary type or datetime (here %s)" % type(value)) + gen.endElement(self.name) + except: + logger.exception("Error occured when saving the following " + "XML node: %s with %s", self.name, attrs) - def getContent(self): - return self.content - - def getChilds(self): - """Return a full list of direct child of this node""" - return self.childs - - def getNode(self, tagname): - """Get a unique node (or the last one) with the given tagname""" - ret = None - for child in self.childs: - if tagname is None or tagname == child.name: - ret = child - return ret - - def getFirstNode(self, tagname): - """Get a unique node (or the last one) with the given tagname""" - for child in self.childs: - if tagname is None or tagname == child.name: - return child - return None - - def getNodes(self, tagname): - """Get all direct childs that have the given tagname""" - for child in self.childs: - if tagname is None or tagname == child.name: - yield child - - def hasNode(self, tagname): - """Return True if at least one node with the given tagname exists""" - for child in self.childs: - if tagname is None or tagname == child.name: - return True - return False - - def addChild(self, child): - """Add a child to this node""" - self.childs.append(child) - if self.index_fieldname is not None: - self.setIndex(self.index_fieldname, self.index_tagname) - - def delChild(self, child): - """Remove the given child from this node""" - self.childs.remove(child) - if self.index_fieldname is not None: - self.setIndex(self.index_fieldname, self.index_tagname) - - def save_node(self, gen): - """Serialize this node as a XML node""" - attribs = {} - for att in self.attributes.keys(): - if att[0] != "_": # Don't save attribute starting by _ - if isinstance(self.attributes[att], datetime): - attribs[att] = str(time.mktime(self.attributes[att].timetuple())) - else: - attribs[att] = str(self.attributes[att]) - attrs = xml.sax.xmlreader.AttributesImpl(attribs) - - try: - gen.startElement(self.name, attrs) - - for child in self.childs: - child.save_node(gen) - - gen.endElement(self.name) - except: - logger.exception("Error occured when saving the following XML node: %s with %s", self.name, attrs) - - def save(self, filename): - """Save the current node as root node in a XML file""" - with open(filename,"w") as f: - gen = xml.sax.saxutils.XMLGenerator(f, "utf-8") - gen.startDocument() - self.save_node(gen) - gen.endDocument() + def save(self, filename): + """Save the current node as root node in a XML file""" + with open(filename, "w") as f: + gen = xml.sax.saxutils.XMLGenerator(f, "utf-8") + gen.startDocument() + self.save_node(gen) + gen.endDocument()