From 26faed014fd2931b1e1b1f0882e7351ca78a4d54 Mon Sep 17 00:00:00 2001 From: nemunaire Date: Thu, 17 Jul 2014 15:27:28 +0200 Subject: [PATCH] Standardize Bot class names --- bin/nemubot | 14 ++++---- lib/__init__.py | 96 ++++++++++++++++++++++++++++--------------------- lib/hooks.py | 8 ++--- lib/importer.py | 6 ++-- setup.py | 2 +- 5 files changed, 72 insertions(+), 54 deletions(-) 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()]