diff --git a/bot.py b/bot.py
index 2c4e58c..30af57e 100644
--- a/bot.py
+++ b/bot.py
@@ -20,26 +20,32 @@ from datetime import datetime
from queue import Queue
import threading
+from botcaps import BotCaps
from consumer import Consumer
import event
-import hooks
+from networkbot import NetworkBot
from server import Server
-class Bot:
+class Bot(BotCaps):
def __init__(self, servers=dict(), modules=dict(), mp=list()):
- self.version = 3.2
- self.version_txt = "3.2"
+ BotCaps.__init__(self, 3.2, "3.2-dev")
+ # Keep global context: servers and modules
self.servers = servers
self.modules = modules
+ # Context paths
self.modules_path = mp
self.datas_path = './datas/'
- self.hooks = hooks.MessagesHook()
+ # Events
self.events = list()
self.event_timer = None
+ # Other known bots, making a bots network
+ self.network = dict()
+
+ # Messages to be treated
self.msg_queue = Queue()
self.msg_thrd = list()
self.msg_thrd_size = -1
@@ -74,7 +80,6 @@ class Bot:
#else:
# print ("Update timer: no timer left")
-
def end_timer(self):
"""Function called at the end of the timer"""
#print ("end timer")
@@ -149,6 +154,14 @@ class Bot:
self.msg_thrd_size += 2
+ def add_networkbot(self, srv, dest, dcc=None):
+ """Append a new bot into the network"""
+ id = srv.id + "/" + dest
+ if id not in self.network:
+ self.network[id] = NetworkBot(self, srv, dest, dcc)
+ return self.network[id]
+
+
def quit(self, verb=False):
"""Save and unload modules and disconnect servers"""
if self.event_timer is not None:
diff --git a/botcaps.py b/botcaps.py
new file mode 100644
index 0000000..624dd3c
--- /dev/null
+++ b/botcaps.py
@@ -0,0 +1,28 @@
+# -*- 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 hooks
+
+class BotCaps:
+ def __init__(self, version=None, version_txt=None):
+ # Bot general informations
+ self.version = version
+ self.version_txt = version_txt
+
+ # Hooks
+ self.hooks = hooks.MessagesHook()
diff --git a/modules/cmd_server.py b/modules/cmd_server.py
index 6609240..354e128 100644
--- a/modules/cmd_server.py
+++ b/modules/cmd_server.py
@@ -16,9 +16,20 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from networkbot import NetworkBot
+
nemubotversion = 3.2
NODATA = True
+def getserver(toks, context, prompt):
+ """Choose the server in toks or prompt"""
+ if len(toks) > 1 and toks[0] in context.servers:
+ return (context.servers[toks[0]], toks[1:])
+ elif prompt.selectedServer is not None:
+ return (prompt.selectedServer, toks)
+ else:
+ return (None, toks)
+
def close(data, toks, context, prompt):
"""Disconnect and forget (remove from the servers list) the server"""
if len(toks) > 1:
@@ -64,6 +75,19 @@ def disconnect(data, toks, context, prompt):
else:
print (" Please SELECT a server or give its name in argument.")
+def discover(data, toks, context, prompt):
+ """Discover a new bot on a server"""
+ (srv, toks) = getserver(toks, context, prompt)
+ if srv is not None:
+ for name in toks[1:]:
+ if "!" in name:
+ bot = context.add_networkbot(srv, name)
+ bot.connect()
+ else:
+ print (" %s is not a valid fullname, for example: nemubot!nemubotV3@bot.nemunai.re")
+ else:
+ print (" Please SELECT a server or give its name in first argument.")
+
def hotswap(data, toks, context, prompt):
"""Reload a server class"""
if len(toks) > 1:
diff --git a/modules/cmd_server.xml b/modules/cmd_server.xml
index 62c16de..7ce23e8 100644
--- a/modules/cmd_server.xml
+++ b/modules/cmd_server.xml
@@ -2,6 +2,7 @@
+
diff --git a/networkbot.py b/networkbot.py
new file mode 100644
index 0000000..0c32ac4
--- /dev/null
+++ b/networkbot.py
@@ -0,0 +1,124 @@
+# -*- 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 random
+import shlex
+
+from botcaps import BotCaps
+from DCC import DCC
+
+class NetworkBot:
+ def __init__(self, context, srv, dest, dcc=None):
+ self.context = context
+ self.srv = srv
+ self.dcc = dcc
+ self.dest = dest
+ self.infos = None
+
+ self.my_tag = random.randint(0,255)
+ self.inc_tag = 0
+ self.tags = dict()
+
+ @property
+ def sender(self):
+ if self.dcc is not None:
+ return self.dcc.sender
+ return None
+
+ @property
+ def nick(self):
+ if self.dcc is not None:
+ return self.dcc.nick
+ return None
+
+ @property
+ def realname(self):
+ if self.dcc is not None:
+ return self.dcc.realname
+ return None
+
+ def send_cmd(self, cmd, data=None):
+ """Create a tag and send the command"""
+ # First, define a tag
+ self.inc_tag = (self.inc_out + 1) % 256
+ while self.inc_tag in self.tags:
+ self.inc_tag = (self.inc_out + 1) % 256
+ tag = ("%c%c" % (self.my_tag, self.inc_tag)).encode()
+
+ if data is not None:
+ self.tags[tag] = data
+ else:
+ self.tags[tag] = cmd
+
+ # Send the command with the tag
+ self.send_response(tag, cmd)
+
+ def send_response(self, tag, msg):
+ """Send a response with a tag"""
+ for line in msg.split("\n"):
+ self.dcc.send_dcc_raw(tag + b' ' + line.encode())
+
+ def send_ack(self, tag):
+ """Acknowledge a command"""
+ if tag in self.tags:
+ del self.tags[tag]
+ self.send_response(tag, "ACK")
+
+ def connect(self):
+ """Making the connexion with dest through srv"""
+ if self.dcc is None:
+ self.dcc = DCC(self.srv, self.dest)
+ self.dcc.treatement = self.hello
+ self.dcc.send_dcc("NEMUBOT###")
+
+ def disconnect(self, reason=""):
+ """Close the connection and remove the bot from network list"""
+ del self.context.network[self.dcc.id]
+ self.dcc.send_dcc("DISCONNECT :%s" % reason)
+ self.dcc.disconnect()
+
+ def hello(self, line):
+ if line == b'NEMUBOT###':
+ self.dcc.treatement = self.treat_msg
+ self.send_cmd("MYTAG %c" % self.my_tag)
+ self.send_cmd("FETCH")
+ elif line != b'Hello ' + self.srv.nick.encode() + b'!':
+ self.disconnect("Sorry, I think you were a bot")
+
+ def treat_msg(self, line, cmd=None):
+ print (line)
+ words = line.split(b' ')
+
+ # Ignore invalid commands
+ if len(words) >= 2:
+ tag = words[0]
+ cmd = words[1]
+ if len(words) > 2:
+ args = shlex.split(line[len(tag) + len(cmd) + 2:].decode())
+ else:
+ args = list()
+
+ # Parse
+ if cmd == b'ACK':
+ if tag in self.tags:
+ del self.tags[tag]
+
+ elif cmd == b'MYTAG' and len(args) > 0:
+ while args[0] == self.my_tag:
+ self.my_tag = random.randint(0,255)
+ self.send_ack(tag)