diff --git a/nemubot/__init__.py b/nemubot/__init__.py
index c4e1df9..8ec9c1a 100644
--- a/nemubot/__init__.py
+++ b/nemubot/__init__.py
@@ -19,7 +19,7 @@ __author__ = 'nemunaire'
from nemubot.modulecontext import ModuleContext
-context = ModuleContext(None, None)
+context = ModuleContext(None, None, None)
def requires_version(min=None, max=None):
diff --git a/nemubot/__main__.py b/nemubot/__main__.py
index c39dd2f..e4acd5c 100644
--- a/nemubot/__main__.py
+++ b/nemubot/__main__.py
@@ -15,6 +15,7 @@
# along with this program. If not, see .
def main():
+ import functools
import os
import signal
import sys
@@ -71,8 +72,8 @@ def main():
args.pidfile = os.path.abspath(os.path.expanduser(args.pidfile))
args.socketfile = os.path.abspath(os.path.expanduser(args.socketfile))
args.logfile = os.path.abspath(os.path.expanduser(args.logfile))
- args.files = [ x for x in map(os.path.abspath, args.files)]
- args.modules_path = [ x for x in map(os.path.abspath, args.modules_path)]
+ args.files = [x for x in map(os.path.abspath, args.files)]
+ args.modules_path = [x for x in map(os.path.abspath, args.modules_path)]
# Check if an instance is already launched
if args.pidfile is not None and os.path.isfile(args.pidfile):
@@ -96,7 +97,7 @@ def main():
with open(args.pidfile, "w+") as f:
f.write(str(os.getpid()))
- # Setup loggin interface
+ # Setup logging interface
import logging
logger = logging.getLogger("nemubot")
logger.setLevel(logging.DEBUG)
@@ -125,51 +126,59 @@ def main():
# Create bot context
from nemubot import datastore
- from nemubot.bot import Bot, sync_act
- context = Bot(modules_paths=modules_paths,
- data_store=datastore.XML(args.data_path),
- verbosity=args.verbose)
+ from nemubot.bot import Bot#, sync_act
+ context = Bot()
if args.no_connect:
context.noautoconnect = True
# Register the hook for futur import
- from nemubot.importer import ModuleFinder
- module_finder = ModuleFinder(context.modules_paths, context.add_module)
- sys.meta_path.append(module_finder)
+ #from nemubot.importer import ModuleFinder
+ #module_finder = ModuleFinder(modules_paths, context.add_module)
+ #sys.meta_path.append(module_finder)
# Load requested configuration files
- for path in args.files:
- if os.path.isfile(path):
- sync_act("loadconf", path)
- else:
- logger.error("%s is not a readable file", path)
+ #for path in args.files:
+ # if os.path.isfile(path):
+ # sync_act("loadconf", path)
+ # else:
+ # logger.error("%s is not a readable file", path)
if args.module:
for module in args.module:
__import__(module)
- # Signals handling
- def sigtermhandler(signum, frame):
- """On SIGTERM and SIGINT, quit nicely"""
- context.quit()
- signal.signal(signal.SIGINT, sigtermhandler)
- signal.signal(signal.SIGTERM, sigtermhandler)
+ # Signals handling ################################################
- def sighuphandler(signum, frame):
- """On SIGHUP, perform a deep reload"""
+ # SIGINT and SIGTERM
+
+ def ask_exit(signame):
+ """On SIGTERM and SIGINT, quit nicely"""
+ context.stop()
+
+ for sig in (signal.SIGINT, signal.SIGTERM):
+ context._loop.add_signal_handler(sig, functools.partial(ask_exit, sig))
+
+
+ # SIGHUP
+
+ def ask_reload():
+ """Perform a deep reload"""
nonlocal context
logger.debug("SIGHUP receive, iniate reload procedure...")
# Reload configuration file
- for path in args.files:
- if os.path.isfile(path):
- sync_act("loadconf", path)
- signal.signal(signal.SIGHUP, sighuphandler)
+ #for path in args.files:
+ # if os.path.isfile(path):
+ # sync_act("loadconf", path)
+ context._loop.add_signal_handler(signal.SIGHUP, ask_reload)
- def sigusr1handler(signum, frame):
- """On SIGHUSR1, display stacktraces"""
+
+ # SIGUSR1
+
+ def ask_debug_info():
+ """Display debug informations and stacktraces"""
import threading, traceback
for threadId, stack in sys._current_frames().items():
thName = "#%d" % threadId
@@ -180,26 +189,26 @@ def main():
logger.debug("########### Thread %s:\n%s",
thName,
"".join(traceback.format_stack(stack)))
- signal.signal(signal.SIGUSR1, sigusr1handler)
+ context._loop.add_signal_handler(signal.SIGUSR1, ask_debug_info)
- if args.socketfile:
- from nemubot.server.socket import UnixSocketListener
- context.add_server(UnixSocketListener(new_server_cb=context.add_server,
- location=args.socketfile,
- name="master_socket"))
+ #if args.socketfile:
+ # from nemubot.server.socket import UnixSocketListener
+ # context.add_server(UnixSocketListener(new_server_cb=context.add_server,
+ # location=args.socketfile,
+ # name="master_socket"))
# context can change when performing an hotswap, always join the latest context
oldcontext = None
while oldcontext != context:
oldcontext = context
- context.start()
- context.join()
+ context.run()
# Wait for consumers
logger.info("Waiting for other threads shuts down...")
if args.debug:
- sigusr1handler(0, None)
+ ask_debug_info()
sys.exit(0)
+
if __name__ == "__main__":
main()
diff --git a/nemubot/bot.py b/nemubot/bot.py
index c8ede40..dc7fa08 100644
--- a/nemubot/bot.py
+++ b/nemubot/bot.py
@@ -14,565 +14,34 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from datetime import datetime, timezone
import logging
-from multiprocessing import JoinableQueue
-import threading
-import select
import sys
-from nemubot import __version__
-from nemubot.consumer import Consumer, EventConsumer, MessageConsumer
-from nemubot import datastore
-import nemubot.hooks
-logger = logging.getLogger("nemubot")
-
-sync_queue = JoinableQueue()
-
-def sync_act(*args):
- sync_queue.put(list(args))
-
-
-class Bot(threading.Thread):
+class Bot:
"""Class containing the bot context and ensuring key goals"""
- def __init__(self, ip="127.0.0.1", modules_paths=list(),
- data_store=datastore.Abstract(), verbosity=0):
- """Initialize the bot context
+ logger = logging.getLogger("nemubot")
- Keyword arguments:
- ip -- The external IP of the bot (default: 127.0.0.1)
- modules_paths -- Paths to all directories where looking for modules
- data_store -- An instance of the nemubot datastore for bot's modules
- verbosity -- verbosity level
+ def __init__(self):
+ """Initialize the bot context
"""
- super().__init__(name="Nemubot main")
+ from nemubot import __version__
+ Bot.logger.info("Initiate nemubot v%s, running on Python %s",
+ __version__, sys.version.split("\n")[0])
- logger.info("Initiate nemubot v%s (running on Python %s.%s.%s)",
- __version__,
- sys.version_info.major, sys.version_info.minor, sys.version_info.micro)
-
- self.verbosity = verbosity
- self.stop = None
-
- # External IP for accessing this bot
- import ipaddress
- self.ip = ipaddress.ip_address(ip)
-
- # Context paths
- self.modules_paths = modules_paths
- self.datastore = data_store
- self.datastore.open()
-
- # Keep global context: servers and modules
- self._poll = select.poll()
- self.servers = dict()
- self.modules = dict()
- self.modules_configuration = dict()
-
- # Events
- self.events = list()
- self.event_timer = None
-
- # Own hooks
- from nemubot.treatment import MessageTreater
- self.treater = MessageTreater()
-
- import re
- def in_ping(msg):
- return msg.respond("pong")
- self.treater.hm.add_hook(nemubot.hooks.Message(in_ping,
- match=lambda msg: re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)",
- msg.message, re.I)),
- "in", "DirectAsk")
-
- def in_echo(msg):
- from nemubot.message import Text
- return Text(msg.nick + ": " + " ".join(msg.args), to=msg.to_response)
- self.treater.hm.add_hook(nemubot.hooks.Command(in_echo, "echo"), "in", "Command")
-
- def _help_msg(msg):
- """Parse and response to help messages"""
- from more import Response
- res = Response(channel=msg.to_response)
- if len(msg.args) >= 1:
- if msg.args[0] in self.modules:
- if hasattr(self.modules[msg.args[0]], "help_full"):
- hlp = self.modules[msg.args[0]].help_full()
- if isinstance(hlp, Response):
- return hlp
- else:
- res.append_message(hlp)
- else:
- res.append_message([str(h) for s,h in self.modules[msg.args[0]].__nemubot_context__.hooks], title="Available commands for module " + msg.args[0])
- elif msg.args[0][0] == "!":
- from nemubot.message.command import Command
- for h in self.treater._in_hooks(Command(msg.args[0][1:])):
- if h.help_usage:
- lp = ["\x03\x02%s%s\x03\x02: %s" % (msg.args[0], (" " + k if k is not None else ""), h.help_usage[k]) for k in h.help_usage]
- jp = h.keywords.help()
- return res.append_message(lp + ([". Moreover, you can provides some optional parameters: "] + jp if len(jp) else []), title="Usage for command %s" % msg.args[0])
- elif h.help:
- return res.append_message("Command %s: %s" % (msg.args[0], h.help))
- else:
- return res.append_message("Sorry, there is currently no help for the command %s. Feel free to make a pull request at https://github.com/nemunaire/nemubot/compare" % msg.args[0])
- res.append_message("Sorry, there is no command %s" % msg.args[0])
- else:
- res.append_message("Sorry, there is no module named %s" % msg.args[0])
- else:
- res.append_message("Pour me demander quelque chose, commencez "
- "votre message par mon nom ; je réagis "
- "également à certaine commandes commençant par"
- " !. Pour plus d'informations, envoyez le "
- "message \"!more\".")
- res.append_message("Mon code source est libre, publié sous "
- "licence AGPL (http://www.gnu.org/licenses/). "
- "Vous pouvez le consulter, le dupliquer, "
- "envoyer des rapports de bogues ou bien "
- "contribuer au projet sur GitHub : "
- "http://github.com/nemunaire/nemubot/")
- res.append_message(title="Pour plus de détails sur un module, "
- "envoyez \"!help nomdumodule\". Voici la liste"
- " de tous les modules disponibles localement",
- message=["\x03\x02%s\x03\x02 (%s)" % (im, self.modules[im].__doc__) for im in self.modules if self.modules[im].__doc__])
- return res
- self.treater.hm.add_hook(nemubot.hooks.Command(_help_msg, "help"), "in", "Command")
-
- from queue import Queue
- # Messages to be treated
- self.cnsr_queue = Queue()
- self.cnsr_thrd = list()
- self.cnsr_thrd_size = -1
+ import asyncio
+ self._loop = asyncio.get_event_loop()
def run(self):
- self._poll.register(sync_queue._reader, select.POLLIN | select.POLLPRI)
+ self._loop.run_forever()
+ self._loop.close()
- logger.info("Starting main loop")
- self.stop = False
- while not self.stop:
- for fd, flag in self._poll.poll():
- # Handle internal socket passing orders
- if fd != sync_queue._reader.fileno() and fd in self.servers:
- srv = self.servers[fd]
- if flag & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
- try:
- srv.exception(flag)
- except:
- logger.exception("Uncatched exception on server exception")
-
- if srv.fileno() > 0:
- if flag & (select.POLLOUT):
- try:
- srv.async_write()
- except:
- logger.exception("Uncatched exception on server write")
-
- if flag & (select.POLLIN | select.POLLPRI):
- try:
- for i in srv.async_read():
- self.receive_message(srv, i)
- except:
- logger.exception("Uncatched exception on server read")
-
- else:
- del self.servers[fd]
-
-
- # Always check the sync queue
- while not sync_queue.empty():
- args = sync_queue.get()
- action = args.pop(0)
-
- if action == "sckt" and len(args) >= 2:
- try:
- if args[0] == "write":
- self._poll.modify(int(args[1]), select.POLLOUT | select.POLLIN | select.POLLPRI)
- elif args[0] == "unwrite":
- self._poll.modify(int(args[1]), select.POLLIN | select.POLLPRI)
-
- elif args[0] == "register":
- self._poll.register(int(args[1]), select.POLLIN | select.POLLPRI)
- elif args[0] == "unregister":
- self._poll.unregister(int(args[1]))
- except:
- logger.exception("Unhandled excpetion during action:")
-
- elif action == "exit":
- self.quit()
-
- elif action == "loadconf":
- for path in args:
- logger.debug("Load configuration from %s", path)
- self.load_file(path)
- logger.info("Configurations successfully loaded")
-
- sync_queue.task_done()
-
-
- # Launch new consumer threads if necessary
- while self.cnsr_queue.qsize() > self.cnsr_thrd_size:
- # Next launch if two more items in queue
- self.cnsr_thrd_size += 2
-
- c = Consumer(self)
- self.cnsr_thrd.append(c)
- c.start()
- logger.info("Ending main loop")
-
-
-
- # Config methods
-
- def load_file(self, filename):
- """Load a configuration file
-
- Arguments:
- filename -- the path to the file to load
- """
-
- import os
-
- # Unexisting file, assume a name was passed, import the module!
- if not os.path.isfile(filename):
- return self.import_module(filename)
-
- from nemubot.channel import Channel
- from nemubot import config
- from nemubot.tools.xmlparser import XMLParser
-
- try:
- p = XMLParser({
- "nemubotconfig": config.Nemubot,
- "server": config.Server,
- "channel": Channel,
- "module": config.Module,
- "include": config.Include,
- })
- config = p.parse_file(filename)
- except:
- logger.exception("Can't load `%s'; this is not a valid nemubot "
- "configuration file." % filename)
- return False
-
- # Preset each server in this file
- for server in config.servers:
- srv = server.server(config)
- # Add the server in the context
- if self.add_server(srv, server.autoconnect):
- logger.info("Server '%s' successfully added." % srv.name)
- else:
- logger.error("Can't add server '%s'." % srv.name)
-
- # Load module and their configuration
- for mod in config.modules:
- self.modules_configuration[mod.name] = mod
- if mod.autoload:
- try:
- __import__(mod.name)
- except:
- logger.exception("Exception occurs when loading module"
- " '%s'", mod.name)
-
-
- # Load files asked by the configuration file
- for load in config.includes:
- self.load_file(load.path)
-
-
- # Events methods
-
- def add_event(self, evt, eid=None, module_src=None):
- """Register an event and return its identifiant for futur update
-
- Return:
- None if the event is not in the queue (eg. if it has been executed during the call) or
- returns the event ID.
-
- Argument:
- evt -- The event object to add
-
- Keyword arguments:
- eid -- The desired event ID (object or string UUID)
- module_src -- The module to which the event is attached to
- """
-
- if hasattr(self, "stop") and self.stop:
- logger.warn("The bot is stopped, can't register new events")
- return
-
- import uuid
-
- # Generate the event id if no given
- if eid is None:
- eid = uuid.uuid1()
-
- # Fill the id field of the event
- if type(eid) is uuid.UUID:
- evt.id = str(eid)
- else:
- # Ok, this is quiet useless...
- try:
- evt.id = str(uuid.UUID(eid))
- except ValueError:
- evt.id = eid
-
- # TODO: mutex here plz
-
- # Add the event in its place
- t = evt.current
- i = 0 # sentinel
- for i in range(0, len(self.events)):
- if self.events[i].current > t:
- break
- self.events.insert(i, evt)
-
- if i == 0:
- # First event changed, reset timer
- self._update_event_timer()
- if len(self.events) <= 0 or self.events[i] != evt:
- # Our event has been executed and removed from queue
- return None
-
- # Register the event in the source module
- if module_src is not None:
- module_src.__nemubot_context__.events.append(evt.id)
- evt.module_src = module_src
-
- logger.info("New event registered in %d position: %s", i, t)
- return evt.id
-
-
- def del_event(self, evt, module_src=None):
- """Find and remove an event from list
-
- Return:
- True if the event has been found and removed, False else
-
- Argument:
- evt -- The ModuleEvent object to remove or just the event identifier
-
- Keyword arguments:
- module_src -- The module to which the event is attached to (ignored if evt is a ModuleEvent)
- """
-
- logger.info("Removing event: %s from %s", evt, module_src)
-
- from nemubot.event import ModuleEvent
- if type(evt) is ModuleEvent:
- id = evt.id
- module_src = evt.module_src
- else:
- id = evt
-
- if len(self.events) > 0 and id == self.events[0].id:
- self.events.remove(self.events[0])
- self._update_event_timer()
- if module_src is not None:
- module_src.__nemubot_context__.events.remove(id)
- return True
-
- for evt in self.events:
- if evt.id == id:
- self.events.remove(evt)
-
- if module_src is not None:
- module_src.__nemubot_context__.events.remove(evt.id)
- return True
- return False
-
-
- def _update_event_timer(self):
- """(Re)launch the timer to end with the closest event"""
-
- # Reset the timer if this is the first item
- if self.event_timer is not None:
- self.event_timer.cancel()
-
- if len(self.events):
- remaining = self.events[0].time_left.total_seconds()
- logger.debug("Update timer: next event in %d seconds", remaining)
- self.event_timer = threading.Timer(remaining if remaining > 0 else 0, self._end_event_timer)
- self.event_timer.start()
-
- else:
- logger.debug("Update timer: no timer left")
-
-
- def _end_event_timer(self):
- """Function called at the end of the event timer"""
-
- while len(self.events) > 0 and datetime.now(timezone.utc) >= self.events[0].current:
- evt = self.events.pop(0)
- self.cnsr_queue.put_nowait(EventConsumer(evt))
-
- self._update_event_timer()
-
-
- # Consumers methods
-
- def add_server(self, srv, autoconnect=True):
- """Add a new server to the context
-
- Arguments:
- srv -- a concrete AbstractServer instance
- autoconnect -- connect after add?
- """
-
- fileno = srv.fileno()
- if fileno not in self.servers:
- self.servers[fileno] = srv
- self.servers[srv.name] = srv
- if autoconnect and not hasattr(self, "noautoconnect"):
- srv.connect()
- return True
-
- else:
- return False
-
-
- # Modules methods
-
- def import_module(self, name):
- """Load a module
-
- Argument:
- name -- name of the module to load
- """
-
- if name in self.modules:
- self.unload_module(name)
-
- __import__(name)
-
-
- def add_module(self, module):
- """Add a module to the context, if already exists, unload the
- old one before"""
- module_name = module.__spec__.name if hasattr(module, "__spec__") else module.__name__
-
- if hasattr(self, "stop") and self.stop:
- logger.warn("The bot is stopped, can't register new modules")
- return
-
- # Check if the module already exists
- if module_name in self.modules:
- self.unload_module(module_name)
-
- # Overwrite print built-in
- def prnt(*args):
- if hasattr(module, "logger"):
- module.logger.info(" ".join([str(s) for s in args]))
- else:
- logger.info("[%s] %s", module_name, " ".join([str(s) for s in args]))
- module.print = prnt
-
- # Create module context
- from nemubot.modulecontext import ModuleContext
- module.__nemubot_context__ = ModuleContext(self, module)
-
- if not hasattr(module, "logger"):
- module.logger = logging.getLogger("nemubot.module." + module_name)
-
- # Replace imported context by real one
- for attr in module.__dict__:
- if attr != "__nemubot_context__" and type(module.__dict__[attr]) == ModuleContext:
- module.__dict__[attr] = module.__nemubot_context__
-
- # Register decorated functions
- import nemubot.hooks
- for s, h in nemubot.hooks.hook.last_registered:
- module.__nemubot_context__.add_hook(h, *s if isinstance(s, list) else s)
- nemubot.hooks.hook.last_registered = []
-
- # Launch the module
- if hasattr(module, "load"):
- try:
- module.load(module.__nemubot_context__)
- except:
- module.__nemubot_context__.unload()
- raise
-
- # Save a reference to the module
- self.modules[module_name] = module
-
-
- def unload_module(self, name):
- """Unload a module"""
- if name in self.modules:
- self.modules[name].print("Unloading module %s" % name)
-
- # Call the user defined unload method
- if hasattr(self.modules[name], "unload"):
- self.modules[name].unload(self)
- self.modules[name].__nemubot_context__.unload()
-
- # Remove from the nemubot dict
- del self.modules[name]
-
- # Remove from the Python dict
- del sys.modules[name]
- for mod in [i for i in sys.modules]:
- if mod[:len(name) + 1] == name + ".":
- logger.debug("Module '%s' also removed from system modules list.", mod)
- del sys.modules[mod]
-
- logger.info("Module `%s' successfully unloaded.", name)
-
- return True
- return False
-
-
- def receive_message(self, srv, msg):
- """Queued the message for treatment
-
- Arguments:
- srv -- The server where the message comes from
- msg -- The message not parsed, as simple as possible
- """
-
- self.cnsr_queue.put_nowait(MessageConsumer(srv, msg))
-
-
- def quit(self):
+ def stop(self):
"""Save and unload modules and disconnect servers"""
- if self.event_timer is not None:
- logger.info("Stop the event timer...")
- self.event_timer.cancel()
-
- logger.info("Save and unload all modules...")
- for mod in self.modules.items():
- self.unload_module(mod)
-
- logger.info("Close all servers connection...")
- for srv in [self.servers[k] for k in self.servers]:
- srv.close()
-
- logger.info("Stop consumers")
- k = self.cnsr_thrd
- for cnsr in k:
- cnsr.stop = True
-
- self.datastore.close()
-
- self.stop = True
- sync_act("end")
- sync_queue.join()
-
-
- # Treatment
-
- def check_rest_times(self, store, hook):
- """Remove from store the hook if it has been executed given time"""
- if hook.times == 0:
- if isinstance(store, dict):
- store[hook.name].remove(hook)
- if len(store) == 0:
- del store[hook.name]
- elif isinstance(store, list):
- store.remove(hook)
+ self._loop.stop()
diff --git a/nemubot/config/server.py b/nemubot/config/server.py
index 14ca9a8..c08b40c 100644
--- a/nemubot/config/server.py
+++ b/nemubot/config/server.py
@@ -33,13 +33,11 @@ class Server:
return True
- def server(self, parent):
+ def server(self, caps=[], **kwargs):
from nemubot.server import factory
- for a in ["nick", "owner", "realname", "encoding"]:
- if a not in self.args:
- self.args[a] = getattr(parent, a)
+ caps += self.caps
- self.caps += parent.caps
+ kwargs.update(self.args)
- return factory(self.uri, caps=self.caps, channels=self.channels, **self.args)
+ return factory(self.uri, caps=caps, channels=self.channels, **kwargs)
diff --git a/nemubot/consumer.py b/nemubot/consumer.py
index 2765aff..37234aa 100644
--- a/nemubot/consumer.py
+++ b/nemubot/consumer.py
@@ -33,6 +33,11 @@ class MessageConsumer:
def run(self, context):
"""Create, parse and treat the message"""
+ # Dereference weakptr
+ srv = self.srv()
+ if srv is None:
+ return
+
from nemubot.bot import Bot
assert isinstance(context, Bot)
@@ -40,7 +45,7 @@ class MessageConsumer:
# Parse message
try:
- for msg in self.srv.parse(self.orig):
+ for msg in srv.parse(self.orig):
msgs.append(msg)
except:
logger.exception("Error occurred during the processing of the %s: "
@@ -52,13 +57,13 @@ class MessageConsumer:
# Identify destination
to_server = None
if isinstance(res, str):
- to_server = self.srv
+ to_server = srv
elif not hasattr(res, "server"):
logger.error("No server defined for response of type %s: %s", type(res).__name__, res)
continue
elif res.server is None:
- to_server = self.srv
- res.server = self.srv.fileno()
+ to_server = srv
+ res.server = srv.fileno()
elif res.server in context.servers:
to_server = context.servers[res.server]
else:
@@ -89,12 +94,7 @@ class EventConsumer:
# Reappend the event in the queue if it has next iteration
if self.evt.next is not None:
- context.add_event(self.evt, eid=self.evt.id)
-
- # Or remove reference of this event
- elif (hasattr(self.evt, "module_src") and
- self.evt.module_src is not None):
- self.evt.module_src.__nemubot_context__.events.remove(self.evt.id)
+ context.add_event(self.evt)
@@ -103,20 +103,23 @@ class Consumer(threading.Thread):
"""Dequeue and exec requested action"""
def __init__(self, context):
- self.context = context
- self.stop = False
super().__init__(name="Nemubot consumer")
+ self.context = context
def run(self):
try:
- while not self.stop:
- stm = self.context.cnsr_queue.get(True, 1)
- stm.run(self.context)
- self.context.cnsr_queue.task_done()
+ while True:
+ context = self.context()
+ if context is None:
+ break
+
+ stm = context.cnsr_queue.get(True, 1)
+ stm.run(context)
+ context.cnsr_queue.task_done()
except queue.Empty:
pass
finally:
- self.context.cnsr_thrd_size -= 2
- self.context.cnsr_thrd.remove(self)
+ if self.context() is not None:
+ self.context().cnsr_thrd.remove(self)
diff --git a/nemubot/modulecontext.py b/nemubot/modulecontext.py
index 1d1b3d0..572b14b 100644
--- a/nemubot/modulecontext.py
+++ b/nemubot/modulecontext.py
@@ -16,19 +16,15 @@
class ModuleContext:
- def __init__(self, context, module):
+ def __init__(self, context, module_name, logger):
"""Initialize the module context
arguments:
context -- the bot context
- module -- the module
+ module_name -- the module name
+ logger -- a logger
"""
- if module is not None:
- module_name = module.__spec__.name if hasattr(module, "__spec__") else module.__name__
- else:
- module_name = ""
-
# Load module configuration if exists
if (context is not None and
module_name in context.modules_configuration):
@@ -60,10 +56,16 @@ class ModuleContext:
def subtreat(msg):
yield from context.treater.treat_msg(msg)
- def add_event(evt, eid=None):
- return context.add_event(evt, eid, module_src=module)
+ def add_event(call=None, **kwargs):
+ evt = context.add_event(call, **kwargs)
+ if evt is not None:
+ self.events.append(evt)
+ return evt
def del_event(evt):
- return context.del_event(evt, module_src=module)
+ if context.del_event(evt):
+ self._clean_events()
+ return True
+ return False
def send_response(server, res):
if server in context.servers:
@@ -72,7 +74,7 @@ class ModuleContext:
else:
return context.servers[server].send_response(res)
else:
- module.logger.error("Try to send a message to the unknown server: %s", server)
+ logger.error("Try to send a message to the unknown server: %s", server)
return False
else: # Used when using outside of nemubot
@@ -88,13 +90,13 @@ class ModuleContext:
self.hooks.remove((triggers, hook))
def subtreat(msg):
return None
- def add_event(evt, eid=None):
- return context.add_event(evt, eid, module_src=module)
+ def add_event(evt):
+ return context.add_event(evt)
def del_event(evt):
return context.del_event(evt, module_src=module)
def send_response(server, res):
- module.logger.info("Send response: %s", res)
+ logger.info("Send response: %s", res)
def save():
context.datastore.save(module_name, self.data)
@@ -121,6 +123,14 @@ class ModuleContext:
return self._data
+ def _clean_events(self):
+ """Look for None weakref in the events list"""
+
+ for i in range(len(self.events), 0, -1):
+ if self.events[i-1]() is None:
+ self.events.remove()
+
+
def unload(self):
"""Perform actions for unloading the module"""
diff --git a/nemubot/server/IRC.py b/nemubot/server/IRC.py
index 89eeab5..6affe55 100644
--- a/nemubot/server/IRC.py
+++ b/nemubot/server/IRC.py
@@ -23,9 +23,9 @@ from nemubot.server.message.IRC import IRC as IRCMessage
from nemubot.server.socket import SocketServer, SecureSocketServer
-class _IRC:
+class IRC():
- """Concrete implementation of a connexion to an IRC server"""
+ """Concrete implementation of a connection to an IRC server"""
def __init__(self, host="localhost", port=6667, owner=None,
nick="nemubot", username=None, password=None,
@@ -273,10 +273,3 @@ class _IRC:
def subparse(self, orig, cnt):
msg = IRCMessage(("@time=%s :%s!user@host.com PRIVMSG %s :%s" % (orig.date.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), orig.frm, ",".join(orig.to), cnt)).encode(self.encoding), self.encoding)
return msg.to_bot_message(self)
-
-
-class IRC(_IRC, SocketServer):
- pass
-
-class IRC_secure(_IRC, SecureSocketServer):
- pass
diff --git a/nemubot/server/__init__.py b/nemubot/server/__init__.py
index 6b583b7..a533491 100644
--- a/nemubot/server/__init__.py
+++ b/nemubot/server/__init__.py
@@ -16,7 +16,7 @@
def factory(uri, ssl=False, **init_args):
- from urllib.parse import urlparse, unquote
+ from urllib.parse import urlparse, unquote, parse_qs
o = urlparse(uri)
srv = None
@@ -45,25 +45,26 @@ def factory(uri, ssl=False, **init_args):
modifiers = o.path.split(",")
target = unquote(modifiers.pop(0)[1:])
- queries = o.query.split("&")
- for q in queries:
- if "=" in q:
- key, val = tuple(q.split("=", 1))
- else:
- key, val = q, ""
- if key == "msg":
- if "on_connect" not in args:
- args["on_connect"] = []
- args["on_connect"].append("PRIVMSG %s :%s" % (target, unquote(val)))
- elif key == "key":
- if "channels" not in args:
- args["channels"] = []
- args["channels"].append((target, unquote(val)))
- elif key == "pass":
- args["password"] = unquote(val)
- elif key == "charset":
- args["encoding"] = unquote(val)
+ # Read query string
+ params = parse_qs(o.query)
+ if "msg" in params:
+ if "on_connect" not in args:
+ args["on_connect"] = []
+ args["on_connect"].append("PRIVMSG %s :%s" % (target, params["msg"]))
+
+ if "key" in params:
+ if "channels" not in args:
+ args["channels"] = []
+ args["channels"].append((target, params["key"]))
+
+ if "pass" in params:
+ args["password"] = params["pass"]
+
+ if "charset" in params:
+ args["encoding"] = params["charset"]
+
+ #
if "channels" not in args and "isnick" not in modifiers:
args["channels"] = [ target ]
diff --git a/nemubot/server/abstract.py b/nemubot/server/abstract.py
index fd25c2d..48c5104 100644
--- a/nemubot/server/abstract.py
+++ b/nemubot/server/abstract.py
@@ -17,7 +17,7 @@
import logging
import queue
-from nemubot.bot import sync_act
+#from nemubot.bot import sync_act
class AbstractServer:
diff --git a/nemubot/server/socket.py b/nemubot/server/socket.py
index 72c0c7b..f5b9c9a 100644
--- a/nemubot/server/socket.py
+++ b/nemubot/server/socket.py
@@ -23,7 +23,7 @@ from nemubot.message.printer.socket import Socket as SocketPrinter
from nemubot.server.abstract import AbstractServer
-class _Socket(AbstractServer):
+class _Socket(asyncio.Protocol):
"""Concrete implementation of a socket connection"""
@@ -34,7 +34,7 @@ class _Socket(AbstractServer):
super().__init__(**kwargs)
self.readbuffer = b''
- self.printer = printer
+ self.printer = printer
# Write