diff --git a/message.py b/message.py
index 005948b..bd053d3 100644
--- a/message.py
+++ b/message.py
@@ -24,6 +24,7 @@ import time
import credits
from credits import Credits
import DCC
+from modules_keeper import loaded as mods
import module_states_file as xmlparser
CREDITS = {}
@@ -157,7 +158,7 @@ class Message:
return False
return self.srv.accepted_channel(self.channel)
- def treat(self, mods):
+ def treat(self):
if self.cmd == "PING":
self.pong ()
elif self.cmd == "PRIVMSG" and self.ctcp:
@@ -197,7 +198,7 @@ class Message:
elif self.content == '\x01USERINFO\x01':
self.srv.send_ctcp(self.sender, "USERINFO %s" % (self.srv.realname))
elif self.content == '\x01VERSION\x01':
- self.srv.send_ctcp(self.sender, "VERSION nemubot v3")
+ self.srv.send_ctcp(self.sender, "VERSION nemubot v%d"%VERSION)
elif self.content[:9] == '\x01DCC CHAT':
words = self.content[1:len(self.content) - 1].split(' ')
ip = self.srv.toIP(int(words[3]))
diff --git a/modules/velib.py b/modules/velib.py
index 19ee264..72b53e4 100644
--- a/modules/velib.py
+++ b/modules/velib.py
@@ -6,13 +6,12 @@ from xml.dom.minidom import parseString
from module_state import ModuleState
-nemubotversion = 3.0
+nemubotversion = 3.1
def load():
global DATAS
DATAS.setIndex("name", "station")
-
def help_tiny ():
"""Line inserted in the response to the command !help"""
return "Gets information about velib stations"
@@ -54,21 +53,20 @@ def station_status(msg, station):
else:
msg.send_chn("%s: station %s inconnue." % (msg.nick, station))
-def parseanswer(msg):
+def checkStation(msg):
global DATAS
- if msg.cmd[0] == "velib":
- if len(msg.cmd) > 5:
- msg.send_chn("%s: Demande-moi moins de stations à la fois." % msg.nick)
- elif len(msg.cmd) > 1:
- for station in msg.cmd[1:]:
- if re.match("^[0-9]{4,5}$", station):
- station_status(msg, station)
- elif station in DATAS.index:
- station_status(msg, DATAS.index[station]["number"])
- else:
- msg.send_chn("%s: numéro de station invalide." % (msg.nick))
- else:
- msg.send_chn("%s: Pour quelle station ?" % msg.nick)
- return True
- else:
+ if len(msg.cmd) > 5:
+ msg.send_chn("%s: Demande-moi moins de stations à la fois." % msg.nick)
return False
+ elif len(msg.cmd) > 1:
+ for station in msg.cmd[1:]:
+ if re.match("^[0-9]{4,5}$", station):
+ station_status(msg, station)
+ elif station in DATAS.index:
+ station_status(msg, DATAS.index[station]["number"])
+ else:
+ msg.send_chn("%s: numéro de station invalide." % (msg.nick))
+ else:
+ msg.send_chn("%s: Pour quelle station ?" % msg.nick)
+ return False
+ return True
diff --git a/modules/velib.xml b/modules/velib.xml
index dc9ca99..5350ac5 100644
--- a/modules/velib.xml
+++ b/modules/velib.xml
@@ -1,4 +1,14 @@
+ Gets information about velib stations
+
+
+ !velib /number/ ...
+ gives available bikes and slots at the station /number/.
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/nemubot.py b/nemubot.py
index 3fac62c..789f08b 100755
--- a/nemubot.py
+++ b/nemubot.py
@@ -24,37 +24,48 @@ import traceback
import reloader
import prompt
+import module_importer
+
+VERSION = 3.1
+module_importer.VERSION = VERSION
+reloader.message.VERSION = VERSION
+
+datas_path = "./datas/"
+prompt.datas_path = datas_path
+module_importer.datas_path = datas_path
servers = dict()
-#Add modules dir path
-if os.path.isdir("./modules/"):
- modules_path = os.path.realpath(os.path.abspath("./modules/"))
- if modules_path not in sys.path:
- sys.path.insert(0, modules_path)
+if __name__ == "__main__":
+ #Add modules dir path
+ if os.path.isdir("./modules/"):
+ modules_path = os.path.realpath(os.path.abspath("./modules/"))
+ if modules_path not in module_importer.modules_path:
+ module_importer.modules_path.append(modules_path + "/")
-#Load given files
-if len(sys.argv) >= 2:
- for arg in sys.argv[1:]:
- if os.path.isfile(arg):
- prompt.load_file(arg, servers)
- elif os.path.isdir(arg):
- sys.path.insert(1, arg)
+ #Load given files
+ if len(sys.argv) >= 2:
+ for arg in sys.argv[1:]:
+ if os.path.isfile(arg):
+ prompt.load_file(arg, servers)
+ elif os.path.isdir(arg):
+ module_importer.modules_path.append(arg)
-print ("Nemubot ready, my PID is %i!" % (os.getpid()))
-while prompt.launch(servers):
- try:
- imp.reload(reloader)
- if prompt.MODS is None:
- reloader.reload()
- else:
- mods = prompt.MODS
- reloader.reload()
- prompt.MODS = mods
- except:
- print ("Unable to reload the prompt due to errors. Fix them before trying to reload the prompt.")
- exc_type, exc_value, exc_traceback = sys.exc_info()
- sys.stdout.write (traceback.format_exception_only(exc_type, exc_value)[0])
+ print ("Nemubot ready, my PID is %i!" % (os.getpid()))
+ while prompt.launch(servers):
+ try:
+ imp.reload(reloader)
+ if module_importer.modules_loaded is None:
+ reloader.reload()
+ module_importer.modules_loaded = list()
+ else:
+ mods = module_importer.modules_loaded
+ reloader.reload()
+ module_importer.modules_loaded = mods
+ except:
+ print ("Unable to reload the prompt due to errors. Fix them before trying to reload the prompt.")
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ sys.stdout.write (traceback.format_exception_only(exc_type, exc_value)[0])
-print ("Bye")
-sys.exit(0)
+ print ("Bye")
+ sys.exit(0)
diff --git a/prompt.py b/prompt.py
index fad092d..bc71dc0 100755
--- a/prompt.py
+++ b/prompt.py
@@ -16,20 +16,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-import imp
import os
import shlex
import sys
import traceback
import server
+from modules_keeper import loaded as keeper
import module_states_file as xmlparser
selectedServer = None
-modules_path = "./modules/"
-datas_path = "./datas/"
-
-MODS = list()
def parsecmd(msg):
"""Parse the command line"""
@@ -60,17 +56,9 @@ def getPS1():
def launch(servers):
"""Launch the prompt and readline"""
- global MODS
- if MODS is None:
- MODS = list()
-
#Load messages module
server.message.load(datas_path + "general.xml")
- #Update launched servers
- for srv in servers:
- servers[srv].update_mods(MODS)
-
ret = ""
cmds = list()
while ret != "quit" and ret != "reset" and ret != "refresh":
@@ -98,131 +86,21 @@ def launch(servers):
if ret == "refresh":
return True
#Save and shutdown modules
- for m in MODS:
- m.save()
- try:
- m.close()
- except AttributeError:
- pass
- MODS = None
+ global keeper
+ for m in keeper:
+ keeper.unload_from_module(m)
+ keeper = None
return ret == "reset"
-##########################
-# #
-# Module functions #
-# #
-##########################
-
-def mod_save(mod, datas_path):
- mod.DATAS.save(datas_path + "/" + mod.name + ".xml")
- mod.print ("Saving!")
-
-def mod_has_access(mod, config, msg):
- if config is not None and config.hasNode("channel"):
- for chan in config.getNodes("channel"):
- if (chan["server"] is None or chan["server"] == msg.srv.id) and (chan["channel"] is None or chan["channel"] == msg.channel):
- return True
- return False
- else:
- return True
-
##########################
# #
# Permorming functions #
# #
##########################
-def load_module_from_name(name, servers, config=None):
- try:
- #Import the module code
- loaded = False
- for md in MODS:
- if md.name == name:
- mod = imp.reload(md)
- loaded = True
- if hasattr(mod, 'reload'):
- mod.reload()
- break
- if not loaded:
- mod = __import__(name)
- imp.reload(mod)
- try:
- if mod.nemubotversion < 3.0:
- print (" Module `%s' is not compatible with this version." % name)
- return false
-
- #Set module common functions and datas
- mod.name = name
- mod.print = lambda msg: print("[%s] %s"%(mod.name, msg))
- mod.DATAS = xmlparser.parse_file(datas_path + "/" + name + ".xml")
- mod.CONF = config
- mod.SRVS = servers
- mod.has_access = lambda msg: mod_has_access(mod, config, msg)
- mod.save = lambda: mod_save(mod, datas_path)
-
- #Load dependancies
- if mod.CONF is not None and mod.CONF.hasNode("dependson"):
- mod.MODS = dict()
- for depend in mod.CONF.getNodes("dependson"):
- for md in MODS:
- if md.name == depend["name"]:
- mod.MODS[md.name] = md
- break
- if depend["name"] not in mod.MODS:
- print ("\033[1;31mERROR:\033[0m in module `%s', module `%s' require by this module but is not loaded." % (mod.name,depend["name"]))
- return
-
- try:
- test = mod.parseask
- except AttributeError:
- print ("\033[1;35mWarning:\033[0m in module `%s', no function parseask defined." % mod.name)
- mod.parseask = lambda x: False
-
- try:
- test = mod.parseanswer
- except AttributeError:
- print ("\033[1;35mWarning:\033[0m in module `%s', no function parseanswer defined." % mod.name)
- mod.parseanswer = lambda x: False
-
- try:
- test = mod.parselisten
- except AttributeError:
- print ("\033[1;35mWarning:\033[0m in module `%s', no function parselisten defined." % mod.name)
- mod.parselisten = lambda x: False
-
- try:
- mod.load()
- print (" Module `%s' successfully loaded." % name)
- except AttributeError:
- print (" Module `%s' successfully added." % name)
- #TODO: don't append already running modules
- exitsts = False
- for md in MODS:
- if md.name == name:
- exitsts = True
- break
- if not exitsts:
- MODS.append(mod)
- except AttributeError :
- print (" Module `%s' is not a nemubot module." % name)
- for srv in servers:
- servers[srv].update_mods(MODS)
- except IOError:
- print (" Module `%s' not loaded: unable to find module implementation." % name)
- except ImportError:
- print ("\033[1;31mERROR:\033[0m Module attached to the file `%s' not loaded. Is this file existing?" % name)
- exc_type, exc_value, exc_traceback = sys.exc_info()
- traceback.print_exception(exc_type, exc_value, exc_traceback)
-
-def load_module(config, servers):
- global MODS
- if config.hasAttribute("name"):
- load_module_from_name(config["name"], servers, config)
-
def load_file(filename, servers):
"""Realy load a file"""
- global MODS
if os.path.isfile(filename):
config = xmlparser.parse_file(filename)
if config.getName() == "nemubotconfig" or config.getName() == "config":
@@ -235,20 +113,16 @@ def load_file(filename, servers):
else:
print (" Server `%s' already added, skiped." % srv.id)
if srv.autoconnect:
- srv.launch(MODS)
+ srv.launch()
#Load files asked by the configuration file
for load in config.getNodes("load"):
load_file(load["path"], servers)
elif config.getName() == "nemubotmodule":
- load_module(config, servers)
+ __import__(config["name"])
else:
print (" Can't load `%s'; this is not a valid nemubot configuration file." % filename)
- elif os.path.isfile(filename + ".xml"):
- load_file(filename + ".xml", servers)
- elif os.path.isfile("./modules/" + filename + ".xml"):
- load_file("./modules/" + filename + ".xml", servers)
else:
- load_module_from_name(filename, servers)
+ __import__(filename)
def load(cmds, servers):
@@ -261,18 +135,16 @@ def load(cmds, servers):
return
def unload(cmds, servers):
- """Unload a module"""
- global MODS
- if len(cmds) == 2 and cmds[1] == "all":
- for mod in MODS:
- try:
- mod.unload()
- except AttributeError:
- continue
- while len(MODS) > 0:
- MODS.pop()
- elif len(cmds) > 1:
- print("Ok")
+ """Unload a module"""
+ if len(cmds) == 2 and cmds[1] == "all":
+ for mod in MODS:
+ keeper.unload_from_module(mod.name)
+ elif len(cmds) > 1:
+ for name in cmds[1:]:
+ if keeper.unload_from_name(name):
+ print(" Module `%s' successfully unloaded." % name)
+ else:
+ print(" No module `%s' loaded, can't unload!" % name)
def close(cmds, servers):
"""Disconnect and forget (remove from the servers list) the server"""
@@ -311,7 +183,7 @@ def liste(cmds, servers):
for srv in servers.keys():
print (" - %s ;" % srv)
elif l == "mod" or l == "mods" or l == "module" or l == "modules":
- for mod in MODS:
+ for mod in keeper:
print (" - %s ;" % mod.name)
elif l == "ban" or l == "banni":
for ban in server.message.BANLIST:
@@ -335,24 +207,23 @@ def connect(cmds, servers):
if len(cmds) > 1:
for s in cmds[1:]:
if s in servers:
- servers[s].launch(MODS)
+ servers[s].launch()
else:
print ("connect: server `%s' not found." % s)
elif selectedServer is not None:
- selectedServer.launch(MODS)
+ selectedServer.launch()
else:
print (" Please SELECT a server or give its name in argument.")
def hotswap(cmds, servers):
"""Reload a server class"""
- global MODS, selectedServer
+ global selectedServer
if len(cmds) > 1:
print ("hotswap: apply only on selected server")
elif selectedServer is not None:
del servers[selectedServer.id]
srv = server.Server(selectedServer.node, selectedServer.nick, selectedServer.owner, selectedServer.realname, selectedServer.s)
- srv.update_mods(MODS)
servers[srv.id] = srv
selectedServer.kill()
selectedServer = srv
diff --git a/reloader.py b/reloader.py
index b3c6465..6c1ee05 100644
--- a/reloader.py
+++ b/reloader.py
@@ -22,6 +22,7 @@ import credits
import channel
import DCC
import message
+import module_importer
import module_state
import module_states_file
import prompt
@@ -32,6 +33,7 @@ def reload():
imp.reload(channel)
imp.reload(DCC)
imp.reload(message)
+ imp.reload(module_importer)
imp.reload(module_state)
imp.reload(module_states_file)
imp.reload(prompt)
diff --git a/server.py b/server.py
deleted file mode 100644
index 1dc5bf5..0000000
--- a/server.py
+++ /dev/null
@@ -1,293 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Nemubot is a modulable IRC bot, built around XML configuration files.
-# Copyright (C) 2012 Mercier Pierre-Olivier
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import socket
-import sys
-import threading
-import traceback
-
-import channel
-import DCC
-import message
-import module_states_file as xmlparser
-
-class Server(threading.Thread):
- def __init__(self, node, nick, owner, realname, socket = None):
- self.stop = False
- self.stopping = threading.Event()
- self.nick = nick
- self.owner = owner
- self.realname = realname
- self.s = socket
- self.connected = self.s is not None
- self.node = node
-
- self.listen_nick = True
-
- self.dcc_clients = dict()
-
- self.channels = dict()
- for chn in node.getNodes("channel"):
- chan = channel.Channel(chn, self)
- self.channels[chan.name] = chan
-
- threading.Thread.__init__(self)
-
- def isDCC(self, to=None):
- return to is not None and to in self.dcc_clients
-
- @property
- def host(self):
- if self.node.hasAttribute("server"):
- return self.node["server"]
- else:
- return "localhost"
-
- @property
- def port(self):
- if self.node.hasAttribute("port"):
- return self.node.getInt("port")
- else:
- return "6667"
-
- @property
- def password(self):
- if self.node.hasAttribute("password"):
- return self.node["password"]
- else:
- return None
-
- @property
- def partner(self):
- if self.node.hasAttribute("partner"):
- return self.node["partner"]
- else:
- return None
-
- @property
- def ip(self):
- """Convert common IP representation to little-endian integer representation"""
- sum = 0
- if self.node.hasAttribute("ip"):
- for b in self.node["ip"].split("."):
- sum = 256 * sum + int(b)
- else:
- #TODO: find the external IP
- pass
- return sum
-
- def toIP(self, input):
- """Convert little-endian int to IPv4 adress"""
- ip = ""
- for i in range(0,4):
- mod = input % 256
- ip = "%d.%s" % (mod, ip)
- input = (input - mod) / 256
- return ip[:len(ip) - 1]
-
- @property
- def autoconnect(self):
- if self.node.hasAttribute("autoconnect"):
- value = self.node["autoconnect"].lower()
- return value != "no" and value != "off" and value != "false"
- else:
- return False
-
- @property
- def id(self):
- return self.host + ":" + str(self.port)
-
- def send_ctcp(self, to, msg, cmd = "NOTICE", endl = "\r\n"):
- """Send a message as CTCP response"""
- if msg is not None and to is not None:
- for line in msg.split("\n"):
- if line != "":
- self.s.send (("%s %s :\x01%s\x01%s" % (cmd, to.split("!")[0], line, endl)).encode ())
-
- def send_dcc(self, msg, to):
- """Send a message through DCC connection"""
- if msg is not None and to is not None:
- realname = to.split("!")[1]
- if realname not in self.dcc_clients.keys():
- d = dcc.DCC(self, to)
- self.dcc_clients[realname] = d
- self.dcc_clients[realname].send_dcc(msg)
-
-
- def send_msg_final(self, channel, msg, cmd = "PRIVMSG", endl = "\r\n"):
- """Send a message without checks"""
- if channel == self.nick:
- exc_type, exc_value, exc_traceback = sys.exc_info()
- traceback.print_exception(exc_type, exc_value, exc_traceback)
- print ("\033[1;35mWarning:\033[0m Nemubot talks to himself: %s" % msg)
- if msg is not None and channel is not None:
- for line in msg.split("\n"):
- if line != "":
- if len(line) < 442:
- self.s.send (("%s %s :%s%s" % (cmd, channel, line, endl)).encode ())
- else:
- self.s.send (("%s %s :%s%s" % (cmd, channel, line[0:442]+"...", endl)).encode ())
-
- def send_msg_prtn(self, msg):
- """Send a message to partner bot"""
- self.send_msg_final(self.partner, msg)
-
- def send_msg_usr(self, user, msg):
- """Send a message to a user instead of a channel"""
- if user is not None and user[0] != "#":
- realname = user.split("!")[1]
- if realname in self.dcc_clients:
- self.send_dcc(msg, user)
- else:
- self.send_msg_final(user.split('!')[0], msg)
-
- def send_msg(self, channel, msg, cmd = "PRIVMSG", endl = "\r\n"):
- """Send a message to a channel"""
- if self.accepted_channel(channel):
- self.send_msg_final(channel, msg, cmd, endl)
-
- def send_msg_verified(self, sender, channel, msg, cmd = "PRIVMSG", endl = "\r\n"):
- """Send a message to a channel, only if the source user is on this channel too"""
- if self.accepted_channel(channel, sender):
- self.send_msg_final(channel, msg, cmd, endl)
-
- def send_global(self, msg, cmd = "PRIVMSG", endl = "\r\n"):
- """Send a message to all channels on this server"""
- for channel in self.channels.keys():
- self.send_msg(channel, msg, cmd, endl)
-
-
- def accepted_channel(self, chan, sender = None):
- """Return True if the channel (or the user) is authorized"""
- if self.listen_nick:
- return (chan in self.channels and (sender is None or sender in self.channels[chan].people)) or chan == self.nick
- else:
- return chan in self.channels and (sender is None or sender in self.channels[chan].people)
-
- def disconnect(self):
- """Close the socket with the server and all DCC client connections"""
- if self.connected:
- self.stop = True
- self.s.shutdown(socket.SHUT_RDWR)
-
- #Close all DCC connection
- for clt in self.dcc_clients:
- self.dcc_clients[clt].disconnect()
-
- self.stopping.wait()
- return True
- else:
- return False
-
- def kill(self):
- if self.connected:
- self.stop = True
- self.connected = False
- #Send a message in order to close the socket
- self.s.send(("WHO %s\r\n" % self.nick).encode ())
- self.stopping.wait()
- return True
- else:
- return False
-
- def join(self, chan, password = None):
- """Join a channel"""
- if chan is not None and self.connected and chan not in self.channels:
- chn = xmlparser.module_state.ModuleState("channel")
- chn["name"] = chan
- chn["password"] = password
- self.node.addChild(chn)
- self.channels[chan] = channel.Channel(chn, self)
- if password is not None:
- self.s.send(("JOIN %s %s\r\n" % (chan, password)).encode ())
- else:
- self.s.send(("JOIN %s\r\n" % chan).encode ())
- return True
- else:
- return False
-
- def leave(self, chan):
- """Leave a channel"""
- if chan is not None and self.connected and chan in self.channels:
- self.s.send(("PART %s\r\n" % self.channels[chan].name).encode ())
- del self.channels[chan]
- return True
- else:
- return False
-
- def update_mods(self, mods):
- self.mods = mods
-
- def launch(self, mods):
- """Connect to the server if it is no yet connected"""
- if not self.connected:
- self.stop = False
- self.mods = mods
- self.start()
- else:
- print (" Already connected.")
-
- def treat_msg(self, line, private = False):
- try:
- msg = message.Message (self, line, private)
- msg.treat (self.mods)
- except:
- print ("\033[1;31mERROR:\033[0m occurred during the processing of the message: %s" % line)
- exc_type, exc_value, exc_traceback = sys.exc_info()
- traceback.print_exception(exc_type, exc_value, exc_traceback)
-
- def run(self):
- if not self.connected:
- self.s = socket.socket() #Create the socket
- self.s.connect((self.host, self.port)) #Connect to server
- self.stopping.clear()
- self.connected = True
-
- if self.password != None:
- self.s.send(b"PASS " + self.password.encode () + b"\r\n")
- self.s.send(("NICK %s\r\n" % self.nick).encode ())
- self.s.send(("USER %s %s bla :%s\r\n" % (self.nick, self.host, self.realname)).encode ())
- print ("Connection to %s:%d completed" % (self.host, self.port))
-
- if len(self.channels) > 0:
- for chn in self.channels.keys():
- if self.channels[chn].password is not None:
- self.s.send(("JOIN %s %s\r\n" % (self.channels[chn].name, self.channels[chn].password)).encode ())
- else:
- self.s.send(("JOIN %s\r\n" % self.channels[chn].name).encode ())
- print ("Listen to channels: %s" % ' '.join (self.channels.keys()))
-
- readbuffer = b'' #Here we store all the messages from server
- while not self.stop:
- raw = self.s.recv(1024) #recieve server messages
- if not raw:
- break
- readbuffer = readbuffer + raw
- temp = readbuffer.split(b'\n')
- readbuffer = temp.pop()
-
- for line in temp:
- self.treat_msg(line)
-
- if self.connected:
- self.s.close()
- self.connected = False
- print ("Server `%s' successfully stopped." % self.id)
- self.stopping.set()
- #Rearm Thread
- threading.Thread.__init__(self)