Introducing a new nemubot architecture for servers and messages treatment: messages have no more context or server
This commit is contained in:
parent
f00dfc82f7
commit
a62940380e
53
DCC.py
53
DCC.py
@ -26,18 +26,14 @@ import time
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import message
|
import message
|
||||||
|
import server
|
||||||
|
|
||||||
#Store all used ports
|
#Store all used ports
|
||||||
PORTS = list()
|
PORTS = list()
|
||||||
|
|
||||||
class DCC(threading.Thread):
|
class DCC(server.Server):
|
||||||
def __init__(self, srv, dest, socket=None):
|
def __init__(self, srv, dest, socket=None):
|
||||||
self.DCC = False # Is this a DCC connection
|
|
||||||
self.error = False # An error has occur, closing the connection?
|
self.error = False # An error has occur, closing the connection?
|
||||||
self.stop = False # Stop requered
|
|
||||||
self.stopping = threading.Event() # Event to listen for full disconnection
|
|
||||||
self.conn = socket # The socket
|
|
||||||
self.connected = self.conn is not None # Is connected?
|
|
||||||
self.messages = list() # Message queued before connexion
|
self.messages = list() # Message queued before connexion
|
||||||
|
|
||||||
# Informations about the sender
|
# Informations about the sender
|
||||||
@ -64,7 +60,7 @@ class DCC(threading.Thread):
|
|||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
def foundPort(self):
|
def foundPort(self):
|
||||||
"""Found a free port for the connexion"""
|
"""Found a free port for the connection"""
|
||||||
for p in range(65432, 65535):
|
for p in range(65432, 65535):
|
||||||
if p not in PORTS:
|
if p not in PORTS:
|
||||||
PORTS.append(p)
|
PORTS.append(p)
|
||||||
@ -73,41 +69,18 @@ class DCC(threading.Thread):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
|
"""Gives the server identifiant"""
|
||||||
return self.srv.id + "/" + self.sender
|
return self.srv.id + "/" + self.sender
|
||||||
|
|
||||||
def setError(self, msg):
|
def setError(self, msg):
|
||||||
self.error = True
|
self.error = True
|
||||||
self.srv.send_msg_usr(self.sender, msg)
|
self.srv.send_msg_usr(self.sender, msg)
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
"""Close the connection if connected"""
|
|
||||||
if self.connected:
|
|
||||||
self.stop = True
|
|
||||||
self.conn.shutdown(socket.SHUT_RDWR)
|
|
||||||
self.stopping.wait()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def kill(self):
|
|
||||||
"""Stop the loop without closing the socket"""
|
|
||||||
if self.connected:
|
|
||||||
self.stop = True
|
|
||||||
self.connected = False
|
|
||||||
#self.stopping.wait()#Compare with server before delete me
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def launch(self, mods=None):
|
|
||||||
"""Connect to the client if not already connected"""
|
|
||||||
if not self.connected:
|
|
||||||
self.stop = False
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def accept_user(self, host, port):
|
def accept_user(self, host, port):
|
||||||
"""Accept a DCC connection"""
|
"""Accept a DCC connection"""
|
||||||
self.conn = socket.socket()
|
self.s = socket.socket()
|
||||||
try:
|
try:
|
||||||
self.conn.connect((host, port))
|
self.s.connect((host, port))
|
||||||
print ('Accepted user from', host, port, "for", self.sender)
|
print ('Accepted user from', host, port, "for", self.sender)
|
||||||
self.connected = True
|
self.connected = True
|
||||||
self.stop = False
|
self.stop = False
|
||||||
@ -143,13 +116,13 @@ class DCC(threading.Thread):
|
|||||||
|
|
||||||
s.listen(1)
|
s.listen(1)
|
||||||
#Waiting for the client
|
#Waiting for the client
|
||||||
(self.conn, addr) = s.accept()
|
(self.s, addr) = s.accept()
|
||||||
print ('Connected by', addr)
|
print ('Connected by', addr)
|
||||||
self.connected = True
|
self.connected = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def send_dcc_raw(self, line):
|
def send_dcc_raw(self, line):
|
||||||
self.conn.sendall(line + b'\n')
|
self.s.sendall(line + b'\n')
|
||||||
|
|
||||||
def send_dcc(self, msg, to = None):
|
def send_dcc(self, msg, to = None):
|
||||||
"""If we talk to this user, send a message through this connection
|
"""If we talk to this user, send a message through this connection
|
||||||
@ -157,7 +130,7 @@ class DCC(threading.Thread):
|
|||||||
if to is None or to == self.sender or to == self.nick:
|
if to is None or to == self.sender or to == self.nick:
|
||||||
if self.error:
|
if self.error:
|
||||||
self.srv.send_msg_final(self.nick, msg)
|
self.srv.send_msg_final(self.nick, msg)
|
||||||
elif not self.connected or self.conn is None:
|
elif not self.connected or self.s is None:
|
||||||
if not self.DCC:
|
if not self.DCC:
|
||||||
self.start()
|
self.start()
|
||||||
self.DCC = True
|
self.DCC = True
|
||||||
@ -190,8 +163,8 @@ class DCC(threading.Thread):
|
|||||||
with open(self.messages, 'rb') as f:
|
with open(self.messages, 'rb') as f:
|
||||||
d = f.read(268435456) #Packets size: 256Mo
|
d = f.read(268435456) #Packets size: 256Mo
|
||||||
while d:
|
while d:
|
||||||
self.conn.sendall(d)
|
self.s.sendall(d)
|
||||||
self.conn.recv(4) #The client send a confirmation after each packet
|
self.s.recv(4) #The client send a confirmation after each packet
|
||||||
d = f.read(268435456) #Packets size: 256Mo
|
d = f.read(268435456) #Packets size: 256Mo
|
||||||
|
|
||||||
# Messages connection
|
# Messages connection
|
||||||
@ -211,7 +184,7 @@ class DCC(threading.Thread):
|
|||||||
self.nicksize = len(self.srv.nick)
|
self.nicksize = len(self.srv.nick)
|
||||||
self.Bnick = self.srv.nick.encode()
|
self.Bnick = self.srv.nick.encode()
|
||||||
while not self.stop:
|
while not self.stop:
|
||||||
raw = self.conn.recv(1024) #recieve server messages
|
raw = self.s.recv(1024) #recieve server messages
|
||||||
if not raw:
|
if not raw:
|
||||||
break
|
break
|
||||||
readbuffer = readbuffer + raw
|
readbuffer = readbuffer + raw
|
||||||
@ -222,7 +195,7 @@ class DCC(threading.Thread):
|
|||||||
self.treatement(line)
|
self.treatement(line)
|
||||||
|
|
||||||
if self.connected:
|
if self.connected:
|
||||||
self.conn.close()
|
self.s.close()
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
#Remove from DCC connections server list
|
#Remove from DCC connections server list
|
||||||
|
263
IRCServer.py
Normal file
263
IRCServer.py
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
# -*- 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 errno
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import channel
|
||||||
|
import DCC
|
||||||
|
import message
|
||||||
|
import server
|
||||||
|
import xmlparser
|
||||||
|
|
||||||
|
class IRCServer(server.Server):
|
||||||
|
"""Class to interact with an IRC server"""
|
||||||
|
|
||||||
|
def __init__(self, node, nick, owner, realname):
|
||||||
|
"""Initialize an IRC server
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
node -- server node from XML configuration
|
||||||
|
nick -- nick used by the bot on this server
|
||||||
|
owner -- nick used by the bot owner on this server
|
||||||
|
realname -- string used as realname on this server
|
||||||
|
|
||||||
|
"""
|
||||||
|
server.Server.__init__(self)
|
||||||
|
|
||||||
|
self.node = node
|
||||||
|
|
||||||
|
self.nick = nick
|
||||||
|
self.owner = owner
|
||||||
|
self.realname = realname
|
||||||
|
|
||||||
|
# Listen private messages?
|
||||||
|
self.listen_nick = True
|
||||||
|
|
||||||
|
self.dcc_clients = dict()
|
||||||
|
|
||||||
|
self.channels = dict()
|
||||||
|
for chn in self.node.getNodes("channel"):
|
||||||
|
chan = channel.Channel(chn, self)
|
||||||
|
self.channels[chan.name] = chan
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host(self):
|
||||||
|
"""Return the server hostname"""
|
||||||
|
if self.node is not None and self.node.hasAttribute("server"):
|
||||||
|
return self.node["server"]
|
||||||
|
else:
|
||||||
|
return "localhost"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port(self):
|
||||||
|
"""Return the connection port used on this server"""
|
||||||
|
if self.node is not None and self.node.hasAttribute("port"):
|
||||||
|
return self.node.getInt("port")
|
||||||
|
else:
|
||||||
|
return "6667"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password(self):
|
||||||
|
"""Return the password used to connect to this server"""
|
||||||
|
if self.node is not None and self.node.hasAttribute("password"):
|
||||||
|
return self.node["password"]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allow_all(self):
|
||||||
|
"""If True, treat message from all channels, not only listed one"""
|
||||||
|
return (self.node is not None and self.node.hasAttribute("allowall")
|
||||||
|
and self.node["allowall"] == "true")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def autoconnect(self):
|
||||||
|
"""Autoconnect the server when added"""
|
||||||
|
if self.node is not None and 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):
|
||||||
|
"""Gives the server identifiant"""
|
||||||
|
return self.host + ":" + str(self.port)
|
||||||
|
|
||||||
|
def accepted_channel(self, chan, sender = None):
|
||||||
|
"""Return True if the channel (or the user) is authorized"""
|
||||||
|
if self.allow_all:
|
||||||
|
return True
|
||||||
|
elif 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 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:
|
||||||
|
if chan.instanceof(list):
|
||||||
|
for c in chan:
|
||||||
|
self.leave(c)
|
||||||
|
else:
|
||||||
|
self.s.send(("PART %s\r\n" % self.channels[chan].name).encode ())
|
||||||
|
del self.channels[chan]
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Main loop
|
||||||
|
def run(self):
|
||||||
|
if not self.connected:
|
||||||
|
self.s = socket.socket() #Create the socket
|
||||||
|
try:
|
||||||
|
self.s.connect((self.host, self.port)) #Connect to server
|
||||||
|
except socket.error as e:
|
||||||
|
self.s = None
|
||||||
|
print ("\033[1;31mError:\033[0m Unable to connect to %s:%d: %s"
|
||||||
|
% (self.host, self.port, os.strerror(e.errno)))
|
||||||
|
return
|
||||||
|
self.stopping.clear()
|
||||||
|
|
||||||
|
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 ())
|
||||||
|
raw = self.s.recv(1024)
|
||||||
|
if not raw:
|
||||||
|
print ("Unable to connect to %s:%d" % (self.host, self.port))
|
||||||
|
return
|
||||||
|
self.connected = True
|
||||||
|
print ("Connection to %s:%d completed" % (self.host, self.port))
|
||||||
|
|
||||||
|
readbuffer = b'' #Here we store all the messages from server
|
||||||
|
while not self.stop:
|
||||||
|
readbuffer = readbuffer + raw
|
||||||
|
temp = readbuffer.split(b'\n')
|
||||||
|
readbuffer = temp.pop()
|
||||||
|
|
||||||
|
for line in temp:
|
||||||
|
self.treat_msg(line)
|
||||||
|
raw = self.s.recv(1024) #recieve server messages
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
# Overwritted methods
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
"""Close the socket with the server and all DCC client connections"""
|
||||||
|
#Close all DCC connection
|
||||||
|
for clt in self.dcc_clients:
|
||||||
|
self.dcc_clients[clt].disconnect()
|
||||||
|
return server.Server.disconnect(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Abstract methods
|
||||||
|
|
||||||
|
def send_pong(self, cnt):
|
||||||
|
"""Send a PONG command to the server with argument cnt"""
|
||||||
|
self.s.send(("PONG %s\r\n" % cnt).encode ())
|
||||||
|
|
||||||
|
def msg_treated(self, origin):
|
||||||
|
"""Do nothing; here for implement abstract class"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
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, line, cmd="PRIVMSG", endl="\r\n"):
|
||||||
|
"""Send a message without checks or format"""
|
||||||
|
#TODO: add something for post message treatment here
|
||||||
|
if channel == self.nick:
|
||||||
|
print ("\033[1;35mWarning:\033[0m Nemubot talks to himself: %s" % msg)
|
||||||
|
traceback.print_stack()
|
||||||
|
if line is not None and channel is not None:
|
||||||
|
if self.s is None:
|
||||||
|
print ("\033[1;35mWarning:\033[0m Attempt to send message on a non connected server: %s: %s" % (self.id, line))
|
||||||
|
traceback.print_stack()
|
||||||
|
elif len(line) < 442:
|
||||||
|
self.s.send (("%s %s :%s%s" % (cmd, channel, line, endl)).encode ())
|
||||||
|
else:
|
||||||
|
print ("\033[1;35mWarning:\033[0m Message truncated due to size (%d ; max : 442) : %s" % (len(line), line))
|
||||||
|
traceback.print_stack()
|
||||||
|
self.s.send (("%s %s :%s%s" % (cmd, channel, line[0:442]+"...", endl)).encode ())
|
||||||
|
|
||||||
|
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 or user in self.dcc_clients:
|
||||||
|
self.send_dcc(msg, user)
|
||||||
|
else:
|
||||||
|
for line in msg.split("\n"):
|
||||||
|
if line != "":
|
||||||
|
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):
|
||||||
|
server.Server.send_msg(self, 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)
|
67
bot.py
67
bot.py
@ -26,19 +26,26 @@ import consumer
|
|||||||
import event
|
import event
|
||||||
import hooks
|
import hooks
|
||||||
from networkbot import NetworkBot
|
from networkbot import NetworkBot
|
||||||
from server import Server
|
from IRCServer import IRCServer
|
||||||
|
import response
|
||||||
|
|
||||||
ID_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
ID_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
class Bot:
|
class Bot:
|
||||||
def __init__(self, servers=dict(), modules=dict(), mp=list()):
|
def __init__(self, ip, realname, mp=list()):
|
||||||
# Bot general informations
|
# Bot general informations
|
||||||
self.version = 3.2
|
self.version = 3.3
|
||||||
self.version_txt = "3.2-dev"
|
self.version_txt = "3.3-dev"
|
||||||
|
|
||||||
|
# Save various informations
|
||||||
|
self.ip = ip
|
||||||
|
self.realname = realname
|
||||||
|
self.ctcp_capabilities = dict()
|
||||||
|
self.init_ctcp_capabilities()
|
||||||
|
|
||||||
# Keep global context: servers and modules
|
# Keep global context: servers and modules
|
||||||
self.servers = servers
|
self.servers = dict()
|
||||||
self.modules = modules
|
self.modules = dict()
|
||||||
|
|
||||||
# Context paths
|
# Context paths
|
||||||
self.modules_path = mp
|
self.modules_path = mp
|
||||||
@ -61,6 +68,36 @@ class Bot:
|
|||||||
self.cnsr_thrd_size = -1
|
self.cnsr_thrd_size = -1
|
||||||
|
|
||||||
|
|
||||||
|
def init_ctcp_capabilities(self):
|
||||||
|
"""Reset existing CTCP capabilities to default one"""
|
||||||
|
self.ctcp_capabilities["ACTION"] = lambda msg: print ("ACTION receive")
|
||||||
|
self.ctcp_capabilities["CLIENTINFO"] = self._ctcp_clientinfo
|
||||||
|
self.ctcp_capabilities["DCC"] = self._ctcp_dcc
|
||||||
|
self.ctcp_capabilities["NEMUBOT"] = lambda srv, msg: _ctcp_response(
|
||||||
|
msg.sender, "NEMUBOT %f" % self.version)
|
||||||
|
self.ctcp_capabilities["TIME"] = lambda srv, msg: _ctcp_response(
|
||||||
|
msg.sender, "TIME %s" % (datetime.now()))
|
||||||
|
self.ctcp_capabilities["USERINFO"] = lambda srv, msg: _ctcp_response(
|
||||||
|
msg.sender, "USERINFO %s" % self.realname)
|
||||||
|
self.ctcp_capabilities["VERSION"] = lambda srv, msg: _ctcp_response(
|
||||||
|
msg.sender, "VERSION nemubot v%s" % self.version_txt)
|
||||||
|
|
||||||
|
def _ctcp_clientinfo(self, srv, msg):
|
||||||
|
"""Response to CLIENTINFO CTCP message"""
|
||||||
|
return _ctcp_response(msg.sndr,
|
||||||
|
" ".join(self.ctcp_capabilities.keys()))
|
||||||
|
|
||||||
|
def _ctcp_dcc(self, srv, msg):
|
||||||
|
"""Response to DCC CTCP message"""
|
||||||
|
ip = self.srv.toIP(int(msg.cmds[3]))
|
||||||
|
conn = DCC(srv, msg.sender)
|
||||||
|
if conn.accept_user(ip, int(msg.cmds[4])):
|
||||||
|
srv.dcc_clients[conn.sender] = conn
|
||||||
|
conn.send_dcc("Hello %s!" % conn.nick)
|
||||||
|
else:
|
||||||
|
print ("DCC: unable to connect to %s:%s" % (ip, msg.cmds[4]))
|
||||||
|
|
||||||
|
|
||||||
def add_event(self, evt, eid=None):
|
def add_event(self, evt, eid=None):
|
||||||
"""Register an event and return its identifiant for futur update"""
|
"""Register an event and return its identifiant for futur update"""
|
||||||
if eid is None:
|
if eid is None:
|
||||||
@ -126,7 +163,7 @@ class Bot:
|
|||||||
while len(self.events)>0 and datetime.now() >= self.events[0].current:
|
while len(self.events)>0 and datetime.now() >= self.events[0].current:
|
||||||
#print ("end timer: while")
|
#print ("end timer: while")
|
||||||
evt = self.events.pop(0)
|
evt = self.events.pop(0)
|
||||||
self.cnsr_queue.put_nowait(consumer.EventConsumer(self, evt))
|
self.cnsr_queue.put_nowait(consumer.EventConsumer(evt))
|
||||||
self.update_consumers()
|
self.update_consumers()
|
||||||
|
|
||||||
self.update_timer()
|
self.update_timer()
|
||||||
@ -134,11 +171,11 @@ class Bot:
|
|||||||
|
|
||||||
def addServer(self, node, nick, owner, realname):
|
def addServer(self, node, nick, owner, realname):
|
||||||
"""Add a new server to the context"""
|
"""Add a new server to the context"""
|
||||||
srv = Server(node, nick, owner, realname)
|
srv = IRCServer(node, nick, owner, realname)
|
||||||
if srv.id not in self.servers:
|
if srv.id not in self.servers:
|
||||||
self.servers[srv.id] = srv
|
self.servers[srv.id] = srv
|
||||||
if srv.autoconnect:
|
if srv.autoconnect:
|
||||||
srv.launch(self)
|
srv.launch(self.receive_message)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@ -296,6 +333,16 @@ class Bot:
|
|||||||
self.check_rest_times(store, h)
|
self.check_rest_times(store, h)
|
||||||
|
|
||||||
|
|
||||||
|
def treat_post(self, msg):
|
||||||
|
"""Treat a message before send"""
|
||||||
|
for h, lvl, store in self.create_cache("all_post"):
|
||||||
|
c = h.run(msg)
|
||||||
|
self.check_rest_times(store, h)
|
||||||
|
if not c:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def treat_cmd(self, msg):
|
def treat_cmd(self, msg):
|
||||||
"""Treat a command message"""
|
"""Treat a command message"""
|
||||||
treated = list()
|
treated = list()
|
||||||
@ -401,6 +448,8 @@ class Bot:
|
|||||||
|
|
||||||
return treated
|
return treated
|
||||||
|
|
||||||
|
def _ctcp_response(sndr, msg):
|
||||||
|
return response.Response(sndr, msg, ctcp=True)
|
||||||
|
|
||||||
def hotswap(bak):
|
def hotswap(bak):
|
||||||
return Bot(bak.servers, bak.modules, bak.modules_path)
|
return Bot(bak.servers, bak.modules, bak.modules_path)
|
||||||
|
77
consumer.py
77
consumer.py
@ -17,12 +17,15 @@
|
|||||||
# 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 queue
|
import queue
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import bot
|
||||||
from message import Message
|
from message import Message
|
||||||
from response import Response
|
from response import Response
|
||||||
|
import server
|
||||||
|
|
||||||
class MessageConsumer:
|
class MessageConsumer:
|
||||||
"""Store a message before treating"""
|
"""Store a message before treating"""
|
||||||
@ -33,11 +36,64 @@ class MessageConsumer:
|
|||||||
self.prvt = prvt
|
self.prvt = prvt
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def run(self):
|
def treat_in(self, context, msg):
|
||||||
# Create, parse and treat the message
|
"""Treat the input message"""
|
||||||
|
if msg.cmd == "PING":
|
||||||
|
self.srv.send_pong(msg.content)
|
||||||
|
else:
|
||||||
|
# TODO: Manage credits here
|
||||||
|
context.treat_pre(msg)
|
||||||
|
|
||||||
|
if msg.cmd == "PRIVMSG" and msg.ctcp:
|
||||||
|
if msg.cmds[0] in context.ctcp_capabilities:
|
||||||
|
return context.ctcp_capabilities[msg.cmds[0]](self.srv, msg)
|
||||||
|
else:
|
||||||
|
return bot._ctcp_response(msg.sender, "ERRMSG Unknown or unimplemented CTCP request")
|
||||||
|
elif msg.cmd == "PRIVMSG" and self.srv.accepted_channel(msg.channel):
|
||||||
|
return self.treat_prvmsg(context, msg)
|
||||||
|
# TODO: continue here
|
||||||
|
pass
|
||||||
|
|
||||||
|
def treat_prvmsg(self, context, msg):
|
||||||
|
# Treat all messages starting with 'nemubot:' as distinct commands
|
||||||
|
if msg.content.find("%s:"%self.srv.nick) == 0:
|
||||||
|
# Remove the bot name
|
||||||
|
msg.content = msg.content[len(self.srv.nick)+1:].strip()
|
||||||
|
|
||||||
|
# Treat ping
|
||||||
|
if re.match(".*(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)",
|
||||||
|
msg.content, re.I) is not None:
|
||||||
|
return Response(msg.sender, message="pong",
|
||||||
|
channel=msg.channel, nick=msg.nick)
|
||||||
|
|
||||||
|
# Ask hooks
|
||||||
|
else:
|
||||||
|
return context.treat_ask(self)
|
||||||
|
|
||||||
|
|
||||||
|
def treat_out(self, context, res):
|
||||||
|
"""Treat the output message"""
|
||||||
|
# Define the destination server
|
||||||
|
if (res.server is not None and
|
||||||
|
res.server.instanceof(string) and res.server in context.servers):
|
||||||
|
res.server = context.servers[res.server]
|
||||||
|
if (res.server is not None and
|
||||||
|
not res.server.instanceof(server.Server)):
|
||||||
|
print ("\033[1;35mWarning:\033[0m the server defined in this "
|
||||||
|
"response doesn't exist: %s" % (res.server))
|
||||||
|
res.server = None
|
||||||
|
if res.server is None:
|
||||||
|
res.server = self.srv
|
||||||
|
|
||||||
|
# Sent the message only if treat_post authorize it
|
||||||
|
if context.treat_post(res):
|
||||||
|
res.server.send_response(res, self.data)
|
||||||
|
|
||||||
|
def run(self, context):
|
||||||
|
"""Create, parse and treat the message"""
|
||||||
try:
|
try:
|
||||||
msg = Message(self.srv, self.raw, self.time, self.prvt)
|
msg = Message(self.srv, self.raw, self.time, self.prvt)
|
||||||
res = msg.treat()
|
res = self.treat_in(context, msg)
|
||||||
except:
|
except:
|
||||||
print ("\033[1;31mERROR:\033[0m occurred during the "
|
print ("\033[1;31mERROR:\033[0m occurred during the "
|
||||||
"processing of the message: %s" % self.raw)
|
"processing of the message: %s" % self.raw)
|
||||||
@ -51,13 +107,13 @@ class MessageConsumer:
|
|||||||
if isinstance(res, list):
|
if isinstance(res, list):
|
||||||
for r in res:
|
for r in res:
|
||||||
if isinstance(r, Response):
|
if isinstance(r, Response):
|
||||||
self.srv.send_response(r, self.data)
|
self.treat_out(context, r)
|
||||||
elif isinstance(r, list):
|
elif isinstance(r, list):
|
||||||
for s in r:
|
for s in r:
|
||||||
if isinstance(s, Response):
|
if isinstance(s, Response):
|
||||||
self.srv.send_response(s, self.data)
|
self.treat_out(context, s)
|
||||||
elif isinstance(res, Response):
|
elif isinstance(res, Response):
|
||||||
self.srv.send_response(res, self.data)
|
self.treat_out(context, res)
|
||||||
|
|
||||||
# Inform that the message has been treated
|
# Inform that the message has been treated
|
||||||
self.srv.msg_treated(self.data)
|
self.srv.msg_treated(self.data)
|
||||||
@ -65,12 +121,11 @@ class MessageConsumer:
|
|||||||
|
|
||||||
class EventConsumer:
|
class EventConsumer:
|
||||||
"""Store a event before treating"""
|
"""Store a event before treating"""
|
||||||
def __init__(self, context, evt, timeout=20):
|
def __init__(self, evt, timeout=20):
|
||||||
self.context = context
|
|
||||||
self.evt = evt
|
self.evt = evt
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
def run(self):
|
def run(self, context):
|
||||||
try:
|
try:
|
||||||
self.evt.launch_check()
|
self.evt.launch_check()
|
||||||
except:
|
except:
|
||||||
@ -79,7 +134,7 @@ class EventConsumer:
|
|||||||
traceback.print_exception(exc_type, exc_value,
|
traceback.print_exception(exc_type, exc_value,
|
||||||
exc_traceback)
|
exc_traceback)
|
||||||
if self.evt.next is not None:
|
if self.evt.next is not None:
|
||||||
self.context.add_event(self.evt, self.evt.id)
|
context.add_event(self.evt, self.evt.id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -94,7 +149,7 @@ class Consumer(threading.Thread):
|
|||||||
try:
|
try:
|
||||||
while not self.stop:
|
while not self.stop:
|
||||||
stm = self.context.cnsr_queue.get(True, 20)
|
stm = self.context.cnsr_queue.get(True, 20)
|
||||||
stm.run()
|
stm.run(self.context)
|
||||||
|
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
2
hooks.py
2
hooks.py
@ -24,7 +24,7 @@ class MessagesHook:
|
|||||||
|
|
||||||
# Store specials hook
|
# Store specials hook
|
||||||
self.all_pre = list() # Treated before any parse
|
self.all_pre = list() # Treated before any parse
|
||||||
#self.all_post = list() # Treated before send message to user
|
self.all_post = list() # Treated before send message to user
|
||||||
|
|
||||||
# Store direct hook
|
# Store direct hook
|
||||||
self.cmd_hook = dict()
|
self.cmd_hook = dict()
|
||||||
|
@ -136,7 +136,7 @@ class ModuleLoader(SourceLoader):
|
|||||||
if not hasattr(module, "nemubotversion"):
|
if not hasattr(module, "nemubotversion"):
|
||||||
raise ImportError("Module `%s' is not a nemubot module."%self.name)
|
raise ImportError("Module `%s' is not a nemubot module."%self.name)
|
||||||
# Check module version
|
# Check module version
|
||||||
if module.nemubotversion != self.context.version:
|
if module.nemubotversion > self.context.version:
|
||||||
raise ImportError("Module `%s' is not compatible with this "
|
raise ImportError("Module `%s' is not compatible with this "
|
||||||
"version." % self.name)
|
"version." % self.name)
|
||||||
|
|
||||||
|
61
message.py
61
message.py
@ -76,6 +76,14 @@ class Message:
|
|||||||
# Check for CTCP request
|
# Check for CTCP request
|
||||||
self.ctcp = len(words[3]) > 1 and (words[3][0] == 0x01 or words[3][1] == 0x01)
|
self.ctcp = len(words[3]) > 1 and (words[3][0] == 0x01 or words[3][1] == 0x01)
|
||||||
self.content = self.pickWords(words[3:])
|
self.content = self.pickWords(words[3:])
|
||||||
|
# If CTCP, remove 0x01
|
||||||
|
if self.ctcp:
|
||||||
|
self.content = self.content[1:len(self.content)-1]
|
||||||
|
# Split content by words
|
||||||
|
try:
|
||||||
|
self.cmds = shlex.split(self.content.decode())
|
||||||
|
except ValueError:
|
||||||
|
self.cmds = self.content.decode().split(' ')
|
||||||
elif self.cmd == '353' and len(words) > 3:
|
elif self.cmd == '353' and len(words) > 3:
|
||||||
for i in range(2, len(words)):
|
for i in range(2, len(words)):
|
||||||
if words[i][0] == 58:
|
if words[i][0] == 58:
|
||||||
@ -125,8 +133,9 @@ class Message:
|
|||||||
def is_owner(self):
|
def is_owner(self):
|
||||||
return self.nick == self.srv.owner
|
return self.nick == self.srv.owner
|
||||||
|
|
||||||
def authorize(self):
|
def authorize_DEPRECATED(self):
|
||||||
"""Is nemubot listening for the sender on this channel?"""
|
"""Is nemubot listening for the sender on this channel?"""
|
||||||
|
# TODO: deprecated
|
||||||
if self.srv.isDCC(self.sender):
|
if self.srv.isDCC(self.sender):
|
||||||
return True
|
return True
|
||||||
elif self.realname not in CREDITS:
|
elif self.realname not in CREDITS:
|
||||||
@ -139,13 +148,7 @@ class Message:
|
|||||||
|
|
||||||
def treat(self):
|
def treat(self):
|
||||||
"""Parse and treat the message"""
|
"""Parse and treat the message"""
|
||||||
if self.cmd == "PING":
|
if self.channel in self.srv.channels:
|
||||||
self.srv.send_pong(self.content)
|
|
||||||
elif self.cmd == "PRIVMSG" and self.ctcp:
|
|
||||||
self.parsectcp()
|
|
||||||
elif self.cmd == "PRIVMSG" and self.authorize():
|
|
||||||
return self.parsemsg()
|
|
||||||
elif self.channel in self.srv.channels:
|
|
||||||
if self.cmd == "353":
|
if self.cmd == "353":
|
||||||
self.srv.channels[self.channel].parse353(self)
|
self.srv.channels[self.channel].parse353(self)
|
||||||
elif self.cmd == "332":
|
elif self.cmd == "332":
|
||||||
@ -166,53 +169,13 @@ class Message:
|
|||||||
self.srv.channels[chn].part(self.nick)
|
self.srv.channels[chn].part(self.nick)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def parsectcp(self):
|
|
||||||
"""Parse CTCP requests"""
|
|
||||||
if self.content == '\x01CLIENTINFO\x01':
|
|
||||||
self.srv.send_ctcp(self.sender, "CLIENTINFO TIME USERINFO VERSION CLIENTINFO")
|
|
||||||
elif self.content == '\x01TIME\x01':
|
|
||||||
self.srv.send_ctcp(self.sender, "TIME %s" % (datetime.now()))
|
|
||||||
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 v%s" % self.srv.context.version_txt)
|
|
||||||
elif self.content[:9] == '\x01DCC CHAT':
|
|
||||||
words = self.content[1:len(self.content) - 1].split(' ')
|
|
||||||
ip = self.srv.toIP(int(words[3]))
|
|
||||||
conn = DCC(self.srv, self.sender)
|
|
||||||
if conn.accept_user(ip, int(words[4])):
|
|
||||||
self.srv.dcc_clients[conn.sender] = conn
|
|
||||||
conn.send_dcc("Hello %s!" % conn.nick)
|
|
||||||
else:
|
|
||||||
print ("DCC: unable to connect to %s:%s" % (ip, words[4]))
|
|
||||||
elif self.content == '\x01NEMUBOT\x01':
|
|
||||||
self.srv.send_ctcp(self.sender, "NEMUBOT %f" % self.srv.context.version)
|
|
||||||
elif self.content[:7] != '\x01ACTION':
|
|
||||||
print (self.content)
|
|
||||||
self.srv.send_ctcp(self.sender, "ERRMSG Unknown or unimplemented CTCP request")
|
|
||||||
|
|
||||||
def reparsemsg(self):
|
def reparsemsg(self):
|
||||||
self.parsemsg()
|
self.parsemsg()
|
||||||
|
|
||||||
def parsemsg (self):
|
def parsemsg (self):
|
||||||
self.srv.context.treat_pre(self)
|
|
||||||
#Treat all messages starting with 'nemubot:' as distinct commands
|
|
||||||
if self.content.find("%s:"%self.srv.nick) == 0:
|
|
||||||
#Remove the bot name
|
|
||||||
self.content = self.content[len(self.srv.nick)+1:].strip()
|
|
||||||
messagel = self.content.lower()
|
|
||||||
|
|
||||||
# Treat ping
|
|
||||||
if re.match(".*(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)",
|
|
||||||
messagel) is not None:
|
|
||||||
return Response(self.sender, message="pong", channel=self.channel, nick=self.nick)
|
|
||||||
|
|
||||||
# Ask hooks
|
|
||||||
else:
|
|
||||||
return self.srv.context.treat_ask(self)
|
|
||||||
|
|
||||||
#Owner commands
|
#Owner commands
|
||||||
elif self.content[0] == '`' and self.sender == self.srv.owner:
|
if self.content[0] == '`' and self.sender == self.srv.owner:
|
||||||
self.cmd = self.content[1:].split(' ')
|
self.cmd = self.content[1:].split(' ')
|
||||||
if self.cmd[0] == "ban":
|
if self.cmd[0] == "ban":
|
||||||
if len(self.cmd) > 1:
|
if len(self.cmd) > 1:
|
||||||
|
@ -50,12 +50,12 @@ def connect(data, toks, context, prompt):
|
|||||||
if len(toks) > 1:
|
if len(toks) > 1:
|
||||||
for s in toks[1:]:
|
for s in toks[1:]:
|
||||||
if s in context.servers:
|
if s in context.servers:
|
||||||
context.servers[s].launch(context)
|
context.servers[s].launch(context.receive_message)
|
||||||
else:
|
else:
|
||||||
print ("connect: server `%s' not found." % s)
|
print ("connect: server `%s' not found." % s)
|
||||||
|
|
||||||
elif prompt.selectedServer is not None:
|
elif prompt.selectedServer is not None:
|
||||||
prompt.selectedServer.launch(context)
|
prompt.selectedServer.launch(context.receive_message)
|
||||||
else:
|
else:
|
||||||
print (" Please SELECT a server or give its name in argument.")
|
print (" Please SELECT a server or give its name in argument.")
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ import importer
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Create bot context
|
# Create bot context
|
||||||
context = bot.Bot()
|
context = bot.Bot(0, "FIXME")
|
||||||
|
|
||||||
# Load the prompt
|
# Load the prompt
|
||||||
prmpt = prompt.Prompt()
|
prmpt = prompt.Prompt()
|
||||||
|
@ -217,6 +217,9 @@ class NetworkBot:
|
|||||||
elif (cmd == b'HOOK' or cmd == b'"HOOK"') and len(args) > 0: # Action requested
|
elif (cmd == b'HOOK' or cmd == b'"HOOK"') and len(args) > 0: # Action requested
|
||||||
self.context.receive_message(self, args[0].encode(), True, tag)
|
self.context.receive_message(self, args[0].encode(), True, tag)
|
||||||
|
|
||||||
|
elif (cmd == b'NOMORE' or cmd == b'"NOMORE"') and len(args) > 0: # Reset !more feature
|
||||||
|
if args[0] in self.srv.moremessages:
|
||||||
|
del self.srv.moremessages[args[0]]
|
||||||
|
|
||||||
def exec_hook(self, msg):
|
def exec_hook(self, msg):
|
||||||
self.send_cmd(["HOOK", msg.raw])
|
self.send_cmd(["HOOK", msg.raw])
|
||||||
|
@ -22,12 +22,14 @@ import sys
|
|||||||
class Response:
|
class Response:
|
||||||
def __init__(self, sender, message=None, channel=None, nick=None, server=None,
|
def __init__(self, sender, message=None, channel=None, nick=None, server=None,
|
||||||
nomore="No more message", title=None, more="(suite) ", count=None,
|
nomore="No more message", title=None, more="(suite) ", count=None,
|
||||||
shown_first_count=-1):
|
ctcp=False, shown_first_count=-1):
|
||||||
self.nomore = nomore
|
self.nomore = nomore
|
||||||
self.more = more
|
self.more = more
|
||||||
self.rawtitle = title
|
self.rawtitle = title
|
||||||
|
self.server = server
|
||||||
self.messages = list()
|
self.messages = list()
|
||||||
self.alone = True
|
self.alone = True
|
||||||
|
self.ctcp = ctcp
|
||||||
if message is not None:
|
if message is not None:
|
||||||
self.append_message(message, shown_first_count=shown_first_count)
|
self.append_message(message, shown_first_count=shown_first_count)
|
||||||
self.elt = 0 # Next element to display
|
self.elt = 0 # Next element to display
|
||||||
|
234
server.py
234
server.py
@ -16,37 +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 errno
|
|
||||||
import os
|
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
|
||||||
|
|
||||||
import channel
|
|
||||||
import DCC
|
|
||||||
import message
|
|
||||||
import xmlparser
|
|
||||||
|
|
||||||
class Server(threading.Thread):
|
class Server(threading.Thread):
|
||||||
def __init__(self, node, nick, owner, realname, socket = None):
|
def __init__(self, socket = None):
|
||||||
self.stop = False
|
self.stop = False
|
||||||
self.stopping = threading.Event()
|
self.stopping = threading.Event()
|
||||||
self.nick = nick
|
|
||||||
self.owner = owner
|
|
||||||
self.realname = realname
|
|
||||||
self.s = socket
|
self.s = socket
|
||||||
self.connected = self.s is not None
|
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
|
|
||||||
|
|
||||||
self.moremessages = dict()
|
self.moremessages = dict()
|
||||||
|
|
||||||
@ -55,41 +34,17 @@ class Server(threading.Thread):
|
|||||||
def isDCC(self, to=None):
|
def isDCC(self, to=None):
|
||||||
return to is not None and to in self.dcc_clients
|
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 allow_all(self):
|
|
||||||
return self.node.hasAttribute("allowall") and self.node["allowall"] == "true"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ip(self):
|
def ip(self):
|
||||||
"""Convert common IP representation to little-endian integer representation"""
|
"""Convert common IP representation to little-endian integer representation"""
|
||||||
sum = 0
|
sum = 0
|
||||||
if self.node.hasAttribute("ip"):
|
if self.node.hasAttribute("ip"):
|
||||||
for b in self.node["ip"].split("."):
|
ip = self.node["ip"]
|
||||||
sum = 256 * sum + int(b)
|
|
||||||
else:
|
else:
|
||||||
#TODO: find the external IP
|
#TODO: find the external IP
|
||||||
pass
|
ip = "0.0.0.0"
|
||||||
|
for b in ip.split("."):
|
||||||
|
sum = 256 * sum + int(b)
|
||||||
return sum
|
return sum
|
||||||
|
|
||||||
def toIP(self, input):
|
def toIP(self, input):
|
||||||
@ -101,27 +56,22 @@ class Server(threading.Thread):
|
|||||||
input = (input - mod) / 256
|
input = (input - mod) / 256
|
||||||
return ip[:len(ip) - 1]
|
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
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
return self.host + ":" + str(self.port)
|
"""Gives the server identifiant"""
|
||||||
|
raise NotImplemented()
|
||||||
def send_pong(self, cnt):
|
|
||||||
self.s.send(("PONG %s\r\n" % cnt).encode ())
|
|
||||||
|
|
||||||
def msg_treated(self, origin):
|
def msg_treated(self, origin):
|
||||||
"""Do nothing, here for implement abstract class"""
|
"""Action done on server when a message was treated"""
|
||||||
pass
|
raise NotImplemented()
|
||||||
|
|
||||||
def send_response(self, res, origin):
|
def send_response(self, res, origin):
|
||||||
if res.channel is not None and res.channel != self.nick:
|
"""Analyse a Response and send it"""
|
||||||
|
# TODO: how to send a CTCP message to a different person
|
||||||
|
if res.ctcp:
|
||||||
|
self.send_ctcp(res.sender, res.get_message())
|
||||||
|
|
||||||
|
elif res.channel is not None and res.channel != self.nick:
|
||||||
self.send_msg(res.channel, res.get_message())
|
self.send_msg(res.channel, res.get_message())
|
||||||
|
|
||||||
if not res.alone:
|
if not res.alone:
|
||||||
@ -136,129 +86,62 @@ class Server(threading.Thread):
|
|||||||
"""Send a message as CTCP response"""
|
"""Send a message as CTCP response"""
|
||||||
if msg is not None and to is not None:
|
if msg is not None and to is not None:
|
||||||
for line in msg.split("\n"):
|
for line in msg.split("\n"):
|
||||||
if self.s is None:
|
if line != "":
|
||||||
print ("\033[1;35mWarning:\033[0m Attempt to send message on a non connected server: %s: %s" % (self.id, msg))
|
self.send_msg_final(to.split("!")[0], "\x01" + line + "\x01", cmd, endl)
|
||||||
traceback.print_stack()
|
|
||||||
elif line != "":
|
|
||||||
self.s.send (("%s %s :\x01%s\x01%s" % (cmd, to.split("!")[0], line, endl)).encode ())
|
|
||||||
|
|
||||||
def send_dcc(self, msg, to):
|
def send_dcc(self, msg, to):
|
||||||
"""Send a message through DCC connection"""
|
"""Send a message through DCC connection"""
|
||||||
if msg is not None and to is not None:
|
raise NotImplemented()
|
||||||
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"):
|
def send_msg_final(self, channel, msg, cmd="PRIVMSG", endl="\r\n"):
|
||||||
"""Send a message without checks"""
|
"""Send a message without checks or format"""
|
||||||
if channel == self.nick:
|
raise NotImplemented()
|
||||||
print ("\033[1;35mWarning:\033[0m Nemubot talks to himself: %s" % msg)
|
|
||||||
traceback.print_stack()
|
|
||||||
if msg is not None and channel is not None:
|
|
||||||
for line in msg.split("\n"):
|
|
||||||
if line != "":
|
|
||||||
if self.s is None:
|
|
||||||
print ("\033[1;35mWarning:\033[0m Attempt to send message on a non connected server: %s: %s" % (self.id, msg))
|
|
||||||
traceback.print_stack()
|
|
||||||
elif 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_usr(self, user, msg):
|
def send_msg_usr(self, user, msg):
|
||||||
"""Send a message to a user instead of a channel"""
|
"""Send a message to a user instead of a channel"""
|
||||||
if user is not None and user[0] != "#":
|
raise NotImplemented()
|
||||||
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"):
|
def send_msg(self, channel, msg, cmd="PRIVMSG", endl="\r\n"):
|
||||||
"""Send a message to a channel"""
|
"""Send a message to a channel"""
|
||||||
if self.accepted_channel(channel):
|
if msg is not None:
|
||||||
self.send_msg_final(channel, msg, cmd, endl)
|
for line in msg.split("\n"):
|
||||||
|
if line != "":
|
||||||
|
self.send_msg_final(channel, line, cmd, endl)
|
||||||
|
|
||||||
def send_msg_verified(self, sender, channel, msg, cmd="PRIVMSG", endl="\r\n"):
|
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"""
|
"""A more secure way to send messages"""
|
||||||
if self.accepted_channel(channel, sender):
|
raise NotImplemented()
|
||||||
self.send_msg_final(channel, msg, cmd, endl)
|
|
||||||
|
|
||||||
def send_global(self, msg, cmd="PRIVMSG", endl="\r\n"):
|
def send_global(self, msg, cmd="PRIVMSG", endl="\r\n"):
|
||||||
"""Send a message to all channels on this server"""
|
"""Send a message to all channels on this server"""
|
||||||
for channel in self.channels.keys():
|
raise NotImplemented()
|
||||||
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.allow_all:
|
|
||||||
return True
|
|
||||||
elif 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):
|
def disconnect(self):
|
||||||
"""Close the socket with the server and all DCC client connections"""
|
"""Close the socket with the server"""
|
||||||
if self.connected:
|
if self.connected:
|
||||||
self.stop = True
|
self.stop = True
|
||||||
self.s.shutdown(socket.SHUT_RDWR)
|
self.s.shutdown(socket.SHUT_RDWR)
|
||||||
|
|
||||||
#Close all DCC connection
|
|
||||||
for clt in self.dcc_clients:
|
|
||||||
self.dcc_clients[clt].disconnect()
|
|
||||||
|
|
||||||
self.stopping.wait()
|
self.stopping.wait()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
|
"""Just stop the main loop, don't close the socket directly"""
|
||||||
if self.connected:
|
if self.connected:
|
||||||
self.stop = True
|
self.stop = True
|
||||||
self.connected = False
|
self.connected = False
|
||||||
#Send a message in order to close the socket
|
#Send a message in order to close the socket
|
||||||
self.s.send(("WHO %s\r\n" % self.nick).encode ())
|
self.s.send(("Bye!\r\n" % self.nick).encode ())
|
||||||
self.stopping.wait()
|
self.stopping.wait()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def join(self, chan, password = None):
|
def launch(self, receive_action, verb=True):
|
||||||
"""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 launch(self, context, verb=True):
|
|
||||||
"""Connect to the server if it is no yet connected"""
|
"""Connect to the server if it is no yet connected"""
|
||||||
self.context = context
|
self._receive_action = receive_action
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
self.stop = False
|
self.stop = False
|
||||||
self.start()
|
self.start()
|
||||||
@ -266,54 +149,7 @@ class Server(threading.Thread):
|
|||||||
print (" Already connected.")
|
print (" Already connected.")
|
||||||
|
|
||||||
def treat_msg(self, line, private=False):
|
def treat_msg(self, line, private=False):
|
||||||
self.context.receive_message(self, line, private)
|
self._receive_action(self, line, private)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.connected:
|
raise NotImplemented()
|
||||||
self.s = socket.socket() #Create the socket
|
|
||||||
try:
|
|
||||||
self.s.connect((self.host, self.port)) #Connect to server
|
|
||||||
except socket.error as e:
|
|
||||||
self.s = None
|
|
||||||
print ("\033[1;31mError:\033[0m Unable to connect to %s:%d: %s"
|
|
||||||
% (self.host, self.port, os.strerror(e.errno)))
|
|
||||||
return
|
|
||||||
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