1
0
Fork 0

Working copy of v3.1

This commit is contained in:
Némunaire 2012-11-04 17:09:54 +01:00
parent afdf951758
commit e2fbdecc59
7 changed files with 93 additions and 493 deletions

View File

@ -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]))

View File

@ -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

View File

@ -1,4 +1,14 @@
<?xml version="1.0" ?>
<nemubotmodule name="velib">
<help>Gets information about velib stations</help>
<command name="velib" call="checkStation">
<usage>!velib /number/ ...</usage>
<help>gives available bikes and slots at the station /number/.</help>
</command>
<event name="velib" call="nothing">
</event>
<server ip="www.velib.paris.fr" url="/service/stationdetails/paris/" />
</nemubotmodule>
</nemubotmodule>

View File

@ -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)

173
prompt.py
View File

@ -16,20 +16,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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

View File

@ -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)

293
server.py
View File

@ -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 <http://www.gnu.org/licenses/>.
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)