diff --git a/bin/nemubot b/bin/nemubot
index 84c8514..1d19424 100755
--- a/bin/nemubot
+++ b/bin/nemubot
@@ -1,8 +1,8 @@
-#!/usr/bin/python3
+#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-# Nemubot is a modulable IRC bot, built around XML configuration files.
-# Copyright (C) 2012 Mercier Pierre-Olivier
+# Nemubot is a smart and modulable IM bot.
+# Copyright (C) 2012-2014 nemunaire
#
# 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
@@ -22,14 +22,14 @@ import os
import imp
import traceback
-from nemubot import Bot
+import nemubot
from nemubot import prompt
from nemubot.prompt.builtins import load_file
from nemubot import importer
if __name__ == "__main__":
# Create bot context
- context = Bot(0, "FIXME")
+ context = nemubot.Bot()
# Load the prompt
prmpt = prompt.Prompt()
@@ -50,7 +50,7 @@ if __name__ == "__main__":
else:
load_file(arg, context)
- print ("Nemubot v%s ready, my PID is %i!" % (context.version_txt,
+ print ("Nemubot v%s ready, my PID is %i!" % (nemubot.__version__,
os.getpid()))
while prmpt.run(context):
try:
@@ -63,7 +63,7 @@ if __name__ == "__main__":
# Reload all other modules
bot.reload()
print ("\033[1;32mContext reloaded\033[0m, now in Nemubot %s" %
- context.version_txt)
+ nemubot.__version__)
except:
print ("\033[1;31mUnable to reload the prompt due to errors.\033[0"
"m Fix them before trying to reload the prompt.")
diff --git a/lib/__init__.py b/lib/__init__.py
index fca6cb7..72144f4 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -16,12 +16,14 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from datetime import datetime
-from datetime import timedelta
+from datetime import datetime, timedelta
+from ipaddress import ip_address
+import logging
from queue import Queue
-import threading
-import time
-import re
+import uuid
+
+__version__ = '4.0.dev0'
+__author__ = 'nemunaire'
from nemubot import consumer
from nemubot import event
@@ -31,28 +33,30 @@ from nemubot.IRCServer import IRCServer
from nemubot.DCC import DCC
from nemubot import response
-ID_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
-
class Bot:
- def __init__(self, ip, realname, mp=list()):
- # Bot general informations
- self.version = 4.0
- self.version_txt = '4.0.dev0'
- # Save various informations
- self.ip = ip
- self.realname = realname
- self.ctcp_capabilities = dict()
- self.init_ctcp_capabilities()
+ """Class containing the bot context and ensuring key goals"""
+
+ def __init__(self, ip="127.0.0.1", modules_paths=list(), data_path="./datas/"):
+ """Initialize the bot context
+
+ Keyword arguments:
+ ip -- The external IP of the bot (default: 127.0.0.1)
+ modules_paths -- Paths to all directories where looking for module
+ data_path -- Path to directory where store bot context data
+ """
+
+ # External IP for accessing this bot
+ self.ip = ip_address(ip)
+
+ # Context paths
+ self.modules_paths = modules_paths
+ self.data_path = data_path
# Keep global context: servers and modules
self.servers = dict()
self.modules = dict()
- # Context paths
- self.modules_path = mp
- self.datas_path = './datas/'
-
# Events
self.events = list()
self.event_timer = None
@@ -62,13 +66,16 @@ class Bot:
# Other known bots, making a bots network
self.network = dict()
- self.hooks_cache = dict()
+ self._hooks_cache = dict()
# Messages to be treated
self.cnsr_queue = Queue()
self.cnsr_thrd = list()
self.cnsr_thrd_size = -1
+ # Stop the class thread
+ self.stop = False
+
self.hooks.add_hook("irc_hook",
hooks.Hook(self.treat_prvmsg, "PRIVMSG"),
self)
@@ -80,13 +87,13 @@ class Bot:
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)
+ msg.sender, "NEMUBOT %f" % __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)
+ msg.sender, "VERSION nemubot v%s" % __version__)
def _ctcp_clientinfo(self, srv, msg):
"""Response to CLIENTINFO CTCP message"""
@@ -104,6 +111,8 @@ class Bot:
print ("DCC: unable to connect to %s:%s" % (ip, msg.cmds[4]))
+ # Events methods
+
def add_event(self, evt, eid=None, module_src=None):
"""Register an event and return its identifiant for futur update"""
if eid is None:
@@ -125,7 +134,7 @@ class Bot:
break
self.events.insert(i + 1, evt)
if i == -1:
- self.update_timer()
+ self._update_event_timer()
if len(self.events) <= 0 or self.events[i+1] != evt:
return None
@@ -138,7 +147,7 @@ class Bot:
"""Find and remove an event from list"""
if len(self.events) > 0 and id == self.events[0].id:
self.events.remove(self.events[0])
- self.update_timer()
+ self._update_event_timer()
if module_src is not None:
module_src.REGISTERED_EVENTS.remove(evt.id)
return True
@@ -152,7 +161,7 @@ class Bot:
return True
return False
- def update_timer(self):
+ def _update_event_timer(self):
"""Relaunch the timer to end with the closest event"""
# Reset the timer if this is the first item
if self.event_timer is not None:
@@ -180,9 +189,11 @@ class Bot:
self.cnsr_queue.put_nowait(consumer.EventConsumer(evt))
self.update_consumers()
- self.update_timer()
+ self._update_event_timer()
+ # Server methods
+
def addServer(self, node, nick, owner, realname, ssl=False):
"""Add a new server to the context"""
srv = IRCServer(node, nick, owner, realname, ssl)
@@ -199,6 +210,8 @@ class Bot:
return False
+ # Modules methods
+
def add_module(self, module):
"""Add a module to the context, if already exists, unload the
old one before"""
@@ -218,8 +231,8 @@ class Bot:
if path[len(path)-1] != "/":
path = path + "/"
- if path not in self.modules_path:
- self.modules_path.append(path)
+ if path not in self.modules_paths:
+ self.modules_paths.append(path)
return True
return False
@@ -243,6 +256,9 @@ class Bot:
return True
return False
+
+ # Consumers methods
+
def update_consumers(self):
"""Launch new consumer thread if necessary"""
if self.cnsr_queue.qsize() > self.cnsr_thrd_size:
@@ -292,14 +308,14 @@ class Bot:
# Hooks cache
def create_cache(self, name):
- if name not in self.hooks_cache:
+ if name not in self._hooks_cache:
if isinstance(self.hooks.__dict__[name], list):
- self.hooks_cache[name] = list()
+ self._hooks_cache[name] = list()
# Start by adding locals hooks
for h in self.hooks.__dict__[name]:
tpl = (h, 0, self.hooks.__dict__[name], self.hooks.bot)
- self.hooks_cache[name].append(tpl)
+ self._hooks_cache[name].append(tpl)
# Now, add extermal hooks
level = 0
@@ -309,17 +325,17 @@ class Bot:
if len(self.network[ext].hooks) > level:
lvl_exist = True
for h in self.network[ext].hooks[level].__dict__[name]:
- if h not in self.hooks_cache[name]:
- self.hooks_cache[name].append((h, level + 1,
+ if h not in self._hooks_cache[name]:
+ self._hooks_cache[name].append((h, level + 1,
self.network[ext].hooks[level].__dict__[name], self.network[ext].hooks[level].bot))
level += 1
elif isinstance(self.hooks.__dict__[name], dict):
- self.hooks_cache[name] = dict()
+ self._hooks_cache[name] = dict()
# Start by adding locals hooks
for h in self.hooks.__dict__[name]:
- self.hooks_cache[name][h] = (self.hooks.__dict__[name][h], 0,
+ self._hooks_cache[name][h] = (self.hooks.__dict__[name][h], 0,
self.hooks.__dict__[name],
self.hooks.bot)
@@ -331,14 +347,14 @@ class Bot:
if len(self.network[ext].hooks) > level:
lvl_exist = True
for h in self.network[ext].hooks[level].__dict__[name]:
- if h not in self.hooks_cache[name]:
- self.hooks_cache[name][h] = (self.network[ext].hooks[level].__dict__[name][h], level + 1, self.network[ext].hooks[level].__dict__[name], self.network[ext].hooks[level].bot)
+ if h not in self._hooks_cache[name]:
+ self._hooks_cache[name][h] = (self.network[ext].hooks[level].__dict__[name][h], level + 1, self.network[ext].hooks[level].__dict__[name], self.network[ext].hooks[level].bot)
level += 1
else:
raise Exception(name + " hook type unrecognized")
- return self.hooks_cache[name]
+ return self._hooks_cache[name]
# Treatment
@@ -603,7 +619,7 @@ def _help_msg(sndr, modules, cmd):
return res
def hotswap(bak):
- return Bot(bak.servers, bak.modules, bak.modules_path)
+ return Bot(bak.servers, bak.modules, bak.modules_paths)
def reload():
import imp
diff --git a/lib/hooks.py b/lib/hooks.py
index 28f596a..b560523 100644
--- a/lib/hooks.py
+++ b/lib/hooks.py
@@ -56,8 +56,8 @@ class MessagesHook:
"add_hook function, please fix it in order to be "
"compatible with unload feature")
- if store in self.context.hooks_cache:
- del self.context.hooks_cache[store]
+ if store in self.context._hooks_cache:
+ del self.context._hooks_cache[store]
if not hasattr(self, store):
print ("\033[1;35mWarning:\033[0m unrecognized hook store")
@@ -143,8 +143,8 @@ class MessagesHook:
def del_hook(self, store, hook, module_src=None):
"""Remove a registered hook from a given store"""
- if store in self.context.hooks_cache:
- del self.context.hooks_cache[store]
+ if store in self.context._hooks_cache:
+ del self.context._hooks_cache[store]
if not hasattr(self, store):
print ("Warning: unrecognized hook store type")
diff --git a/lib/importer.py b/lib/importer.py
index 97cb804..126d8f0 100644
--- a/lib/importer.py
+++ b/lib/importer.py
@@ -16,12 +16,14 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from distutils.version import StrictVersion
from importlib.abc import Finder
from importlib.abc import SourceLoader
import imp
import os
import sys
+import nemubot
from nemubot import event
from nemubot import exception
from nemubot.hooks import Hook
@@ -37,7 +39,7 @@ class ModuleFinder(Finder):
#print ("looking for", fullname, "in", path)
# Search only for new nemubot modules (packages init)
if path is None:
- for mpath in self.context.modules_path:
+ for mpath in self.context.modules_paths:
#print ("looking for", fullname, "in", mpath)
if os.path.isfile(mpath + fullname + ".xml"):
return ModuleLoader(self.context, self.prompt, fullname,
@@ -137,7 +139,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 StrictVersion(module.nemubotversion) != StrictVersion(nemubot.__version__):
raise ImportError("Module `%s' is not compatible with this "
"version." % self.name)
diff --git a/setup.py b/setup.py
index 86d53ed..181e318 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ except ImportError:
with open(os.path.join(os.path.dirname(__file__),
'lib',
'__init__.py')) as f:
- version = re.search("self.version_txt = '([^']+)'", f.read()).group(1)
+ version = re.search("__version__ = '([^']+)'", f.read()).group(1)
with open('requirements.txt', 'r') as f:
requires = [x.strip() for x in f if x.strip()]