Compare commits
12 commits
master
...
feature/ev
| Author | SHA1 | Date | |
|---|---|---|---|
| e943a55626 | |||
| 47ada614fa | |||
| 27f1b74eef | |||
| 5e9056c7a4 | |||
| fcca0cd5ec | |||
| 19d8ede570 | |||
| 8380e565a4 | |||
| e720d3c99a | |||
| 3ec1173c00 | |||
| 3ecab04f19 | |||
| 93f7061e08 | |||
| 57003f9d03 |
13 changed files with 265 additions and 612 deletions
|
|
@ -1,204 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Nemubot is a smart and modulable IM bot.
|
|
||||||
# Copyright (C) 2012-2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from nemubot.hooks import hook
|
|
||||||
|
|
||||||
nemubotversion = 3.4
|
|
||||||
NODATA = True
|
|
||||||
|
|
||||||
|
|
||||||
def getserver(toks, context, prompt, mandatory=False, **kwargs):
|
|
||||||
"""Choose the server in toks or prompt.
|
|
||||||
This function modify the tokens list passed as argument"""
|
|
||||||
|
|
||||||
if len(toks) > 1 and toks[1] in context.servers:
|
|
||||||
return context.servers[toks.pop(1)]
|
|
||||||
elif not mandatory or prompt.selectedServer:
|
|
||||||
return prompt.selectedServer
|
|
||||||
else:
|
|
||||||
from nemubot.prompt.error import PromptError
|
|
||||||
raise PromptError("Please SELECT a server or give its name in argument.")
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "close")
|
|
||||||
def close(toks, context, **kwargs):
|
|
||||||
"""Disconnect and forget (remove from the servers list) the server"""
|
|
||||||
srv = getserver(toks, context=context, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
if srv.close():
|
|
||||||
del context.servers[srv.id]
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "connect")
|
|
||||||
def connect(toks, **kwargs):
|
|
||||||
"""Make the connexion to a server"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
return not srv.open()
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "disconnect")
|
|
||||||
def disconnect(toks, **kwargs):
|
|
||||||
"""Close the connection to a server"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
return not srv.close()
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "discover")
|
|
||||||
def discover(toks, context, **kwargs):
|
|
||||||
"""Discover a new bot on a server"""
|
|
||||||
srv = getserver(toks, context=context, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
if len(toks) > 1 and "!" in toks[1]:
|
|
||||||
bot = context.add_networkbot(srv, name)
|
|
||||||
return not bot.connect()
|
|
||||||
else:
|
|
||||||
print(" %s is not a valid fullname, for example: "
|
|
||||||
"nemubot!nemubotV3@bot.nemunai.re" % ''.join(toks[1:1]))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "join")
|
|
||||||
@hook("prompt_cmd", "leave")
|
|
||||||
@hook("prompt_cmd", "part")
|
|
||||||
def join(toks, **kwargs):
|
|
||||||
"""Join or leave a channel"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
if len(toks) <= 2:
|
|
||||||
print("%s: not enough arguments." % toks[0])
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if toks[0] == "join":
|
|
||||||
if len(toks) > 2:
|
|
||||||
srv.write("JOIN %s %s" % (toks[1], toks[2]))
|
|
||||||
else:
|
|
||||||
srv.write("JOIN %s" % toks[1])
|
|
||||||
|
|
||||||
elif toks[0] == "leave" or toks[0] == "part":
|
|
||||||
if len(toks) > 2:
|
|
||||||
srv.write("PART %s :%s" % (toks[1], " ".join(toks[2:])))
|
|
||||||
else:
|
|
||||||
srv.write("PART %s" % toks[1])
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "save")
|
|
||||||
def save_mod(toks, context, **kwargs):
|
|
||||||
"""Force save module data"""
|
|
||||||
if len(toks) < 2:
|
|
||||||
print("save: not enough arguments.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
wrn = 0
|
|
||||||
for mod in toks[1:]:
|
|
||||||
if mod in context.modules:
|
|
||||||
context.modules[mod].save()
|
|
||||||
print("save: module `%s´ saved successfully" % mod)
|
|
||||||
else:
|
|
||||||
wrn += 1
|
|
||||||
print("save: no module named `%s´" % mod)
|
|
||||||
return wrn
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "send")
|
|
||||||
def send(toks, **kwargs):
|
|
||||||
"""Send a message on a channel"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
# Check the server is connected
|
|
||||||
if not srv.connected:
|
|
||||||
print ("send: server `%s' not connected." % srv.id)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if len(toks) <= 3:
|
|
||||||
print ("send: not enough arguments.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if toks[1] not in srv.channels:
|
|
||||||
print ("send: channel `%s' not authorized in server `%s'."
|
|
||||||
% (toks[1], srv.id))
|
|
||||||
return 3
|
|
||||||
|
|
||||||
from nemubot.message import Text
|
|
||||||
srv.send_response(Text(" ".join(toks[2:]), server=None,
|
|
||||||
to=[toks[1]]))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "zap")
|
|
||||||
def zap(toks, **kwargs):
|
|
||||||
"""Hard change connexion state"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
srv.connected = not srv.connected
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "top")
|
|
||||||
def top(toks, context, **kwargs):
|
|
||||||
"""Display consumers load information"""
|
|
||||||
print("Queue size: %d, %d thread(s) running (counter: %d)" %
|
|
||||||
(context.cnsr_queue.qsize(),
|
|
||||||
len(context.cnsr_thrd),
|
|
||||||
context.cnsr_thrd_size))
|
|
||||||
if len(context.events) > 0:
|
|
||||||
print("Events registered: %d, next in %d seconds" %
|
|
||||||
(len(context.events),
|
|
||||||
context.events[0].time_left.seconds))
|
|
||||||
else:
|
|
||||||
print("No events registered")
|
|
||||||
|
|
||||||
for th in context.cnsr_thrd:
|
|
||||||
if th.is_alive():
|
|
||||||
print(("#" * 15 + " Stack trace for thread %u " + "#" * 15) %
|
|
||||||
th.ident)
|
|
||||||
traceback.print_stack(sys._current_frames()[th.ident])
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "netstat")
|
|
||||||
def netstat(toks, context, **kwargs):
|
|
||||||
"""Display sockets in use and many other things"""
|
|
||||||
if len(context.network) > 0:
|
|
||||||
print("Distant bots connected: %d:" % len(context.network))
|
|
||||||
for name, bot in context.network.items():
|
|
||||||
print("# %s:" % name)
|
|
||||||
print(" * Declared hooks:")
|
|
||||||
lvl = 0
|
|
||||||
for hlvl in bot.hooks:
|
|
||||||
lvl += 1
|
|
||||||
for hook in (hlvl.all_pre + hlvl.all_post + hlvl.cmd_rgxp +
|
|
||||||
hlvl.cmd_default + hlvl.ask_rgxp +
|
|
||||||
hlvl.ask_default + hlvl.msg_rgxp +
|
|
||||||
hlvl.msg_default):
|
|
||||||
print(" %s- %s" % (' ' * lvl * 2, hook))
|
|
||||||
for kind in ["irc_hook", "cmd_hook", "ask_hook", "msg_hook"]:
|
|
||||||
print(" %s- <%s> %s" % (' ' * lvl * 2, kind,
|
|
||||||
", ".join(hlvl.__dict__[kind].keys())))
|
|
||||||
print(" * My tag: %d" % bot.my_tag)
|
|
||||||
print(" * Tags in use (%d):" % bot.inc_tag)
|
|
||||||
for tag, (cmd, data) in bot.tags.items():
|
|
||||||
print(" - %11s: %s « %s »" % (tag, cmd, data))
|
|
||||||
else:
|
|
||||||
print("No distant bot connected")
|
|
||||||
|
|
@ -20,6 +20,7 @@ __version__ = '4.0.dev3'
|
||||||
__author__ = 'nemunaire'
|
__author__ = 'nemunaire'
|
||||||
|
|
||||||
from nemubot.modulecontext import ModuleContext
|
from nemubot.modulecontext import ModuleContext
|
||||||
|
|
||||||
context = ModuleContext(None, None)
|
context = ModuleContext(None, None)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -40,12 +41,112 @@ def requires_version(min=None, max=None):
|
||||||
"but this is nemubot v%s." % (str(max), __version__))
|
"but this is nemubot v%s." % (str(max), __version__))
|
||||||
|
|
||||||
|
|
||||||
|
def attach(pid, socketfile):
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
|
||||||
|
print("nemubot is already launched with PID %d. Attaching to Unix socket at: %s" % (pid, socketfile))
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
try:
|
||||||
|
sock.connect(socketfile)
|
||||||
|
except socket.error as e:
|
||||||
|
sys.stderr.write(str(e))
|
||||||
|
sys.stderr.write("\n")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
from select import select
|
||||||
|
try:
|
||||||
|
print("Connection established.")
|
||||||
|
while True:
|
||||||
|
rl, wl, xl = select([sys.stdin, sock], [], [])
|
||||||
|
|
||||||
|
if sys.stdin in rl:
|
||||||
|
line = sys.stdin.readline().strip()
|
||||||
|
if line == "exit" or line == "quit":
|
||||||
|
return 0
|
||||||
|
elif line == "reload":
|
||||||
|
import os, signal
|
||||||
|
os.kill(pid, signal.SIGHUP)
|
||||||
|
print("Reload signal sent. Please wait...")
|
||||||
|
|
||||||
|
elif line == "shutdown":
|
||||||
|
import os, signal
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
print("Shutdown signal sent. Please wait...")
|
||||||
|
|
||||||
|
elif line == "kill":
|
||||||
|
import os, signal
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
print("Signal sent...")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
elif line == "stack" or line == "stacks":
|
||||||
|
import os, signal
|
||||||
|
os.kill(pid, signal.SIGUSR1)
|
||||||
|
print("Debug signal sent. Consult logs.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
sock.send(line.encode() + b'\r\n')
|
||||||
|
|
||||||
|
if sock in rl:
|
||||||
|
sys.stdout.write(sock.recv(2048).decode())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def daemonize():
|
||||||
|
"""Detach the running process to run as a daemon
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
pid = os.fork()
|
||||||
|
if pid > 0:
|
||||||
|
sys.exit(0)
|
||||||
|
except OSError as err:
|
||||||
|
sys.stderr.write("Unable to fork: %s\n" % err)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
os.setsid()
|
||||||
|
os.umask(0)
|
||||||
|
os.chdir('/')
|
||||||
|
|
||||||
|
try:
|
||||||
|
pid = os.fork()
|
||||||
|
if pid > 0:
|
||||||
|
sys.exit(0)
|
||||||
|
except OSError as err:
|
||||||
|
sys.stderr.write("Unable to fork: %s\n" % err)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
si = open(os.devnull, 'r')
|
||||||
|
so = open(os.devnull, 'a+')
|
||||||
|
se = open(os.devnull, 'a+')
|
||||||
|
|
||||||
|
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||||
|
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||||
|
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
"""Reload code of all Python modules used by nemubot
|
"""Reload code of all Python modules used by nemubot
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import imp
|
import imp
|
||||||
|
|
||||||
|
import nemubot.bot
|
||||||
|
imp.reload(nemubot.bot)
|
||||||
|
|
||||||
import nemubot.channel
|
import nemubot.channel
|
||||||
imp.reload(nemubot.channel)
|
imp.reload(nemubot.channel)
|
||||||
|
|
||||||
|
|
@ -71,15 +172,14 @@ def reload():
|
||||||
|
|
||||||
nemubot.message.reload()
|
nemubot.message.reload()
|
||||||
|
|
||||||
import nemubot.prompt
|
|
||||||
imp.reload(nemubot.prompt)
|
|
||||||
|
|
||||||
nemubot.prompt.reload()
|
|
||||||
|
|
||||||
import nemubot.server
|
import nemubot.server
|
||||||
rl, wl, xl = nemubot.server._rlist, nemubot.server._wlist, nemubot.server._xlist
|
rl = nemubot.server._rlist
|
||||||
|
wl = nemubot.server._wlist
|
||||||
|
xl = nemubot.server._xlist
|
||||||
imp.reload(nemubot.server)
|
imp.reload(nemubot.server)
|
||||||
nemubot.server._rlist, nemubot.server._wlist, nemubot.server._xlist = rl, wl, xl
|
nemubot.server._rlist = rl
|
||||||
|
nemubot.server._wlist = wl
|
||||||
|
nemubot.server._xlist = xl
|
||||||
|
|
||||||
nemubot.server.reload()
|
nemubot.server.reload()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Parse command line arguments
|
# Parse command line arguments
|
||||||
|
|
@ -38,6 +39,15 @@ def main():
|
||||||
default=["./modules/"],
|
default=["./modules/"],
|
||||||
help="directory to use as modules store")
|
help="directory to use as modules store")
|
||||||
|
|
||||||
|
parser.add_argument("-d", "--debug", action="store_true",
|
||||||
|
help="don't deamonize, keep in foreground")
|
||||||
|
|
||||||
|
parser.add_argument("-P", "--pidfile", default="./nemubot.pid",
|
||||||
|
help="Path to the file where store PID")
|
||||||
|
|
||||||
|
parser.add_argument("-S", "--socketfile", default="./nemubot.sock",
|
||||||
|
help="path where open the socket for internal communication")
|
||||||
|
|
||||||
parser.add_argument("-l", "--logfile", default="./nemubot.log",
|
parser.add_argument("-l", "--logfile", default="./nemubot.log",
|
||||||
help="Path to store logs")
|
help="Path to store logs")
|
||||||
|
|
||||||
|
|
@ -60,10 +70,34 @@ def main():
|
||||||
|
|
||||||
# Resolve relatives paths
|
# Resolve relatives paths
|
||||||
args.data_path = os.path.abspath(os.path.expanduser(args.data_path))
|
args.data_path = os.path.abspath(os.path.expanduser(args.data_path))
|
||||||
|
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.logfile = os.path.abspath(os.path.expanduser(args.logfile))
|
||||||
args.files = [ x for x in map(os.path.abspath, args.files)]
|
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.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):
|
||||||
|
with open(args.pidfile, "r") as f:
|
||||||
|
pid = int(f.readline())
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
from nemubot import attach
|
||||||
|
sys.exit(attach(pid, args.socketfile))
|
||||||
|
|
||||||
|
# Daemonize
|
||||||
|
if not args.debug:
|
||||||
|
from nemubot import daemonize
|
||||||
|
daemonize()
|
||||||
|
|
||||||
|
# Store PID to pidfile
|
||||||
|
if args.pidfile is not None:
|
||||||
|
with open(args.pidfile, "w+") as f:
|
||||||
|
f.write(str(os.getpid()))
|
||||||
|
|
||||||
# Setup loggin interface
|
# Setup loggin interface
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger("nemubot")
|
logger = logging.getLogger("nemubot")
|
||||||
|
|
@ -72,11 +106,12 @@ def main():
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
'%(asctime)s %(name)s %(levelname)s %(message)s')
|
'%(asctime)s %(name)s %(levelname)s %(message)s')
|
||||||
|
|
||||||
ch = logging.StreamHandler()
|
if args.debug:
|
||||||
ch.setFormatter(formatter)
|
ch = logging.StreamHandler()
|
||||||
if args.verbose < 2:
|
ch.setFormatter(formatter)
|
||||||
ch.setLevel(logging.INFO)
|
if args.verbose < 2:
|
||||||
logger.addHandler(ch)
|
ch.setLevel(logging.INFO)
|
||||||
|
logger.addHandler(ch)
|
||||||
|
|
||||||
fh = logging.FileHandler(args.logfile)
|
fh = logging.FileHandler(args.logfile)
|
||||||
fh.setFormatter(formatter)
|
fh.setFormatter(formatter)
|
||||||
|
|
@ -100,13 +135,10 @@ def main():
|
||||||
if args.no_connect:
|
if args.no_connect:
|
||||||
context.noautoconnect = True
|
context.noautoconnect = True
|
||||||
|
|
||||||
# Load the prompt
|
|
||||||
import nemubot.prompt
|
|
||||||
prmpt = nemubot.prompt.Prompt()
|
|
||||||
|
|
||||||
# Register the hook for futur import
|
# Register the hook for futur import
|
||||||
from nemubot.importer import ModuleFinder
|
from nemubot.importer import ModuleFinder
|
||||||
sys.meta_path.append(ModuleFinder(context.modules_paths, context.add_module))
|
module_finder = ModuleFinder(context.modules_paths, context.add_module)
|
||||||
|
sys.meta_path.append(module_finder)
|
||||||
|
|
||||||
# Load requested configuration files
|
# Load requested configuration files
|
||||||
for path in args.files:
|
for path in args.files:
|
||||||
|
|
@ -119,36 +151,62 @@ def main():
|
||||||
for module in args.module:
|
for module in args.module:
|
||||||
__import__(module)
|
__import__(module)
|
||||||
|
|
||||||
print ("Nemubot v%s ready, my PID is %i!" % (nemubot.__version__,
|
# Signals handling
|
||||||
os.getpid()))
|
def sigtermhandler(signum, frame):
|
||||||
while True:
|
"""On SIGTERM and SIGINT, quit nicely"""
|
||||||
from nemubot.prompt.reset import PromptReset
|
context.quit()
|
||||||
try:
|
signal.signal(signal.SIGINT, sigtermhandler)
|
||||||
context.start()
|
signal.signal(signal.SIGTERM, sigtermhandler)
|
||||||
if prmpt.run(context):
|
|
||||||
break
|
|
||||||
except PromptReset as e:
|
|
||||||
if e.type == "quit":
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
def sighuphandler(signum, frame):
|
||||||
import imp
|
"""On SIGHUP, perform a deep reload"""
|
||||||
# Reload all other modules
|
import imp
|
||||||
imp.reload(nemubot)
|
nonlocal nemubot, context, module_finder
|
||||||
imp.reload(nemubot.prompt)
|
|
||||||
nemubot.reload()
|
|
||||||
import nemubot.bot
|
|
||||||
context = nemubot.bot.hotswap(context)
|
|
||||||
prmpt = nemubot.prompt.hotswap(prmpt)
|
|
||||||
print("\033[1;32mContext reloaded\033[0m, now in Nemubot %s" %
|
|
||||||
nemubot.__version__)
|
|
||||||
except:
|
|
||||||
logger.exception("\033[1;31mUnable to reload the prompt due to "
|
|
||||||
"errors.\033[0m Fix them before trying to reload "
|
|
||||||
"the prompt.")
|
|
||||||
|
|
||||||
context.quit()
|
logger.debug("SIGHUP receive, iniate reload procedure...")
|
||||||
print("Waiting for other threads shuts down...")
|
|
||||||
|
# Reload nemubot Python modules
|
||||||
|
imp.reload(nemubot)
|
||||||
|
nemubot.reload()
|
||||||
|
|
||||||
|
# Hotswap context
|
||||||
|
import nemubot.bot
|
||||||
|
context = nemubot.bot.hotswap(context)
|
||||||
|
|
||||||
|
# Reload ModuleFinder
|
||||||
|
sys.meta_path.remove(module_finder)
|
||||||
|
module_finder = ModuleFinder(context.modules_paths, context.add_module)
|
||||||
|
sys.meta_path.append(module_finder)
|
||||||
|
|
||||||
|
# Reload configuration file
|
||||||
|
for path in args.files:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
context.sync_queue.put_nowait(["loadconf", path])
|
||||||
|
signal.signal(signal.SIGHUP, sighuphandler)
|
||||||
|
|
||||||
|
def sigusr1handler(signum, frame):
|
||||||
|
"""On SIGHUSR1, display stacktraces"""
|
||||||
|
import traceback
|
||||||
|
for threadId, stack in sys._current_frames().items():
|
||||||
|
logger.debug("########### Thread %d:\n%s",
|
||||||
|
threadId,
|
||||||
|
"".join(traceback.format_stack(stack)))
|
||||||
|
signal.signal(signal.SIGUSR1, sigusr1handler)
|
||||||
|
|
||||||
|
if args.socketfile:
|
||||||
|
from nemubot.server.socket import SocketListener
|
||||||
|
context.add_server(SocketListener(context.add_server, "master_socket",
|
||||||
|
sock_location=args.socketfile))
|
||||||
|
|
||||||
|
# context can change when performing an hotswap, always join the latest context
|
||||||
|
oldcontext = None
|
||||||
|
while oldcontext != context:
|
||||||
|
oldcontext = context
|
||||||
|
context.start()
|
||||||
|
context.join()
|
||||||
|
|
||||||
|
# Wait for consumers
|
||||||
|
logger.info("Waiting for other threads shuts down...")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ class Bot(threading.Thread):
|
||||||
from select import select
|
from select import select
|
||||||
from nemubot.server import _lock, _rlist, _wlist, _xlist
|
from nemubot.server import _lock, _rlist, _wlist, _xlist
|
||||||
|
|
||||||
|
logger.info("Starting main loop")
|
||||||
self.stop = False
|
self.stop = False
|
||||||
while not self.stop:
|
while not self.stop:
|
||||||
with _lock:
|
with _lock:
|
||||||
|
|
@ -208,6 +209,7 @@ class Bot(threading.Thread):
|
||||||
from nemubot.tools.config import load_file
|
from nemubot.tools.config import load_file
|
||||||
load_file(path, self)
|
load_file(path, self)
|
||||||
self.sync_queue.task_done()
|
self.sync_queue.task_done()
|
||||||
|
logger.info("Ending main loop")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -509,14 +511,16 @@ def hotswap(bak):
|
||||||
bak.stop = True
|
bak.stop = True
|
||||||
if bak.event_timer is not None:
|
if bak.event_timer is not None:
|
||||||
bak.event_timer.cancel()
|
bak.event_timer.cancel()
|
||||||
|
|
||||||
|
# Unload modules
|
||||||
|
for mod in [k for k in bak.modules.keys()]:
|
||||||
|
bak.unload_module(mod)
|
||||||
|
|
||||||
|
# Save datastore
|
||||||
bak.datastore.close()
|
bak.datastore.close()
|
||||||
|
|
||||||
new = Bot(str(bak.ip), bak.modules_paths, bak.datastore)
|
new = Bot(str(bak.ip), bak.modules_paths, bak.datastore)
|
||||||
new.servers = bak.servers
|
new.servers = bak.servers
|
||||||
new.modules = bak.modules
|
|
||||||
new.modules_configuration = bak.modules_configuration
|
|
||||||
new.events = bak.events
|
|
||||||
new.hooks = bak.hooks
|
|
||||||
|
|
||||||
new._update_event_timer()
|
new._update_event_timer()
|
||||||
return new
|
return new
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ class ModuleEvent:
|
||||||
"""Representation of a event initiated by a bot module"""
|
"""Representation of a event initiated by a bot module"""
|
||||||
|
|
||||||
def __init__(self, call=None, call_data=None, func=None, func_data=None,
|
def __init__(self, call=None, call_data=None, func=None, func_data=None,
|
||||||
cmp=None, cmp_data=None, interval=60, offset=0, times=1):
|
cmp=None, cmp_data=None, end_call=None, end_data=None,
|
||||||
|
interval=60, offset=0, times=1, max_attempt=-1):
|
||||||
"""Initialize the event
|
"""Initialize the event
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
|
|
@ -35,9 +35,12 @@ class ModuleEvent:
|
||||||
func_data -- Argument(s) (single or dict) to pass as argument OR if no func, initial data to watch
|
func_data -- Argument(s) (single or dict) to pass as argument OR if no func, initial data to watch
|
||||||
cmp -- Boolean function called to check changes
|
cmp -- Boolean function called to check changes
|
||||||
cmp_data -- Argument(s) (single or dict) to pass as argument OR if no cmp, data compared to previous
|
cmp_data -- Argument(s) (single or dict) to pass as argument OR if no cmp, data compared to previous
|
||||||
|
end_call -- Function called when times or max_attempt reach 0 (mainly for interaction with the event manager)
|
||||||
|
end_data -- Argument(s) (single or dict) to pass as argument
|
||||||
interval -- Time in seconds between each check (default: 60)
|
interval -- Time in seconds between each check (default: 60)
|
||||||
offset -- Time in seconds added to interval before the first check (default: 0)
|
offset -- Time in seconds added to interval before the first check (default: 0)
|
||||||
times -- Number of times the event has to be realized before being removed; -1 for no limit (default: 1)
|
times -- Number of times the event has to be realized before being removed; -1 for no limit (default: 1)
|
||||||
|
max_attempt -- Maximum number of times the event will be checked
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# What have we to check?
|
# What have we to check?
|
||||||
|
|
@ -46,16 +49,17 @@ class ModuleEvent:
|
||||||
|
|
||||||
# How detect a change?
|
# How detect a change?
|
||||||
self.cmp = cmp
|
self.cmp = cmp
|
||||||
self.cmp_data = None
|
|
||||||
if cmp_data is not None:
|
if cmp_data is not None:
|
||||||
self.cmp_data = cmp_data
|
self.cmp_data = cmp_data
|
||||||
elif self.func is not None:
|
elif callable(self.func):
|
||||||
if self.func_data is None:
|
if self.func_data is None:
|
||||||
self.cmp_data = self.func()
|
self.cmp_data = self.func()
|
||||||
elif isinstance(self.func_data, dict):
|
elif isinstance(self.func_data, dict):
|
||||||
self.cmp_data = self.func(**self.func_data)
|
self.cmp_data = self.func(**self.func_data)
|
||||||
else:
|
else:
|
||||||
self.cmp_data = self.func(self.func_data)
|
self.cmp_data = self.func(self.func_data)
|
||||||
|
else:
|
||||||
|
self.cmp_data = None
|
||||||
|
|
||||||
# What should we call when?
|
# What should we call when?
|
||||||
self.call = call
|
self.call = call
|
||||||
|
|
@ -64,46 +68,29 @@ class ModuleEvent:
|
||||||
else:
|
else:
|
||||||
self.call_data = func_data
|
self.call_data = func_data
|
||||||
|
|
||||||
# Store times
|
# Store time between each event
|
||||||
self.offset = timedelta(seconds=offset) # Time to wait before the first check
|
|
||||||
self.interval = timedelta(seconds=interval)
|
self.interval = timedelta(seconds=interval)
|
||||||
self._end = None # Cache
|
|
||||||
|
|
||||||
# How many times do this event?
|
# How many times do this event?
|
||||||
self.times = times
|
self.times = times
|
||||||
|
|
||||||
@property
|
# Cache the time of the next occurence
|
||||||
def current(self):
|
self.next_occur = datetime.now(timezone.utc) + timedelta(seconds=offset) + self.interval
|
||||||
"""Return the date of the near check"""
|
|
||||||
if self.times != 0:
|
|
||||||
if self._end is None:
|
|
||||||
self._end = datetime.now(timezone.utc) + self.offset + self.interval
|
|
||||||
return self._end
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def next(self):
|
|
||||||
"""Return the date of the next check"""
|
|
||||||
if self.times != 0:
|
|
||||||
if self._end is None:
|
|
||||||
return self.current
|
|
||||||
elif self._end < datetime.now(timezone.utc):
|
|
||||||
self._end += self.interval
|
|
||||||
return self._end
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def time_left(self):
|
def time_left(self):
|
||||||
"""Return the time left before/after the near check"""
|
"""Return the time left before/after the near check"""
|
||||||
if self.current is not None:
|
|
||||||
return self.current - datetime.now(timezone.utc)
|
return self.next_occur - datetime.now(timezone.utc)
|
||||||
return 99999 # TODO: 99999 is not a valid time to return
|
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
"""Run a check and realized the event if this is time"""
|
"""Run a check and realized the event if this is time"""
|
||||||
|
|
||||||
|
self.max_attempt -= 1
|
||||||
|
|
||||||
# Get initial data
|
# Get initial data
|
||||||
if self.func is None:
|
if not callable(self.func):
|
||||||
d_init = self.func_data
|
d_init = self.func_data
|
||||||
elif self.func_data is None:
|
elif self.func_data is None:
|
||||||
d_init = self.func()
|
d_init = self.func()
|
||||||
|
|
@ -113,7 +100,7 @@ class ModuleEvent:
|
||||||
d_init = self.func(self.func_data)
|
d_init = self.func(self.func_data)
|
||||||
|
|
||||||
# then compare with current data
|
# then compare with current data
|
||||||
if self.cmp is None:
|
if not callable(self.cmp):
|
||||||
if self.cmp_data is None:
|
if self.cmp_data is None:
|
||||||
rlz = True
|
rlz = True
|
||||||
else:
|
else:
|
||||||
|
|
@ -138,3 +125,19 @@ class ModuleEvent:
|
||||||
self.call(d_init, **self.call_data)
|
self.call(d_init, **self.call_data)
|
||||||
else:
|
else:
|
||||||
self.call(d_init, self.call_data)
|
self.call(d_init, self.call_data)
|
||||||
|
|
||||||
|
# Is it finished?
|
||||||
|
if self.times == 0 or self.max_attempt == 0:
|
||||||
|
if not callable(self.end_call):
|
||||||
|
pass # TODO: log a WARN here
|
||||||
|
else:
|
||||||
|
if self.end_data is None:
|
||||||
|
self.end_call()
|
||||||
|
elif isinstance(self.end_data, dict):
|
||||||
|
self.end_call(**self.end_data)
|
||||||
|
else:
|
||||||
|
self.end_call(self.end_data)
|
||||||
|
|
||||||
|
# Not finished, ready to next one!
|
||||||
|
else:
|
||||||
|
self.next_occur += self.interval
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class ModuleContext:
|
||||||
self.config = ModuleState("module")
|
self.config = ModuleState("module")
|
||||||
|
|
||||||
self.hooks = list()
|
self.hooks = list()
|
||||||
self.events = list()
|
self.events = list() # Un eventManager, qui contient une liste globale et un thread global et quelques méthodes statique, mais chaque événement est exécuté dans son propre contexte :)
|
||||||
self.debug = context.verbosity > 0 if context is not None else False
|
self.debug = context.verbosity > 0 if context is not None else False
|
||||||
|
|
||||||
# Define some callbacks
|
# Define some callbacks
|
||||||
|
|
@ -79,7 +79,9 @@ class ModuleContext:
|
||||||
def subtreat(msg):
|
def subtreat(msg):
|
||||||
yield from context.treater.treat_msg(msg)
|
yield from context.treater.treat_msg(msg)
|
||||||
def add_event(evt, eid=None):
|
def add_event(evt, eid=None):
|
||||||
return context.add_event(evt, eid, module_src=module)
|
eid = context.add_event(evt, eid, module_src=module)
|
||||||
|
self.events.append(eid)
|
||||||
|
return eid
|
||||||
def del_event(evt):
|
def del_event(evt):
|
||||||
return context.del_event(evt, module_src=module)
|
return context.del_event(evt, module_src=module)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Nemubot is a smart and modulable IM bot.
|
|
||||||
# Copyright (C) 2012-2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import shlex
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from nemubot.prompt import builtins
|
|
||||||
|
|
||||||
|
|
||||||
class Prompt:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.selectedServer = None
|
|
||||||
self.lastretcode = 0
|
|
||||||
|
|
||||||
self.HOOKS_CAPS = dict()
|
|
||||||
self.HOOKS_LIST = dict()
|
|
||||||
|
|
||||||
def add_cap_hook(self, name, call, data=None):
|
|
||||||
self.HOOKS_CAPS[name] = lambda t, c: call(t, data=data,
|
|
||||||
context=c, prompt=self)
|
|
||||||
|
|
||||||
def add_list_hook(self, name, call):
|
|
||||||
self.HOOKS_LIST[name] = call
|
|
||||||
|
|
||||||
def lex_cmd(self, line):
|
|
||||||
"""Return an array of tokens
|
|
||||||
|
|
||||||
Argument:
|
|
||||||
line -- the line to lex
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
cmds = shlex.split(line)
|
|
||||||
except:
|
|
||||||
exc_type, exc_value, _ = sys.exc_info()
|
|
||||||
sys.stderr.write(traceback.format_exception_only(exc_type,
|
|
||||||
exc_value)[0])
|
|
||||||
return
|
|
||||||
|
|
||||||
bgn = 0
|
|
||||||
|
|
||||||
# Separate commands (command separator: ;)
|
|
||||||
for i in range(0, len(cmds)):
|
|
||||||
if cmds[i][-1] == ';':
|
|
||||||
if i != bgn:
|
|
||||||
yield cmds[bgn:i]
|
|
||||||
bgn = i + 1
|
|
||||||
|
|
||||||
# Return rest of the command (that not end with a ;)
|
|
||||||
if bgn != len(cmds):
|
|
||||||
yield cmds[bgn:]
|
|
||||||
|
|
||||||
def exec_cmd(self, toks, context):
|
|
||||||
"""Execute the command
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
toks -- lexed tokens to executes
|
|
||||||
context -- current bot context
|
|
||||||
"""
|
|
||||||
|
|
||||||
if toks[0] in builtins.CAPS:
|
|
||||||
self.lastretcode = builtins.CAPS[toks[0]](toks, context, self)
|
|
||||||
elif toks[0] in self.HOOKS_CAPS:
|
|
||||||
self.lastretcode = self.HOOKS_CAPS[toks[0]](toks, context)
|
|
||||||
else:
|
|
||||||
print("Unknown command: `%s'" % toks[0])
|
|
||||||
self.lastretcode = 127
|
|
||||||
|
|
||||||
def getPS1(self):
|
|
||||||
"""Get the PS1 associated to the selected server"""
|
|
||||||
if self.selectedServer is None:
|
|
||||||
return "nemubot"
|
|
||||||
else:
|
|
||||||
return self.selectedServer.id
|
|
||||||
|
|
||||||
def run(self, context):
|
|
||||||
"""Launch the prompt
|
|
||||||
|
|
||||||
Argument:
|
|
||||||
context -- current bot context
|
|
||||||
"""
|
|
||||||
|
|
||||||
from nemubot.prompt.error import PromptError
|
|
||||||
from nemubot.prompt.reset import PromptReset
|
|
||||||
|
|
||||||
while True: # Stopped by exception
|
|
||||||
try:
|
|
||||||
line = input("\033[0;33m%s\033[0;%dm§\033[0m " %
|
|
||||||
(self.getPS1(), 31 if self.lastretcode else 32))
|
|
||||||
cmds = self.lex_cmd(line.strip())
|
|
||||||
for toks in cmds:
|
|
||||||
try:
|
|
||||||
self.exec_cmd(toks, context)
|
|
||||||
except PromptReset:
|
|
||||||
raise
|
|
||||||
except PromptError as e:
|
|
||||||
print(e.message)
|
|
||||||
self.lastretcode = 128
|
|
||||||
except:
|
|
||||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
||||||
traceback.print_exception(exc_type, exc_value,
|
|
||||||
exc_traceback)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("")
|
|
||||||
except EOFError:
|
|
||||||
print("quit")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def hotswap(bak):
|
|
||||||
p = Prompt()
|
|
||||||
p.HOOKS_CAPS = bak.HOOKS_CAPS
|
|
||||||
p.HOOKS_LIST = bak.HOOKS_LIST
|
|
||||||
return p
|
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
|
||||||
import imp
|
|
||||||
|
|
||||||
import nemubot.prompt.builtins
|
|
||||||
imp.reload(nemubot.prompt.builtins)
|
|
||||||
|
|
||||||
import nemubot.prompt.error
|
|
||||||
imp.reload(nemubot.prompt.error)
|
|
||||||
|
|
||||||
import nemubot.prompt.reset
|
|
||||||
imp.reload(nemubot.prompt.reset)
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Nemubot is a smart and modulable IM bot.
|
|
||||||
# Copyright (C) 2012-2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
def end(toks, context, prompt):
|
|
||||||
"""Quit the prompt for reload or exit"""
|
|
||||||
from nemubot.prompt.reset import PromptReset
|
|
||||||
|
|
||||||
if toks[0] == "refresh":
|
|
||||||
raise PromptReset("refresh")
|
|
||||||
elif toks[0] == "reset":
|
|
||||||
raise PromptReset("reset")
|
|
||||||
raise PromptReset("quit")
|
|
||||||
|
|
||||||
|
|
||||||
def liste(toks, context, prompt):
|
|
||||||
"""Show some lists"""
|
|
||||||
if len(toks) > 1:
|
|
||||||
for l in toks[1:]:
|
|
||||||
l = l.lower()
|
|
||||||
if l == "server" or l == "servers":
|
|
||||||
for srv in context.servers.keys():
|
|
||||||
print (" - %s (state: %s) ;" % (srv,
|
|
||||||
"connected" if context.servers[srv].connected else "disconnected"))
|
|
||||||
if len(context.servers) == 0:
|
|
||||||
print (" > No server loaded")
|
|
||||||
|
|
||||||
elif l == "mod" or l == "mods" or l == "module" or l == "modules":
|
|
||||||
for mod in context.modules.keys():
|
|
||||||
print (" - %s ;" % mod)
|
|
||||||
if len(context.modules) == 0:
|
|
||||||
print (" > No module loaded")
|
|
||||||
|
|
||||||
elif l in prompt.HOOKS_LIST:
|
|
||||||
f, d = prompt.HOOKS_LIST[l]
|
|
||||||
f(d, context, prompt)
|
|
||||||
|
|
||||||
else:
|
|
||||||
print (" Unknown list `%s'" % l)
|
|
||||||
return 2
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
print (" Please give a list to show: servers, ...")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def load(toks, context, prompt):
|
|
||||||
"""Load an XML configuration file"""
|
|
||||||
if len(toks) > 1:
|
|
||||||
from nemubot.tools.config import load_file
|
|
||||||
|
|
||||||
for filename in toks[1:]:
|
|
||||||
load_file(filename, context)
|
|
||||||
else:
|
|
||||||
print ("Not enough arguments. `load' takes a filename.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def select(toks, context, prompt):
|
|
||||||
"""Select the current server"""
|
|
||||||
if (len(toks) == 2 and toks[1] != "None" and
|
|
||||||
toks[1] != "nemubot" and toks[1] != "none"):
|
|
||||||
if toks[1] in context.servers:
|
|
||||||
prompt.selectedServer = context.servers[toks[1]]
|
|
||||||
else:
|
|
||||||
print ("select: server `%s' not found." % toks[1])
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
prompt.selectedServer = None
|
|
||||||
|
|
||||||
|
|
||||||
def unload(toks, context, prompt):
|
|
||||||
"""Unload a module"""
|
|
||||||
if len(toks) == 2 and toks[1] == "all":
|
|
||||||
for name in context.modules.keys():
|
|
||||||
context.unload_module(name)
|
|
||||||
elif len(toks) > 1:
|
|
||||||
for name in toks[1:]:
|
|
||||||
if context.unload_module(name):
|
|
||||||
print (" Module `%s' successfully unloaded." % name)
|
|
||||||
else:
|
|
||||||
print (" No module `%s' loaded, can't unload!" % name)
|
|
||||||
return 2
|
|
||||||
else:
|
|
||||||
print ("Not enough arguments. `unload' takes a module name.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def debug(toks, context, prompt):
|
|
||||||
"""Enable/Disable debug mode on a module"""
|
|
||||||
if len(toks) > 1:
|
|
||||||
for name in toks[1:]:
|
|
||||||
if name in context.modules:
|
|
||||||
context.modules[name].DEBUG = not context.modules[name].DEBUG
|
|
||||||
if context.modules[name].DEBUG:
|
|
||||||
print (" Module `%s' now in DEBUG mode." % name)
|
|
||||||
else:
|
|
||||||
print (" Debug for module module `%s' disabled." % name)
|
|
||||||
else:
|
|
||||||
print (" No module `%s' loaded, can't debug!" % name)
|
|
||||||
return 2
|
|
||||||
else:
|
|
||||||
print ("Not enough arguments. `debug' takes a module name.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
# Register build-ins
|
|
||||||
CAPS = {
|
|
||||||
'quit': end, # Disconnect all server and quit
|
|
||||||
'exit': end, # Alias for quit
|
|
||||||
'reset': end, # Reload the prompt
|
|
||||||
'refresh': end, # Reload the prompt but save modules
|
|
||||||
'load': load, # Load a servers or module configuration file
|
|
||||||
'unload': unload, # Unload a module and remove it from the list
|
|
||||||
'select': select, # Select a server
|
|
||||||
'list': liste, # Show lists
|
|
||||||
'debug': debug, # Pass a module in debug mode
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
# Nemubot is a smart and modulable IM bot.
|
|
||||||
# Copyright (C) 2012-2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
class PromptError(Exception):
|
|
||||||
|
|
||||||
def __init__(self, message):
|
|
||||||
super(PromptError, self).__init__(message)
|
|
||||||
self.message = message
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
# Nemubot is a smart and modulable IM bot.
|
|
||||||
# Copyright (C) 2012-2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
class PromptReset(Exception):
|
|
||||||
|
|
||||||
def __init__(self, type):
|
|
||||||
super(PromptReset, self).__init__("Prompt reset asked")
|
|
||||||
self.type = type
|
|
||||||
|
|
@ -115,7 +115,7 @@ class AbstractServer(io.IOBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._sending_queue.put(self.format(message))
|
self._sending_queue.put(self.format(message))
|
||||||
self.logger.debug("Message '%s' appended to Queue", message)
|
self.logger.debug("Message '%s' appended to write queue", message)
|
||||||
if self not in _wlist:
|
if self not in _wlist:
|
||||||
_wlist.append(self)
|
_wlist.append(self)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import nemubot.message as message
|
||||||
from nemubot.message.printer.socket import Socket as SocketPrinter
|
from nemubot.message.printer.socket import Socket as SocketPrinter
|
||||||
from nemubot.server.abstract import AbstractServer
|
from nemubot.server.abstract import AbstractServer
|
||||||
|
|
||||||
|
|
@ -132,6 +133,18 @@ class SocketServer(AbstractServer):
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
|
|
||||||
|
def parse(self, line):
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
line = line.strip().decode()
|
||||||
|
try:
|
||||||
|
args = shlex.split(line)
|
||||||
|
except ValueError:
|
||||||
|
args = line.split(' ')
|
||||||
|
|
||||||
|
yield message.Command(cmd=args[0], args=args[1:], server=self)
|
||||||
|
|
||||||
|
|
||||||
class SocketListener(AbstractServer):
|
class SocketListener(AbstractServer):
|
||||||
|
|
||||||
def __init__(self, new_server_cb, id, sock_location=None, host=None, port=None, ssl=None):
|
def __init__(self, new_server_cb, id, sock_location=None, host=None, port=None, ssl=None):
|
||||||
|
|
|
||||||
1
setup.py
1
setup.py
|
|
@ -66,7 +66,6 @@ setup(
|
||||||
'nemubot.hooks',
|
'nemubot.hooks',
|
||||||
'nemubot.message',
|
'nemubot.message',
|
||||||
'nemubot.message.printer',
|
'nemubot.message.printer',
|
||||||
'nemubot.prompt',
|
|
||||||
'nemubot.server',
|
'nemubot.server',
|
||||||
'nemubot.server.message',
|
'nemubot.server.message',
|
||||||
'nemubot.tools',
|
'nemubot.tools',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue