Working copy of v3.1
This commit is contained in:
parent
afdf951758
commit
e2fbdecc59
@ -24,6 +24,7 @@ import time
|
|||||||
import credits
|
import credits
|
||||||
from credits import Credits
|
from credits import Credits
|
||||||
import DCC
|
import DCC
|
||||||
|
from modules_keeper import loaded as mods
|
||||||
import module_states_file as xmlparser
|
import module_states_file as xmlparser
|
||||||
|
|
||||||
CREDITS = {}
|
CREDITS = {}
|
||||||
@ -157,7 +158,7 @@ class Message:
|
|||||||
return False
|
return False
|
||||||
return self.srv.accepted_channel(self.channel)
|
return self.srv.accepted_channel(self.channel)
|
||||||
|
|
||||||
def treat(self, mods):
|
def treat(self):
|
||||||
if self.cmd == "PING":
|
if self.cmd == "PING":
|
||||||
self.pong ()
|
self.pong ()
|
||||||
elif self.cmd == "PRIVMSG" and self.ctcp:
|
elif self.cmd == "PRIVMSG" and self.ctcp:
|
||||||
@ -197,7 +198,7 @@ class Message:
|
|||||||
elif self.content == '\x01USERINFO\x01':
|
elif self.content == '\x01USERINFO\x01':
|
||||||
self.srv.send_ctcp(self.sender, "USERINFO %s" % (self.srv.realname))
|
self.srv.send_ctcp(self.sender, "USERINFO %s" % (self.srv.realname))
|
||||||
elif self.content == '\x01VERSION\x01':
|
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':
|
elif self.content[:9] == '\x01DCC CHAT':
|
||||||
words = self.content[1:len(self.content) - 1].split(' ')
|
words = self.content[1:len(self.content) - 1].split(' ')
|
||||||
ip = self.srv.toIP(int(words[3]))
|
ip = self.srv.toIP(int(words[3]))
|
||||||
|
@ -6,13 +6,12 @@ from xml.dom.minidom import parseString
|
|||||||
|
|
||||||
from module_state import ModuleState
|
from module_state import ModuleState
|
||||||
|
|
||||||
nemubotversion = 3.0
|
nemubotversion = 3.1
|
||||||
|
|
||||||
def load():
|
def load():
|
||||||
global DATAS
|
global DATAS
|
||||||
DATAS.setIndex("name", "station")
|
DATAS.setIndex("name", "station")
|
||||||
|
|
||||||
|
|
||||||
def help_tiny ():
|
def help_tiny ():
|
||||||
"""Line inserted in the response to the command !help"""
|
"""Line inserted in the response to the command !help"""
|
||||||
return "Gets information about velib stations"
|
return "Gets information about velib stations"
|
||||||
@ -54,21 +53,20 @@ def station_status(msg, station):
|
|||||||
else:
|
else:
|
||||||
msg.send_chn("%s: station %s inconnue." % (msg.nick, station))
|
msg.send_chn("%s: station %s inconnue." % (msg.nick, station))
|
||||||
|
|
||||||
def parseanswer(msg):
|
def checkStation(msg):
|
||||||
global DATAS
|
global DATAS
|
||||||
if msg.cmd[0] == "velib":
|
if len(msg.cmd) > 5:
|
||||||
if len(msg.cmd) > 5:
|
msg.send_chn("%s: Demande-moi moins de stations à la fois." % msg.nick)
|
||||||
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:
|
|
||||||
return False
|
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
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
<nemubotmodule name="velib">
|
<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/" />
|
<server ip="www.velib.paris.fr" url="/service/stationdetails/paris/" />
|
||||||
</nemubotmodule>
|
</nemubotmodule>
|
||||||
|
67
nemubot.py
67
nemubot.py
@ -24,37 +24,48 @@ import traceback
|
|||||||
|
|
||||||
import reloader
|
import reloader
|
||||||
import prompt
|
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()
|
servers = dict()
|
||||||
|
|
||||||
#Add modules dir path
|
if __name__ == "__main__":
|
||||||
if os.path.isdir("./modules/"):
|
#Add modules dir path
|
||||||
modules_path = os.path.realpath(os.path.abspath("./modules/"))
|
if os.path.isdir("./modules/"):
|
||||||
if modules_path not in sys.path:
|
modules_path = os.path.realpath(os.path.abspath("./modules/"))
|
||||||
sys.path.insert(0, modules_path)
|
if modules_path not in module_importer.modules_path:
|
||||||
|
module_importer.modules_path.append(modules_path + "/")
|
||||||
|
|
||||||
#Load given files
|
#Load given files
|
||||||
if len(sys.argv) >= 2:
|
if len(sys.argv) >= 2:
|
||||||
for arg in sys.argv[1:]:
|
for arg in sys.argv[1:]:
|
||||||
if os.path.isfile(arg):
|
if os.path.isfile(arg):
|
||||||
prompt.load_file(arg, servers)
|
prompt.load_file(arg, servers)
|
||||||
elif os.path.isdir(arg):
|
elif os.path.isdir(arg):
|
||||||
sys.path.insert(1, arg)
|
module_importer.modules_path.append(arg)
|
||||||
|
|
||||||
print ("Nemubot ready, my PID is %i!" % (os.getpid()))
|
print ("Nemubot ready, my PID is %i!" % (os.getpid()))
|
||||||
while prompt.launch(servers):
|
while prompt.launch(servers):
|
||||||
try:
|
try:
|
||||||
imp.reload(reloader)
|
imp.reload(reloader)
|
||||||
if prompt.MODS is None:
|
if module_importer.modules_loaded is None:
|
||||||
reloader.reload()
|
reloader.reload()
|
||||||
else:
|
module_importer.modules_loaded = list()
|
||||||
mods = prompt.MODS
|
else:
|
||||||
reloader.reload()
|
mods = module_importer.modules_loaded
|
||||||
prompt.MODS = mods
|
reloader.reload()
|
||||||
except:
|
module_importer.modules_loaded = mods
|
||||||
print ("Unable to reload the prompt due to errors. Fix them before trying to reload the prompt.")
|
except:
|
||||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
print ("Unable to reload the prompt due to errors. Fix them before trying to reload the prompt.")
|
||||||
sys.stdout.write (traceback.format_exception_only(exc_type, exc_value)[0])
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
sys.stdout.write (traceback.format_exception_only(exc_type, exc_value)[0])
|
||||||
|
|
||||||
print ("Bye")
|
print ("Bye")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
173
prompt.py
173
prompt.py
@ -16,20 +16,16 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import imp
|
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import server
|
import server
|
||||||
|
from modules_keeper import loaded as keeper
|
||||||
import module_states_file as xmlparser
|
import module_states_file as xmlparser
|
||||||
|
|
||||||
selectedServer = None
|
selectedServer = None
|
||||||
modules_path = "./modules/"
|
|
||||||
datas_path = "./datas/"
|
|
||||||
|
|
||||||
MODS = list()
|
|
||||||
|
|
||||||
def parsecmd(msg):
|
def parsecmd(msg):
|
||||||
"""Parse the command line"""
|
"""Parse the command line"""
|
||||||
@ -60,17 +56,9 @@ def getPS1():
|
|||||||
|
|
||||||
def launch(servers):
|
def launch(servers):
|
||||||
"""Launch the prompt and readline"""
|
"""Launch the prompt and readline"""
|
||||||
global MODS
|
|
||||||
if MODS is None:
|
|
||||||
MODS = list()
|
|
||||||
|
|
||||||
#Load messages module
|
#Load messages module
|
||||||
server.message.load(datas_path + "general.xml")
|
server.message.load(datas_path + "general.xml")
|
||||||
|
|
||||||
#Update launched servers
|
|
||||||
for srv in servers:
|
|
||||||
servers[srv].update_mods(MODS)
|
|
||||||
|
|
||||||
ret = ""
|
ret = ""
|
||||||
cmds = list()
|
cmds = list()
|
||||||
while ret != "quit" and ret != "reset" and ret != "refresh":
|
while ret != "quit" and ret != "reset" and ret != "refresh":
|
||||||
@ -98,131 +86,21 @@ def launch(servers):
|
|||||||
if ret == "refresh":
|
if ret == "refresh":
|
||||||
return True
|
return True
|
||||||
#Save and shutdown modules
|
#Save and shutdown modules
|
||||||
for m in MODS:
|
global keeper
|
||||||
m.save()
|
for m in keeper:
|
||||||
try:
|
keeper.unload_from_module(m)
|
||||||
m.close()
|
keeper = None
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
MODS = None
|
|
||||||
return ret == "reset"
|
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 #
|
# 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):
|
def load_file(filename, servers):
|
||||||
"""Realy load a file"""
|
"""Realy load a file"""
|
||||||
global MODS
|
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
config = xmlparser.parse_file(filename)
|
config = xmlparser.parse_file(filename)
|
||||||
if config.getName() == "nemubotconfig" or config.getName() == "config":
|
if config.getName() == "nemubotconfig" or config.getName() == "config":
|
||||||
@ -235,20 +113,16 @@ def load_file(filename, servers):
|
|||||||
else:
|
else:
|
||||||
print (" Server `%s' already added, skiped." % srv.id)
|
print (" Server `%s' already added, skiped." % srv.id)
|
||||||
if srv.autoconnect:
|
if srv.autoconnect:
|
||||||
srv.launch(MODS)
|
srv.launch()
|
||||||
#Load files asked by the configuration file
|
#Load files asked by the configuration file
|
||||||
for load in config.getNodes("load"):
|
for load in config.getNodes("load"):
|
||||||
load_file(load["path"], servers)
|
load_file(load["path"], servers)
|
||||||
elif config.getName() == "nemubotmodule":
|
elif config.getName() == "nemubotmodule":
|
||||||
load_module(config, servers)
|
__import__(config["name"])
|
||||||
else:
|
else:
|
||||||
print (" Can't load `%s'; this is not a valid nemubot configuration file." % filename)
|
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:
|
else:
|
||||||
load_module_from_name(filename, servers)
|
__import__(filename)
|
||||||
|
|
||||||
|
|
||||||
def load(cmds, servers):
|
def load(cmds, servers):
|
||||||
@ -261,18 +135,16 @@ def load(cmds, servers):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def unload(cmds, servers):
|
def unload(cmds, servers):
|
||||||
"""Unload a module"""
|
"""Unload a module"""
|
||||||
global MODS
|
if len(cmds) == 2 and cmds[1] == "all":
|
||||||
if len(cmds) == 2 and cmds[1] == "all":
|
for mod in MODS:
|
||||||
for mod in MODS:
|
keeper.unload_from_module(mod.name)
|
||||||
try:
|
elif len(cmds) > 1:
|
||||||
mod.unload()
|
for name in cmds[1:]:
|
||||||
except AttributeError:
|
if keeper.unload_from_name(name):
|
||||||
continue
|
print(" Module `%s' successfully unloaded." % name)
|
||||||
while len(MODS) > 0:
|
else:
|
||||||
MODS.pop()
|
print(" No module `%s' loaded, can't unload!" % name)
|
||||||
elif len(cmds) > 1:
|
|
||||||
print("Ok")
|
|
||||||
|
|
||||||
def close(cmds, servers):
|
def close(cmds, servers):
|
||||||
"""Disconnect and forget (remove from the servers list) the server"""
|
"""Disconnect and forget (remove from the servers list) the server"""
|
||||||
@ -311,7 +183,7 @@ def liste(cmds, servers):
|
|||||||
for srv in servers.keys():
|
for srv in servers.keys():
|
||||||
print (" - %s ;" % srv)
|
print (" - %s ;" % srv)
|
||||||
elif l == "mod" or l == "mods" or l == "module" or l == "modules":
|
elif l == "mod" or l == "mods" or l == "module" or l == "modules":
|
||||||
for mod in MODS:
|
for mod in keeper:
|
||||||
print (" - %s ;" % mod.name)
|
print (" - %s ;" % mod.name)
|
||||||
elif l == "ban" or l == "banni":
|
elif l == "ban" or l == "banni":
|
||||||
for ban in server.message.BANLIST:
|
for ban in server.message.BANLIST:
|
||||||
@ -335,24 +207,23 @@ def connect(cmds, servers):
|
|||||||
if len(cmds) > 1:
|
if len(cmds) > 1:
|
||||||
for s in cmds[1:]:
|
for s in cmds[1:]:
|
||||||
if s in servers:
|
if s in servers:
|
||||||
servers[s].launch(MODS)
|
servers[s].launch()
|
||||||
else:
|
else:
|
||||||
print ("connect: server `%s' not found." % s)
|
print ("connect: server `%s' not found." % s)
|
||||||
|
|
||||||
elif selectedServer is not None:
|
elif selectedServer is not None:
|
||||||
selectedServer.launch(MODS)
|
selectedServer.launch()
|
||||||
else:
|
else:
|
||||||
print (" Please SELECT a server or give its name in argument.")
|
print (" Please SELECT a server or give its name in argument.")
|
||||||
|
|
||||||
def hotswap(cmds, servers):
|
def hotswap(cmds, servers):
|
||||||
"""Reload a server class"""
|
"""Reload a server class"""
|
||||||
global MODS, selectedServer
|
global selectedServer
|
||||||
if len(cmds) > 1:
|
if len(cmds) > 1:
|
||||||
print ("hotswap: apply only on selected server")
|
print ("hotswap: apply only on selected server")
|
||||||
elif selectedServer is not None:
|
elif selectedServer is not None:
|
||||||
del servers[selectedServer.id]
|
del servers[selectedServer.id]
|
||||||
srv = server.Server(selectedServer.node, selectedServer.nick, selectedServer.owner, selectedServer.realname, selectedServer.s)
|
srv = server.Server(selectedServer.node, selectedServer.nick, selectedServer.owner, selectedServer.realname, selectedServer.s)
|
||||||
srv.update_mods(MODS)
|
|
||||||
servers[srv.id] = srv
|
servers[srv.id] = srv
|
||||||
selectedServer.kill()
|
selectedServer.kill()
|
||||||
selectedServer = srv
|
selectedServer = srv
|
||||||
|
@ -22,6 +22,7 @@ import credits
|
|||||||
import channel
|
import channel
|
||||||
import DCC
|
import DCC
|
||||||
import message
|
import message
|
||||||
|
import module_importer
|
||||||
import module_state
|
import module_state
|
||||||
import module_states_file
|
import module_states_file
|
||||||
import prompt
|
import prompt
|
||||||
@ -32,6 +33,7 @@ def reload():
|
|||||||
imp.reload(channel)
|
imp.reload(channel)
|
||||||
imp.reload(DCC)
|
imp.reload(DCC)
|
||||||
imp.reload(message)
|
imp.reload(message)
|
||||||
|
imp.reload(module_importer)
|
||||||
imp.reload(module_state)
|
imp.reload(module_state)
|
||||||
imp.reload(module_states_file)
|
imp.reload(module_states_file)
|
||||||
imp.reload(prompt)
|
imp.reload(prompt)
|
||||||
|
293
server.py
293
server.py
@ -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)
|
|
Loading…
Reference in New Issue
Block a user