diff --git a/DCC.py b/DCC.py
index 48bb0b0..703bc90 100644
--- a/DCC.py
+++ b/DCC.py
@@ -26,18 +26,14 @@ import time
import traceback
import message
+import server
#Store all used ports
PORTS = list()
-class DCC(threading.Thread):
+class DCC(server.Server):
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.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
# Informations about the sender
@@ -64,7 +60,7 @@ class DCC(threading.Thread):
threading.Thread.__init__(self)
def foundPort(self):
- """Found a free port for the connexion"""
+ """Found a free port for the connection"""
for p in range(65432, 65535):
if p not in PORTS:
PORTS.append(p)
@@ -73,41 +69,18 @@ class DCC(threading.Thread):
@property
def id(self):
+ """Gives the server identifiant"""
return self.srv.id + "/" + self.sender
def setError(self, msg):
self.error = True
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):
"""Accept a DCC connection"""
- self.conn = socket.socket()
+ self.s = socket.socket()
try:
- self.conn.connect((host, port))
+ self.s.connect((host, port))
print ('Accepted user from', host, port, "for", self.sender)
self.connected = True
self.stop = False
@@ -143,13 +116,13 @@ class DCC(threading.Thread):
s.listen(1)
#Waiting for the client
- (self.conn, addr) = s.accept()
+ (self.s, addr) = s.accept()
print ('Connected by', addr)
self.connected = True
return True
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):
"""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 self.error:
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:
self.start()
self.DCC = True
@@ -190,8 +163,8 @@ class DCC(threading.Thread):
with open(self.messages, 'rb') as f:
d = f.read(268435456) #Packets size: 256Mo
while d:
- self.conn.sendall(d)
- self.conn.recv(4) #The client send a confirmation after each packet
+ self.s.sendall(d)
+ self.s.recv(4) #The client send a confirmation after each packet
d = f.read(268435456) #Packets size: 256Mo
# Messages connection
@@ -211,7 +184,7 @@ class DCC(threading.Thread):
self.nicksize = len(self.srv.nick)
self.Bnick = self.srv.nick.encode()
while not self.stop:
- raw = self.conn.recv(1024) #recieve server messages
+ raw = self.s.recv(1024) #recieve server messages
if not raw:
break
readbuffer = readbuffer + raw
@@ -222,7 +195,7 @@ class DCC(threading.Thread):
self.treatement(line)
if self.connected:
- self.conn.close()
+ self.s.close()
self.connected = False
#Remove from DCC connections server list
diff --git a/IRCServer.py b/IRCServer.py
new file mode 100644
index 0000000..2a3cfae
--- /dev/null
+++ b/IRCServer.py
@@ -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 .
+
+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)
diff --git a/bot.py b/bot.py
index f0cb476..878cde9 100644
--- a/bot.py
+++ b/bot.py
@@ -26,19 +26,26 @@ import consumer
import event
import hooks
from networkbot import NetworkBot
-from server import Server
+from IRCServer import IRCServer
+import response
ID_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
class Bot:
- def __init__(self, servers=dict(), modules=dict(), mp=list()):
+ def __init__(self, ip, realname, mp=list()):
# Bot general informations
- self.version = 3.2
- self.version_txt = "3.2-dev"
+ self.version = 3.3
+ 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
- self.servers = servers
- self.modules = modules
+ self.servers = dict()
+ self.modules = dict()
# Context paths
self.modules_path = mp
@@ -61,6 +68,36 @@ class Bot:
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):
"""Register an event and return its identifiant for futur update"""
if eid is None:
@@ -126,7 +163,7 @@ class Bot:
while len(self.events)>0 and datetime.now() >= self.events[0].current:
#print ("end timer: while")
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_timer()
@@ -134,11 +171,11 @@ class Bot:
def addServer(self, node, nick, owner, realname):
"""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:
self.servers[srv.id] = srv
if srv.autoconnect:
- srv.launch(self)
+ srv.launch(self.receive_message)
return True
else:
return False
@@ -296,6 +333,16 @@ class Bot:
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):
"""Treat a command message"""
treated = list()
@@ -401,6 +448,8 @@ class Bot:
return treated
+def _ctcp_response(sndr, msg):
+ return response.Response(sndr, msg, ctcp=True)
def hotswap(bak):
return Bot(bak.servers, bak.modules, bak.modules_path)
diff --git a/consumer.py b/consumer.py
index a71a51a..0789d40 100644
--- a/consumer.py
+++ b/consumer.py
@@ -17,12 +17,15 @@
# along with this program. If not, see .
import queue
+import re
import threading
import traceback
import sys
+import bot
from message import Message
from response import Response
+import server
class MessageConsumer:
"""Store a message before treating"""
@@ -33,11 +36,64 @@ class MessageConsumer:
self.prvt = prvt
self.data = data
- def run(self):
- # Create, parse and treat the message
+ def treat_in(self, context, msg):
+ """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:
msg = Message(self.srv, self.raw, self.time, self.prvt)
- res = msg.treat()
+ res = self.treat_in(context, msg)
except:
print ("\033[1;31mERROR:\033[0m occurred during the "
"processing of the message: %s" % self.raw)
@@ -51,26 +107,25 @@ class MessageConsumer:
if isinstance(res, list):
for r in res:
if isinstance(r, Response):
- self.srv.send_response(r, self.data)
+ self.treat_out(context, r)
elif isinstance(r, list):
for s in r:
if isinstance(s, Response):
- self.srv.send_response(s, self.data)
+ self.treat_out(context, s)
elif isinstance(res, Response):
- self.srv.send_response(res, self.data)
-
+ self.treat_out(context, res)
+
# Inform that the message has been treated
self.srv.msg_treated(self.data)
-
+
class EventConsumer:
"""Store a event before treating"""
- def __init__(self, context, evt, timeout=20):
- self.context = context
+ def __init__(self, evt, timeout=20):
self.evt = evt
self.timeout = timeout
- def run(self):
+ def run(self, context):
try:
self.evt.launch_check()
except:
@@ -79,9 +134,9 @@ class EventConsumer:
traceback.print_exception(exc_type, exc_value,
exc_traceback)
if self.evt.next is not None:
- self.context.add_event(self.evt, self.evt.id)
-
-
+ context.add_event(self.evt, self.evt.id)
+
+
class Consumer(threading.Thread):
"""Dequeue and exec requested action"""
@@ -94,7 +149,7 @@ class Consumer(threading.Thread):
try:
while not self.stop:
stm = self.context.cnsr_queue.get(True, 20)
- stm.run()
+ stm.run(self.context)
except queue.Empty:
pass
diff --git a/hooks.py b/hooks.py
index d6a5641..34e5332 100644
--- a/hooks.py
+++ b/hooks.py
@@ -24,7 +24,7 @@ class MessagesHook:
# Store specials hook
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
self.cmd_hook = dict()
diff --git a/importer.py b/importer.py
index 810daa1..3cc10ba 100644
--- a/importer.py
+++ b/importer.py
@@ -136,7 +136,7 @@ class ModuleLoader(SourceLoader):
if not hasattr(module, "nemubotversion"):
raise ImportError("Module `%s' is not a nemubot module."%self.name)
# Check module version
- if module.nemubotversion != self.context.version:
+ if module.nemubotversion > self.context.version:
raise ImportError("Module `%s' is not compatible with this "
"version." % self.name)
diff --git a/message.py b/message.py
index 0839c42..d6be735 100644
--- a/message.py
+++ b/message.py
@@ -73,9 +73,17 @@ class Message:
self.channel = self.pickWords(words[2:]).decode()
if self.cmd == 'PRIVMSG':
- #Check for CTCP request
- self.ctcp = len(words[3]) > 1 and (words[3][0] == 0x01 or words[3][1] == 0x01)
- self.content = self.pickWords(words[3:])
+ # Check for CTCP request
+ self.ctcp = len(words[3]) > 1 and (words[3][0] == 0x01 or words[3][1] == 0x01)
+ 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:
for i in range(2, len(words)):
if words[i][0] == 58:
@@ -125,8 +133,9 @@ class Message:
def is_owner(self):
return self.nick == self.srv.owner
- def authorize(self):
+ def authorize_DEPRECATED(self):
"""Is nemubot listening for the sender on this channel?"""
+ # TODO: deprecated
if self.srv.isDCC(self.sender):
return True
elif self.realname not in CREDITS:
@@ -139,13 +148,7 @@ class Message:
def treat(self):
"""Parse and treat the message"""
- if self.cmd == "PING":
- 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.channel in self.srv.channels:
if self.cmd == "353":
self.srv.channels[self.channel].parse353(self)
elif self.cmd == "332":
@@ -166,53 +169,13 @@ class Message:
self.srv.channels[chn].part(self.nick)
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):
self.parsemsg()
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
- 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(' ')
if self.cmd[0] == "ban":
if len(self.cmd) > 1:
diff --git a/modules/cmd_server.py b/modules/cmd_server.py
index 354e128..b86cd9c 100644
--- a/modules/cmd_server.py
+++ b/modules/cmd_server.py
@@ -50,12 +50,12 @@ def connect(data, toks, context, prompt):
if len(toks) > 1:
for s in toks[1:]:
if s in context.servers:
- context.servers[s].launch(context)
+ context.servers[s].launch(context.receive_message)
else:
print ("connect: server `%s' not found." % s)
elif prompt.selectedServer is not None:
- prompt.selectedServer.launch(context)
+ prompt.selectedServer.launch(context.receive_message)
else:
print (" Please SELECT a server or give its name in argument.")
diff --git a/nemubot.py b/nemubot.py
index f984e25..5948304 100755
--- a/nemubot.py
+++ b/nemubot.py
@@ -29,7 +29,7 @@ import importer
if __name__ == "__main__":
# Create bot context
- context = bot.Bot()
+ context = bot.Bot(0, "FIXME")
# Load the prompt
prmpt = prompt.Prompt()
diff --git a/networkbot.py b/networkbot.py
index 53e886b..3624efc 100644
--- a/networkbot.py
+++ b/networkbot.py
@@ -217,6 +217,9 @@ class NetworkBot:
elif (cmd == b'HOOK' or cmd == b'"HOOK"') and len(args) > 0: # Action requested
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):
self.send_cmd(["HOOK", msg.raw])
diff --git a/response.py b/response.py
index f4cb09c..8d41a3b 100644
--- a/response.py
+++ b/response.py
@@ -22,12 +22,14 @@ import sys
class Response:
def __init__(self, sender, message=None, channel=None, nick=None, server=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.more = more
self.rawtitle = title
+ self.server = server
self.messages = list()
self.alone = True
+ self.ctcp = ctcp
if message is not None:
self.append_message(message, shown_first_count=shown_first_count)
self.elt = 0 # Next element to display
diff --git a/server.py b/server.py
index 479d2e4..67bf0bc 100644
--- a/server.py
+++ b/server.py
@@ -16,37 +16,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-import errno
-import os
import socket
import sys
import threading
-import traceback
-
-import channel
-import DCC
-import message
-import xmlparser
class Server(threading.Thread):
- def __init__(self, node, nick, owner, realname, socket = None):
+ def __init__(self, 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
self.moremessages = dict()
@@ -55,41 +34,17 @@ class Server(threading.Thread):
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 allow_all(self):
- return self.node.hasAttribute("allowall") and self.node["allowall"] == "true"
-
@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)
+ ip = self.node["ip"]
else:
#TODO: find the external IP
- pass
+ ip = "0.0.0.0"
+ for b in ip.split("."):
+ sum = 256 * sum + int(b)
return sum
def toIP(self, input):
@@ -101,27 +56,22 @@ class Server(threading.Thread):
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_pong(self, cnt):
- self.s.send(("PONG %s\r\n" % cnt).encode ())
+ """Gives the server identifiant"""
+ raise NotImplemented()
def msg_treated(self, origin):
- """Do nothing, here for implement abstract class"""
- pass
+ """Action done on server when a message was treated"""
+ raise NotImplemented()
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())
if not res.alone:
@@ -132,188 +82,74 @@ class Server(threading.Thread):
if not res.alone:
self.moremessages[res.sender] = res
- def send_ctcp(self, to, msg, cmd = "NOTICE", endl = "\r\n"):
+ 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 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 line != "":
- self.s.send (("%s %s :\x01%s\x01%s" % (cmd, to.split("!")[0], line, endl)).encode ())
+ if line != "":
+ self.send_msg_final(to.split("!")[0], "\x01" + line + "\x01", cmd, endl)
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)
+ raise NotImplemented()
-
- def send_msg_final(self, channel, msg, cmd = "PRIVMSG", endl = "\r\n"):
- """Send a message without checks"""
- if channel == self.nick:
- 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_final(self, channel, msg, cmd="PRIVMSG", endl="\r\n"):
+ """Send a message without checks or format"""
+ raise NotImplemented()
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)
+ raise NotImplemented()
- 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"""
- if self.accepted_channel(channel):
- self.send_msg_final(channel, msg, cmd, endl)
+ if msg is not None:
+ 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"):
- """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_msg_verified(self, sender, channel, msg, cmd="PRIVMSG", endl="\r\n"):
+ """A more secure way to send messages"""
+ raise NotImplemented()
- 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"""
- 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.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)
+ raise NotImplemented()
def disconnect(self):
- """Close the socket with the server and all DCC client connections"""
+ """Close the socket with the server"""
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):
+ """Just stop the main loop, don't close the socket directly"""
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.s.send(("Bye!\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 launch(self, context, verb=True):
+ def launch(self, receive_action, verb=True):
"""Connect to the server if it is no yet connected"""
- self.context = context
+ self._receive_action = receive_action
if not self.connected:
self.stop = False
self.start()
elif verb:
print (" Already connected.")
- def treat_msg(self, line, private = False):
- self.context.receive_message(self, line, private)
+ def treat_msg(self, line, private=False):
+ self._receive_action(self, line, private)
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()
- 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)
+ raise NotImplemented()