Optimize imports
This commit is contained in:
parent
2e7a4ad132
commit
e588c30044
29 changed files with 731 additions and 925 deletions
20
bin/nemubot
20
bin/nemubot
|
@ -17,21 +17,12 @@
|
||||||
# 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 argparse
|
|
||||||
import imp
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import nemubot
|
|
||||||
from nemubot import datastore
|
|
||||||
import nemubot.prompt
|
|
||||||
from nemubot.prompt.builtins import load_file
|
|
||||||
from nemubot.prompt.reset import PromptReset
|
|
||||||
from nemubot.importer import ModuleFinder
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Parse command line arguments
|
# Parse command line arguments
|
||||||
|
import argparse
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
parser.add_argument("-a", "--no-connect", action="store_true",
|
parser.add_argument("-a", "--no-connect", action="store_true",
|
||||||
|
@ -59,11 +50,14 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
import nemubot
|
||||||
|
|
||||||
if args.version:
|
if args.version:
|
||||||
print(nemubot.__version__)
|
print(nemubot.__version__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Setup loggin interface
|
# Setup loggin interface
|
||||||
|
import logging
|
||||||
logger = logging.getLogger("nemubot")
|
logger = logging.getLogger("nemubot")
|
||||||
|
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
|
@ -92,6 +86,7 @@ if __name__ == "__main__":
|
||||||
logger.error("%s is not a directory", path)
|
logger.error("%s is not a directory", path)
|
||||||
|
|
||||||
# Create bot context
|
# Create bot context
|
||||||
|
from nemubot import datastore
|
||||||
context = nemubot.Bot(modules_paths=modules_paths, data_store=datastore.XML(args.data_path),
|
context = nemubot.Bot(modules_paths=modules_paths, data_store=datastore.XML(args.data_path),
|
||||||
verbosity=args.verbose)
|
verbosity=args.verbose)
|
||||||
|
|
||||||
|
@ -99,14 +94,17 @@ if __name__ == "__main__":
|
||||||
context.noautoconnect = True
|
context.noautoconnect = True
|
||||||
|
|
||||||
# Load the prompt
|
# Load the prompt
|
||||||
|
import nemubot.prompt
|
||||||
prmpt = nemubot.prompt.Prompt()
|
prmpt = nemubot.prompt.Prompt()
|
||||||
|
|
||||||
# Register the hook for futur import
|
# Register the hook for futur import
|
||||||
|
from nemubot.importer import ModuleFinder
|
||||||
sys.meta_path.append(ModuleFinder(context.modules_paths, context.add_module))
|
sys.meta_path.append(ModuleFinder(context.modules_paths, context.add_module))
|
||||||
|
|
||||||
# Load requested configuration files
|
# Load requested configuration files
|
||||||
for path in args.files:
|
for path in args.files:
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
|
from nemubot.prompt.builtins import load_file
|
||||||
load_file(path, context)
|
load_file(path, context)
|
||||||
else:
|
else:
|
||||||
logger.error("%s is not a readable file", path)
|
logger.error("%s is not a readable file", path)
|
||||||
|
@ -117,6 +115,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
print ("Nemubot v%s ready, my PID is %i!" % (nemubot.__version__,
|
print ("Nemubot v%s ready, my PID is %i!" % (nemubot.__version__,
|
||||||
os.getpid()))
|
os.getpid()))
|
||||||
|
from nemubot.prompt.reset import PromptReset
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
context.start()
|
context.start()
|
||||||
|
@ -127,6 +126,7 @@ if __name__ == "__main__":
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
import imp
|
||||||
# Reload all other modules
|
# Reload all other modules
|
||||||
imp.reload(nemubot)
|
imp.reload(nemubot)
|
||||||
imp.reload(nemubot.prompt)
|
imp.reload(nemubot.prompt)
|
||||||
|
|
|
@ -19,10 +19,7 @@
|
||||||
import traceback
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from nemubot.prompt.error import PromptError
|
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.message import TextMessage
|
|
||||||
from nemubot.networkbot import NetworkBot
|
|
||||||
|
|
||||||
nemubotversion = 3.4
|
nemubotversion = 3.4
|
||||||
NODATA = True
|
NODATA = True
|
||||||
|
@ -37,6 +34,7 @@ def getserver(toks, context, prompt, mandatory=False, **kwargs):
|
||||||
elif not mandatory or prompt.selectedServer:
|
elif not mandatory or prompt.selectedServer:
|
||||||
return prompt.selectedServer
|
return prompt.selectedServer
|
||||||
else:
|
else:
|
||||||
|
from nemubot.prompt.error import PromptError
|
||||||
raise PromptError("Please SELECT a server or give its name in argument.")
|
raise PromptError("Please SELECT a server or give its name in argument.")
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,6 +142,7 @@ def send(toks, **kwargs):
|
||||||
% (toks[1], srv.id))
|
% (toks[1], srv.id))
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
|
from nemubot.message import TextMessage
|
||||||
srv.send_response(TextMessage(" ".join(toks[2:]), server=None,
|
srv.send_response(TextMessage(" ".join(toks[2:]), server=None,
|
||||||
to=[toks[1]]))
|
to=[toks[1]]))
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import re
|
import re
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
from nemubot import context
|
||||||
from nemubot.exception import IRCException
|
from nemubot.exception import IRCException
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
|
|
||||||
|
|
|
@ -16,476 +16,15 @@
|
||||||
# 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/>.
|
||||||
|
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
import imp
|
|
||||||
import ipaddress
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from queue import Queue
|
|
||||||
import re
|
|
||||||
from select import select
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
__version__ = '4.0.dev1'
|
__version__ = '4.0.dev1'
|
||||||
__author__ = 'nemunaire'
|
__author__ = 'nemunaire'
|
||||||
|
|
||||||
from nemubot.consumer import Consumer, EventConsumer, MessageConsumer
|
|
||||||
from nemubot import datastore
|
|
||||||
from nemubot.event import ModuleEvent
|
|
||||||
import nemubot.hooks
|
|
||||||
from nemubot.hooks.messagehook import MessageHook
|
|
||||||
from nemubot.hooks.manager import HooksManager
|
|
||||||
from nemubot.modulecontext import ModuleContext
|
from nemubot.modulecontext import ModuleContext
|
||||||
from nemubot.networkbot import NetworkBot
|
|
||||||
|
|
||||||
context = ModuleContext(None, None)
|
context = ModuleContext(None, None)
|
||||||
|
|
||||||
logger = logging.getLogger("nemubot")
|
|
||||||
|
|
||||||
|
|
||||||
class Bot(threading.Thread):
|
|
||||||
|
|
||||||
"""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
|
|
||||||
|
|
||||||
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_store -- An instance of the nemubot datastore for bot's modules
|
|
||||||
"""
|
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
|
|
||||||
logger.info("Initiate nemubot v%s", __version__)
|
|
||||||
|
|
||||||
self.verbosity = verbosity
|
|
||||||
|
|
||||||
# External IP for accessing this bot
|
|
||||||
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.servers = dict()
|
|
||||||
self.modules = dict()
|
|
||||||
self.modules_configuration = dict()
|
|
||||||
|
|
||||||
# Events
|
|
||||||
self.events = list()
|
|
||||||
self.event_timer = None
|
|
||||||
|
|
||||||
# Own hooks
|
|
||||||
self.hooks = HooksManager()
|
|
||||||
|
|
||||||
def in_ping(msg):
|
|
||||||
if re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", msg.message, re.I) is not None:
|
|
||||||
return msg.respond("pong")
|
|
||||||
self.hooks.add_hook(MessageHook(in_ping), "in", "DirectAsk")
|
|
||||||
|
|
||||||
def _help_msg(msg):
|
|
||||||
"""Parse and response to help messages"""
|
|
||||||
from more import Response
|
|
||||||
res = Response(channel=msg.frm)
|
|
||||||
if len(msg.args) > 1:
|
|
||||||
if msg.args[0] in self.modules:
|
|
||||||
if len(msg.args) > 2:
|
|
||||||
if hasattr(self.modules[msg.args[0]], "HELP_cmd"):
|
|
||||||
res.append_message(self.modules[msg.args[0]].HELP_cmd(msg.args[1]))
|
|
||||||
else:
|
|
||||||
res.append_message("No help for command %s in module %s" % (msg.args[1], msg.args[0]))
|
|
||||||
elif hasattr(self.modules[msg.args[0]], "help_full"):
|
|
||||||
res.append_message(self.modules[msg.args[0]].help_full())
|
|
||||||
else:
|
|
||||||
res.append_message("No help for module %s" % msg.args[0])
|
|
||||||
else:
|
|
||||||
res.append_message("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.hooks.add_hook(MessageHook(_help_msg, "help"), "in", "Command")
|
|
||||||
|
|
||||||
# Other known bots, making a bots network
|
|
||||||
self.network = dict()
|
|
||||||
|
|
||||||
# Messages to be treated
|
|
||||||
self.cnsr_queue = Queue()
|
|
||||||
self.cnsr_thrd = list()
|
|
||||||
self.cnsr_thrd_size = -1
|
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
from nemubot.server import _rlist, _wlist, _xlist
|
|
||||||
|
|
||||||
self.stop = False
|
|
||||||
while not self.stop:
|
|
||||||
try:
|
|
||||||
rl, wl, xl = select(_rlist, _wlist, _xlist, 0.1)
|
|
||||||
except:
|
|
||||||
logger.error("Something went wrong in select")
|
|
||||||
fnd_smth = False
|
|
||||||
# Looking for invalid server
|
|
||||||
for r in _rlist:
|
|
||||||
if not hasattr(r, "fileno") or not isinstance(r.fileno(), int):
|
|
||||||
_rlist.remove(r)
|
|
||||||
logger.error("Found invalid object in _rlist: " + r)
|
|
||||||
fnd_smth = True
|
|
||||||
for w in _wlist:
|
|
||||||
if not hasattr(r, "fileno") or not isinstance(w.fileno(), int):
|
|
||||||
_wlist.remove(w)
|
|
||||||
logger.error("Found invalid object in _wlist: " + w)
|
|
||||||
fnd_smth = True
|
|
||||||
for x in _xlist:
|
|
||||||
if not hasattr(r, "fileno") or not isinstance(x.fileno(), int):
|
|
||||||
_xlist.remove(x)
|
|
||||||
logger.error("Found invalid object in _xlist: " + x)
|
|
||||||
fnd_smth = True
|
|
||||||
if not fnd_smth:
|
|
||||||
logger.exception("Can't continue, sorry")
|
|
||||||
self.stop = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
for x in xl:
|
|
||||||
try:
|
|
||||||
x.exception()
|
|
||||||
except:
|
|
||||||
logger.exception("Uncatched exception on server exception")
|
|
||||||
for w in wl:
|
|
||||||
try:
|
|
||||||
w.write_select()
|
|
||||||
except:
|
|
||||||
logger.exception("Uncatched exception on server write")
|
|
||||||
for r in rl:
|
|
||||||
for i in r.read():
|
|
||||||
try:
|
|
||||||
self.receive_message(r, i)
|
|
||||||
except:
|
|
||||||
logger.exception("Uncatched exception on server read")
|
|
||||||
|
|
||||||
|
|
||||||
# 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
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 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 quite 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: %s -> %s", evt.id, evt)
|
|
||||||
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)
|
|
||||||
|
|
||||||
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) > 0:
|
|
||||||
logger.debug("Update timer: next event in %d seconds",
|
|
||||||
self.events[0].time_left.seconds)
|
|
||||||
if datetime.now(timezone.utc) + timedelta(seconds=5) >= self.events[0].current:
|
|
||||||
while datetime.now(timezone.utc) < self.events[0].current:
|
|
||||||
time.sleep(0.6)
|
|
||||||
self._end_event_timer()
|
|
||||||
else:
|
|
||||||
self.event_timer = threading.Timer(
|
|
||||||
self.events[0].time_left.seconds + 1, 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._launch_consumers()
|
|
||||||
|
|
||||||
self._update_event_timer()
|
|
||||||
|
|
||||||
|
|
||||||
# Consumers methods
|
|
||||||
|
|
||||||
def _launch_consumers(self):
|
|
||||||
"""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()
|
|
||||||
|
|
||||||
|
|
||||||
def add_server(self, srv, autoconnect=True):
|
|
||||||
"""Add a new server to the context
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
srv -- a concrete AbstractServer instance
|
|
||||||
autoconnect -- connect after add?
|
|
||||||
"""
|
|
||||||
|
|
||||||
if srv.id not in self.servers:
|
|
||||||
self.servers[srv.id] = srv
|
|
||||||
if autoconnect and not hasattr(self, "noautoconnect"):
|
|
||||||
srv.open()
|
|
||||||
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)
|
|
||||||
tt = __import__(name)
|
|
||||||
imp.reload(tt)
|
|
||||||
else:
|
|
||||||
__import__(name)
|
|
||||||
|
|
||||||
|
|
||||||
def add_module(self, module):
|
|
||||||
"""Add a module to the context, if already exists, unload the
|
|
||||||
old one before"""
|
|
||||||
# Check if the module already exists
|
|
||||||
if module.__name__ in self.modules:
|
|
||||||
self.unload_module(module.__name__)
|
|
||||||
|
|
||||||
# Overwrite print built-in
|
|
||||||
def prnt(*args):
|
|
||||||
print("[%s]" % module.__name__, *args)
|
|
||||||
if hasattr(module, "logger"):
|
|
||||||
module.logger.info(" ".join(args))
|
|
||||||
module.print = prnt
|
|
||||||
|
|
||||||
# Create module context
|
|
||||||
module.__nemubot_context__ = ModuleContext(self, module)
|
|
||||||
|
|
||||||
# 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
|
|
||||||
for s, h in nemubot.hooks.last_registered:
|
|
||||||
module.__nemubot_context__.add_hook(s, h)
|
|
||||||
nemubot.hooks.last_registered = []
|
|
||||||
|
|
||||||
# Save a reference to the module
|
|
||||||
self.modules[module.__name__] = module
|
|
||||||
|
|
||||||
# Launch the module
|
|
||||||
if hasattr(module, "load"):
|
|
||||||
module.load(module.__nemubot_context__)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def unload_module(self, name):
|
|
||||||
"""Unload a module"""
|
|
||||||
if name in self.modules:
|
|
||||||
self.modules[name].print("Unloading module %s" % name)
|
|
||||||
if hasattr(self.modules[name], "unload"):
|
|
||||||
self.modules[name].unload(self)
|
|
||||||
self.modules[name].__nemubot_context__.unload()
|
|
||||||
# Remove from the dict
|
|
||||||
del self.modules[name]
|
|
||||||
logger.info("Module `%s' successfully unloaded.", name)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def receive_message(self, srv, msg, private=False, data=None):
|
|
||||||
"""Queued the message for treatment"""
|
|
||||||
#print("READ", raw_msg)
|
|
||||||
self.cnsr_queue.put_nowait(MessageConsumer(srv, msg))
|
|
||||||
|
|
||||||
# Launch a new thread if necessary
|
|
||||||
self._launch_consumers()
|
|
||||||
|
|
||||||
|
|
||||||
def add_networkbot(self, srv, dest, dcc=None):
|
|
||||||
"""Append a new bot into the network"""
|
|
||||||
id = srv.id + "/" + dest
|
|
||||||
if id not in self.network:
|
|
||||||
self.network[id] = NetworkBot(self, srv, dest, dcc)
|
|
||||||
return self.network[id]
|
|
||||||
|
|
||||||
def send_networkbot(self, srv, cmd, data=None):
|
|
||||||
for bot in self.network:
|
|
||||||
if self.network[bot].srv == srv:
|
|
||||||
self.network[bot].send_cmd(cmd, data)
|
|
||||||
|
|
||||||
def quit(self):
|
|
||||||
"""Save and unload modules and disconnect servers"""
|
|
||||||
|
|
||||||
self.datastore.close()
|
|
||||||
|
|
||||||
if self.event_timer is not None:
|
|
||||||
logger.info("Stop the event timer...")
|
|
||||||
self.event_timer.cancel()
|
|
||||||
|
|
||||||
logger.info("Save and unload all modules...")
|
|
||||||
k = list(self.modules.keys())
|
|
||||||
for mod in k:
|
|
||||||
self.unload_module(mod)
|
|
||||||
|
|
||||||
logger.info("Close all servers connection...")
|
|
||||||
k = list(self.servers.keys())
|
|
||||||
for srv in k:
|
|
||||||
self.servers[srv].close()
|
|
||||||
|
|
||||||
self.stop = True
|
|
||||||
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
|
||||||
def hotswap(bak):
|
|
||||||
bak.stop = True
|
|
||||||
if bak.event_timer is not None:
|
|
||||||
bak.event_timer.cancel()
|
|
||||||
bak.datastore.close()
|
|
||||||
|
|
||||||
new = Bot(str(bak.ip), bak.modules_paths, bak.datastore)
|
|
||||||
new.servers = bak.servers
|
|
||||||
new.modules = bak.modules
|
|
||||||
new.modules_configuration = bak.modules_configuration
|
|
||||||
new.events = bak.events
|
|
||||||
new.hooks = bak.hooks
|
|
||||||
new.network = bak.network
|
|
||||||
|
|
||||||
new._update_event_timer()
|
|
||||||
return new
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
|
import imp
|
||||||
|
|
||||||
import nemubot.channel
|
import nemubot.channel
|
||||||
imp.reload(nemubot.channel)
|
imp.reload(nemubot.channel)
|
||||||
|
|
||||||
|
|
465
nemubot/bot.py
Normal file
465
nemubot/bot.py
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from nemubot import __version__
|
||||||
|
from nemubot.consumer import Consumer, EventConsumer, MessageConsumer
|
||||||
|
from nemubot import datastore
|
||||||
|
from nemubot.hooks.messagehook import MessageHook
|
||||||
|
from nemubot.modulecontext import ModuleContext
|
||||||
|
|
||||||
|
logger = logging.getLogger("nemubot")
|
||||||
|
|
||||||
|
|
||||||
|
class Bot(threading.Thread):
|
||||||
|
|
||||||
|
"""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
|
||||||
|
|
||||||
|
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_store -- An instance of the nemubot datastore for bot's modules
|
||||||
|
"""
|
||||||
|
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
logger.info("Initiate nemubot v%s", __version__)
|
||||||
|
|
||||||
|
self.verbosity = verbosity
|
||||||
|
|
||||||
|
# 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.servers = dict()
|
||||||
|
self.modules = dict()
|
||||||
|
self.modules_configuration = dict()
|
||||||
|
|
||||||
|
# Events
|
||||||
|
self.events = list()
|
||||||
|
self.event_timer = None
|
||||||
|
|
||||||
|
# Own hooks
|
||||||
|
from nemubot.hooks.manager import HooksManager
|
||||||
|
self.hooks = HooksManager()
|
||||||
|
|
||||||
|
import re
|
||||||
|
def in_ping(msg):
|
||||||
|
if re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", msg.message, re.I) is not None:
|
||||||
|
return msg.respond("pong")
|
||||||
|
self.hooks.add_hook(MessageHook(in_ping), "in", "DirectAsk")
|
||||||
|
|
||||||
|
def _help_msg(msg):
|
||||||
|
"""Parse and response to help messages"""
|
||||||
|
from more import Response
|
||||||
|
res = Response(channel=msg.frm)
|
||||||
|
if len(msg.args) > 1:
|
||||||
|
if msg.args[0] in self.modules:
|
||||||
|
if len(msg.args) > 2:
|
||||||
|
if hasattr(self.modules[msg.args[0]], "HELP_cmd"):
|
||||||
|
res.append_message(self.modules[msg.args[0]].HELP_cmd(msg.args[1]))
|
||||||
|
else:
|
||||||
|
res.append_message("No help for command %s in module %s" % (msg.args[1], msg.args[0]))
|
||||||
|
elif hasattr(self.modules[msg.args[0]], "help_full"):
|
||||||
|
res.append_message(self.modules[msg.args[0]].help_full())
|
||||||
|
else:
|
||||||
|
res.append_message("No help for module %s" % msg.args[0])
|
||||||
|
else:
|
||||||
|
res.append_message("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.hooks.add_hook(MessageHook(_help_msg, "help"), "in", "Command")
|
||||||
|
|
||||||
|
# Messages to be treated
|
||||||
|
from queue import Queue
|
||||||
|
self.cnsr_queue = Queue()
|
||||||
|
self.cnsr_thrd = list()
|
||||||
|
self.cnsr_thrd_size = -1
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
from select import select
|
||||||
|
from nemubot.server import _rlist, _wlist, _xlist
|
||||||
|
|
||||||
|
self.stop = False
|
||||||
|
while not self.stop:
|
||||||
|
try:
|
||||||
|
rl, wl, xl = select(_rlist, _wlist, _xlist, 0.1)
|
||||||
|
except:
|
||||||
|
logger.error("Something went wrong in select")
|
||||||
|
fnd_smth = False
|
||||||
|
# Looking for invalid server
|
||||||
|
for r in _rlist:
|
||||||
|
if not hasattr(r, "fileno") or not isinstance(r.fileno(), int):
|
||||||
|
_rlist.remove(r)
|
||||||
|
logger.error("Found invalid object in _rlist: " + r)
|
||||||
|
fnd_smth = True
|
||||||
|
for w in _wlist:
|
||||||
|
if not hasattr(r, "fileno") or not isinstance(w.fileno(), int):
|
||||||
|
_wlist.remove(w)
|
||||||
|
logger.error("Found invalid object in _wlist: " + w)
|
||||||
|
fnd_smth = True
|
||||||
|
for x in _xlist:
|
||||||
|
if not hasattr(r, "fileno") or not isinstance(x.fileno(), int):
|
||||||
|
_xlist.remove(x)
|
||||||
|
logger.error("Found invalid object in _xlist: " + x)
|
||||||
|
fnd_smth = True
|
||||||
|
if not fnd_smth:
|
||||||
|
logger.exception("Can't continue, sorry")
|
||||||
|
self.stop = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
for x in xl:
|
||||||
|
try:
|
||||||
|
x.exception()
|
||||||
|
except:
|
||||||
|
logger.exception("Uncatched exception on server exception")
|
||||||
|
for w in wl:
|
||||||
|
try:
|
||||||
|
w.write_select()
|
||||||
|
except:
|
||||||
|
logger.exception("Uncatched exception on server write")
|
||||||
|
for r in rl:
|
||||||
|
for i in r.read():
|
||||||
|
try:
|
||||||
|
self.receive_message(r, i)
|
||||||
|
except:
|
||||||
|
logger.exception("Uncatched exception on server read")
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 quite 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: %s -> %s", evt.id, evt)
|
||||||
|
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) > 0:
|
||||||
|
logger.debug("Update timer: next event in %d seconds",
|
||||||
|
self.events[0].time_left.seconds)
|
||||||
|
if datetime.now(timezone.utc) + timedelta(seconds=5) >= self.events[0].current:
|
||||||
|
import time
|
||||||
|
while datetime.now(timezone.utc) < self.events[0].current:
|
||||||
|
time.sleep(0.6)
|
||||||
|
self._end_event_timer()
|
||||||
|
else:
|
||||||
|
self.event_timer = threading.Timer(
|
||||||
|
self.events[0].time_left.seconds + 1, 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._launch_consumers()
|
||||||
|
|
||||||
|
self._update_event_timer()
|
||||||
|
|
||||||
|
|
||||||
|
# Consumers methods
|
||||||
|
|
||||||
|
def _launch_consumers(self):
|
||||||
|
"""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()
|
||||||
|
|
||||||
|
|
||||||
|
def add_server(self, srv, autoconnect=True):
|
||||||
|
"""Add a new server to the context
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
srv -- a concrete AbstractServer instance
|
||||||
|
autoconnect -- connect after add?
|
||||||
|
"""
|
||||||
|
|
||||||
|
if srv.id not in self.servers:
|
||||||
|
self.servers[srv.id] = srv
|
||||||
|
if autoconnect and not hasattr(self, "noautoconnect"):
|
||||||
|
srv.open()
|
||||||
|
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:
|
||||||
|
import imp
|
||||||
|
self.unload_module(name)
|
||||||
|
tt = __import__(name)
|
||||||
|
imp.reload(tt)
|
||||||
|
else:
|
||||||
|
__import__(name)
|
||||||
|
|
||||||
|
|
||||||
|
def add_module(self, module):
|
||||||
|
"""Add a module to the context, if already exists, unload the
|
||||||
|
old one before"""
|
||||||
|
# Check if the module already exists
|
||||||
|
if module.__name__ in self.modules:
|
||||||
|
self.unload_module(module.__name__)
|
||||||
|
|
||||||
|
# Overwrite print built-in
|
||||||
|
def prnt(*args):
|
||||||
|
print("[%s]" % module.__name__, *args)
|
||||||
|
if hasattr(module, "logger"):
|
||||||
|
module.logger.info(" ".join(args))
|
||||||
|
module.print = prnt
|
||||||
|
|
||||||
|
# Create module context
|
||||||
|
module.__nemubot_context__ = ModuleContext(self, module)
|
||||||
|
|
||||||
|
# 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.last_registered:
|
||||||
|
module.__nemubot_context__.add_hook(s, h)
|
||||||
|
nemubot.hooks.last_registered = []
|
||||||
|
|
||||||
|
# Save a reference to the module
|
||||||
|
self.modules[module.__name__] = module
|
||||||
|
|
||||||
|
# Launch the module
|
||||||
|
if hasattr(module, "load"):
|
||||||
|
module.load(module.__nemubot_context__)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def unload_module(self, name):
|
||||||
|
"""Unload a module"""
|
||||||
|
if name in self.modules:
|
||||||
|
self.modules[name].print("Unloading module %s" % name)
|
||||||
|
if hasattr(self.modules[name], "unload"):
|
||||||
|
self.modules[name].unload(self)
|
||||||
|
self.modules[name].__nemubot_context__.unload()
|
||||||
|
# Remove from the dict
|
||||||
|
del self.modules[name]
|
||||||
|
logger.info("Module `%s' successfully unloaded.", name)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def receive_message(self, srv, msg, private=False, data=None):
|
||||||
|
"""Queued the message for treatment"""
|
||||||
|
#print("READ", raw_msg)
|
||||||
|
self.cnsr_queue.put_nowait(MessageConsumer(srv, msg))
|
||||||
|
|
||||||
|
# Launch a new thread if necessary
|
||||||
|
self._launch_consumers()
|
||||||
|
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
"""Save and unload modules and disconnect servers"""
|
||||||
|
|
||||||
|
self.datastore.close()
|
||||||
|
|
||||||
|
if self.event_timer is not None:
|
||||||
|
logger.info("Stop the event timer...")
|
||||||
|
self.event_timer.cancel()
|
||||||
|
|
||||||
|
logger.info("Save and unload all modules...")
|
||||||
|
k = list(self.modules.keys())
|
||||||
|
for mod in k:
|
||||||
|
self.unload_module(mod)
|
||||||
|
|
||||||
|
logger.info("Close all servers connection...")
|
||||||
|
k = list(self.servers.keys())
|
||||||
|
for srv in k:
|
||||||
|
self.servers[srv].close()
|
||||||
|
|
||||||
|
self.stop = True
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
||||||
|
def hotswap(bak):
|
||||||
|
bak.stop = True
|
||||||
|
if bak.event_timer is not None:
|
||||||
|
bak.event_timer.cancel()
|
||||||
|
bak.datastore.close()
|
||||||
|
|
||||||
|
new = Bot(str(bak.ip), bak.modules_paths, bak.datastore)
|
||||||
|
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()
|
||||||
|
return new
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import queue
|
import queue
|
||||||
import re
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
logger = logging.getLogger("nemubot.consumer")
|
logger = logging.getLogger("nemubot.consumer")
|
||||||
|
|
|
@ -14,9 +14,6 @@
|
||||||
# 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/>.
|
||||||
|
|
||||||
from nemubot.tools.xmlparser import module_state
|
|
||||||
|
|
||||||
|
|
||||||
class Abstract:
|
class Abstract:
|
||||||
|
|
||||||
"""Abstract implementation of a module data store, that always return an
|
"""Abstract implementation of a module data store, that always return an
|
||||||
|
@ -38,6 +35,7 @@ class Abstract:
|
||||||
The loaded data
|
The loaded data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from nemubot.tools.xmlparser import module_state
|
||||||
return module_state.ModuleState("nemubotstate")
|
return module_state.ModuleState("nemubotstate")
|
||||||
|
|
||||||
def save(self, module, data):
|
def save(self, module, data):
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from nemubot.datastore.abstract import Abstract
|
from nemubot.datastore.abstract import Abstract
|
||||||
from nemubot.tools.xmlparser import parse_file
|
|
||||||
|
|
||||||
|
|
||||||
class XML(Abstract):
|
class XML(Abstract):
|
||||||
|
@ -66,6 +65,7 @@ class XML(Abstract):
|
||||||
|
|
||||||
data_file = self._get_data_file_path(module)
|
data_file = self._get_data_file_path(module)
|
||||||
if os.path.isfile(data_file):
|
if os.path.isfile(data_file):
|
||||||
|
from nemubot.tools.xmlparser import parse_file
|
||||||
return parse_file(data_file)
|
return parse_file(data_file)
|
||||||
else:
|
else:
|
||||||
return Abstract.load(self, module)
|
return Abstract.load(self, module)
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
# 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/>.
|
||||||
|
|
||||||
from nemubot.message import TextMessage, DirectAsk
|
|
||||||
|
|
||||||
|
|
||||||
class IRCException(Exception):
|
class IRCException(Exception):
|
||||||
|
|
||||||
def __init__(self, message, personnal=True):
|
def __init__(self, message, personnal=True):
|
||||||
|
@ -28,8 +25,11 @@ class IRCException(Exception):
|
||||||
|
|
||||||
def fill_response(self, msg):
|
def fill_response(self, msg):
|
||||||
if self.personnal:
|
if self.personnal:
|
||||||
|
from nemubot.message import DirectAsk
|
||||||
return DirectAsk(msg.frm, self.message,
|
return DirectAsk(msg.frm, self.message,
|
||||||
server=msg.server, to=msg.to_response)
|
server=msg.server, to=msg.to_response)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
from nemubot.message import TextMessage
|
||||||
return TextMessage(self.message,
|
return TextMessage(self.message,
|
||||||
server=msg.server, to=msg.to_response)
|
server=msg.server, to=msg.to_response)
|
||||||
|
|
|
@ -16,11 +16,6 @@
|
||||||
# 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 imp
|
|
||||||
|
|
||||||
from nemubot.exception import IRCException
|
|
||||||
|
|
||||||
|
|
||||||
def call_game(call, *args, **kargs):
|
def call_game(call, *args, **kargs):
|
||||||
"""TODO"""
|
"""TODO"""
|
||||||
l = list()
|
l = list()
|
||||||
|
@ -54,6 +49,8 @@ class AbstractHook:
|
||||||
|
|
||||||
def run(self, data1, *args):
|
def run(self, data1, *args):
|
||||||
"""Run the hook"""
|
"""Run the hook"""
|
||||||
|
|
||||||
|
from nemubot.exception import IRCException
|
||||||
self.times -= 1
|
self.times -= 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -81,6 +78,8 @@ def hook(store, *args, **kargs):
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
|
import imp
|
||||||
|
|
||||||
import nemubot.hooks.manager
|
import nemubot.hooks.manager
|
||||||
imp.reload(nemubot.hooks.manager)
|
imp.reload(nemubot.hooks.manager)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from nemubot.exception import IRCException
|
|
||||||
from nemubot.hooks import AbstractHook
|
from nemubot.hooks import AbstractHook
|
||||||
import nemubot.message
|
import nemubot.message
|
||||||
|
|
||||||
|
|
|
@ -16,18 +16,10 @@
|
||||||
# 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/>.
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from importlib.abc import Finder
|
from importlib.abc import Finder
|
||||||
from importlib.machinery import SourceFileLoader
|
from importlib.machinery import SourceFileLoader
|
||||||
import imp
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
from nemubot import __version__
|
|
||||||
import nemubot.hooks
|
|
||||||
from nemubot.message import TextMessage
|
|
||||||
from nemubot.tools.xmlparser import parse_file, module_state
|
|
||||||
|
|
||||||
logger = logging.getLogger("nemubot.importer")
|
logger = logging.getLogger("nemubot.importer")
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import imp
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractMessage:
|
class AbstractMessage:
|
||||||
|
@ -166,6 +165,8 @@ class OwnerCommand(Command):
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
|
import imp
|
||||||
|
|
||||||
import nemubot.message.visitor
|
import nemubot.message.visitor
|
||||||
imp.reload(nemubot.message.visitor)
|
imp.reload(nemubot.message.visitor)
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,8 @@
|
||||||
# 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 imp
|
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
|
import imp
|
||||||
|
|
||||||
import nemubot.message.printer.IRC
|
import nemubot.message.printer.IRC
|
||||||
imp.reload(nemubot.message.printer.IRC)
|
imp.reload(nemubot.message.printer.IRC)
|
||||||
|
|
|
@ -14,9 +14,6 @@
|
||||||
# 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/>.
|
||||||
|
|
||||||
from nemubot.tools.xmlparser import module_state
|
|
||||||
|
|
||||||
|
|
||||||
def convert_legacy_store(old):
|
def convert_legacy_store(old):
|
||||||
if old == "cmd_hook" or old == "cmd_rgxp" or old == "cmd_default":
|
if old == "cmd_hook" or old == "cmd_rgxp" or old == "cmd_default":
|
||||||
return "in_Command"
|
return "in_Command"
|
||||||
|
@ -84,6 +81,8 @@ class ModuleContext:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
from nemubot.tools.xmlparser import module_state
|
||||||
|
|
||||||
self.data = module_state.ModuleState("nemubotstate")
|
self.data = module_state.ModuleState("nemubotstate")
|
||||||
|
|
||||||
def add_hook(store, hook):
|
def add_hook(store, hook):
|
||||||
|
|
|
@ -1,239 +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 json
|
|
||||||
import random
|
|
||||||
import shlex
|
|
||||||
import urllib.parse
|
|
||||||
import zlib
|
|
||||||
|
|
||||||
from nemubot.server.DCC import DCC
|
|
||||||
import nemubot.hooks as hooks
|
|
||||||
|
|
||||||
class NetworkBot:
|
|
||||||
def __init__(self, context, srv, dest, dcc=None):
|
|
||||||
# General informations
|
|
||||||
self.context = context
|
|
||||||
self.srv = srv
|
|
||||||
self.dest = dest
|
|
||||||
|
|
||||||
self.dcc = dcc # DCC connection to the other bot
|
|
||||||
if self.dcc is not None:
|
|
||||||
self.dcc.closing_event = self.closing_event
|
|
||||||
|
|
||||||
self.hooks = list()
|
|
||||||
self.REGISTERED_HOOKS = list()
|
|
||||||
|
|
||||||
# Tags monitor
|
|
||||||
self.my_tag = random.randint(0,255)
|
|
||||||
self.inc_tag = 0
|
|
||||||
self.tags = dict()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
return self.dcc.id
|
|
||||||
@property
|
|
||||||
def sender(self):
|
|
||||||
if self.dcc is not None:
|
|
||||||
return self.dcc.sender
|
|
||||||
return None
|
|
||||||
@property
|
|
||||||
def nick(self):
|
|
||||||
if self.dcc is not None:
|
|
||||||
return self.dcc.nick
|
|
||||||
return None
|
|
||||||
@property
|
|
||||||
def realname(self):
|
|
||||||
if self.dcc is not None:
|
|
||||||
return self.dcc.realname
|
|
||||||
return None
|
|
||||||
@property
|
|
||||||
def owner(self):
|
|
||||||
return self.srv.owner
|
|
||||||
|
|
||||||
def isDCC(self, someone):
|
|
||||||
"""Abstract implementation"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def accepted_channel(self, chan, sender=None):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def send_cmd(self, cmd, data=None):
|
|
||||||
"""Create a tag and send the command"""
|
|
||||||
# First, define a tag
|
|
||||||
self.inc_tag = (self.inc_tag + 1) % 256
|
|
||||||
while self.inc_tag in self.tags:
|
|
||||||
self.inc_tag = (self.inc_tag + 1) % 256
|
|
||||||
tag = ("%c%c" % (self.my_tag, self.inc_tag)).encode()
|
|
||||||
|
|
||||||
self.tags[tag] = (cmd, data)
|
|
||||||
|
|
||||||
# Send the command with the tag
|
|
||||||
self.send_response_final(tag, cmd)
|
|
||||||
|
|
||||||
def send_response(self, res, tag):
|
|
||||||
self.send_response_final(tag, [res.sender, res.channel, res.nick, res.nomore, res.title, res.more, res.count, json.dumps(res.messages)])
|
|
||||||
|
|
||||||
def msg_treated(self, tag):
|
|
||||||
self.send_ack(tag)
|
|
||||||
|
|
||||||
def send_response_final(self, tag, msg):
|
|
||||||
"""Send a response with a tag"""
|
|
||||||
if isinstance(msg, list):
|
|
||||||
cnt = b''
|
|
||||||
for i in msg:
|
|
||||||
if i is None:
|
|
||||||
cnt += b' ""'
|
|
||||||
elif isinstance(i, int):
|
|
||||||
cnt += (' %d' % i).encode()
|
|
||||||
elif isinstance(i, float):
|
|
||||||
cnt += (' %f' % i).encode()
|
|
||||||
else:
|
|
||||||
cnt += b' "' + urllib.parse.quote(i).encode() + b'"'
|
|
||||||
if False and len(cnt) > 10:
|
|
||||||
cnt = b' Z ' + zlib.compress(cnt)
|
|
||||||
print (cnt)
|
|
||||||
self.dcc.send_dcc_raw(tag + cnt)
|
|
||||||
else:
|
|
||||||
for line in msg.split("\n"):
|
|
||||||
self.dcc.send_dcc_raw(tag + b' ' + line.encode())
|
|
||||||
|
|
||||||
def send_ack(self, tag):
|
|
||||||
"""Acknowledge a command"""
|
|
||||||
if tag in self.tags:
|
|
||||||
del self.tags[tag]
|
|
||||||
self.send_response_final(tag, "ACK")
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
"""Making the connexion with dest through srv"""
|
|
||||||
if self.dcc is None or not self.dcc.connected:
|
|
||||||
self.dcc = DCC(self.srv, self.dest)
|
|
||||||
self.dcc.closing_event = self.closing_event
|
|
||||||
self.dcc.treatement = self.hello
|
|
||||||
self.dcc.send_dcc("NEMUBOT###")
|
|
||||||
else:
|
|
||||||
self.send_cmd("FETCH")
|
|
||||||
|
|
||||||
def disconnect(self, reason=""):
|
|
||||||
"""Close the connection and remove the bot from network list"""
|
|
||||||
del self.context.network[self.dcc.id]
|
|
||||||
self.dcc.send_dcc("DISCONNECT :%s" % reason)
|
|
||||||
self.dcc.disconnect()
|
|
||||||
|
|
||||||
def hello(self, line):
|
|
||||||
if line == b'NEMUBOT###':
|
|
||||||
self.dcc.treatement = self.treat_msg
|
|
||||||
self.send_cmd("MYTAG %c" % self.my_tag)
|
|
||||||
self.send_cmd("FETCH")
|
|
||||||
elif line != b'Hello ' + self.srv.nick.encode() + b'!':
|
|
||||||
self.disconnect("Sorry, I think you were a bot")
|
|
||||||
|
|
||||||
def treat_msg(self, line, cmd=None):
|
|
||||||
words = line.split(b' ')
|
|
||||||
|
|
||||||
# Ignore invalid commands
|
|
||||||
if len(words) >= 2:
|
|
||||||
tag = words[0]
|
|
||||||
|
|
||||||
# Is it a response?
|
|
||||||
if tag in self.tags:
|
|
||||||
# Is it compressed content?
|
|
||||||
if words[1] == b'Z':
|
|
||||||
#print (line)
|
|
||||||
line = zlib.decompress(line[len(tag) + 3:])
|
|
||||||
self.response(line, tag, [urllib.parse.unquote(arg) for arg in shlex.split(line[len(tag) + 1:].decode())], self.tags[tag])
|
|
||||||
else:
|
|
||||||
cmd = words[1]
|
|
||||||
if len(words) > 2:
|
|
||||||
args = shlex.split(line[len(tag) + len(cmd) + 2:].decode())
|
|
||||||
args = [urllib.parse.unquote(arg) for arg in args]
|
|
||||||
else:
|
|
||||||
args = list()
|
|
||||||
#print ("request:", line)
|
|
||||||
self.request(tag, cmd, args)
|
|
||||||
|
|
||||||
def closing_event(self):
|
|
||||||
for lvl in self.hooks:
|
|
||||||
lvl.clear()
|
|
||||||
|
|
||||||
def response(self, line, tag, args, t):
|
|
||||||
(cmds, data) = t
|
|
||||||
#print ("response for", cmds, ":", args)
|
|
||||||
|
|
||||||
if isinstance(cmds, list):
|
|
||||||
cmd = cmds[0]
|
|
||||||
else:
|
|
||||||
cmd = cmds
|
|
||||||
cmds = list(cmd)
|
|
||||||
|
|
||||||
if args[0] == 'ACK': # Acknowledge a command
|
|
||||||
del self.tags[tag]
|
|
||||||
|
|
||||||
elif cmd == "FETCH" and len(args) >= 5:
|
|
||||||
level = int(args[1])
|
|
||||||
while len(self.hooks) <= level:
|
|
||||||
self.hooks.append(hooks.MessagesHook(self.context, self))
|
|
||||||
|
|
||||||
if args[2] == "": args[2] = None
|
|
||||||
if args[3] == "": args[3] = None
|
|
||||||
if args[4] == "": args[4] = list()
|
|
||||||
else: args[4] = args[4].split(',')
|
|
||||||
|
|
||||||
self.hooks[level].add_hook(args[0], hooks.Hook(self.exec_hook, args[2], None, args[3], args[4]), self)
|
|
||||||
|
|
||||||
elif cmd == "HOOK" and len(args) >= 8:
|
|
||||||
# Rebuild the response
|
|
||||||
if args[1] == '': args[1] = None
|
|
||||||
if args[2] == '': args[2] = None
|
|
||||||
if args[3] == '': args[3] = None
|
|
||||||
if args[4] == '': args[4] = None
|
|
||||||
if args[5] == '': args[5] = None
|
|
||||||
if args[6] == '': args[6] = None
|
|
||||||
res = Response(args[0], channel=args[1], nick=args[2], nomore=args[3], title=args[4], more=args[5], count=args[6])
|
|
||||||
for msg in json.loads(args[7]):
|
|
||||||
res.append_message(msg)
|
|
||||||
if len(res.messages) <= 1:
|
|
||||||
res.alone = True
|
|
||||||
self.srv.send_response(res, None)
|
|
||||||
|
|
||||||
|
|
||||||
def request(self, tag, cmd, args):
|
|
||||||
# Parse
|
|
||||||
if cmd == b'MYTAG' and len(args) > 0: # Inform about choosen tag
|
|
||||||
while args[0] == self.my_tag:
|
|
||||||
self.my_tag = random.randint(0,255)
|
|
||||||
self.send_ack(tag)
|
|
||||||
|
|
||||||
elif cmd == b'FETCH': # Get known commands
|
|
||||||
for name in ["cmd_hook", "ask_hook", "msg_hook"]:
|
|
||||||
elts = self.context.create_cache(name)
|
|
||||||
for elt in elts:
|
|
||||||
(hooks, lvl, store, bot) = elts[elt]
|
|
||||||
for h in hooks:
|
|
||||||
self.send_response_final(tag, [name, lvl, elt, h.regexp, ','.join(h.channels)])
|
|
||||||
self.send_ack(tag)
|
|
||||||
|
|
||||||
elif (cmd == b'HOOK' or cmd == b'"HOOK"') and len(args) > 0: # Action requested
|
|
||||||
self.context.receive_message(self, args[0].encode(), True, tag)
|
|
||||||
|
|
||||||
elif (cmd == b'NOMORE' or cmd == b'"NOMORE"') and len(args) > 0: # Reset !more feature
|
|
||||||
if args[0] in self.srv.moremessages:
|
|
||||||
del self.srv.moremessages[args[0]]
|
|
||||||
|
|
||||||
def exec_hook(self, msg):
|
|
||||||
self.send_cmd(["HOOK", msg.raw])
|
|
|
@ -16,15 +16,10 @@
|
||||||
# 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 imp
|
|
||||||
import os
|
|
||||||
import readline
|
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from nemubot.prompt.error import PromptError
|
|
||||||
from nemubot.prompt.reset import PromptReset
|
|
||||||
from nemubot.prompt import builtins
|
from nemubot.prompt import builtins
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,6 +97,9 @@ class Prompt:
|
||||||
context -- current bot context
|
context -- current bot context
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from nemubot.prompt.error import PromptError
|
||||||
|
from nemubot.prompt.reset import PromptReset
|
||||||
|
|
||||||
while True: # Stopped by exception
|
while True: # Stopped by exception
|
||||||
try:
|
try:
|
||||||
line = input("\033[0;33m%s\033[0;%dm§\033[0m " %
|
line = input("\033[0;33m%s\033[0;%dm§\033[0m " %
|
||||||
|
@ -134,6 +132,8 @@ def hotswap(bak):
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
|
import imp
|
||||||
|
|
||||||
import nemubot.prompt.builtins
|
import nemubot.prompt.builtins
|
||||||
imp.reload(nemubot.prompt.builtins)
|
imp.reload(nemubot.prompt.builtins)
|
||||||
|
|
||||||
|
|
|
@ -16,16 +16,10 @@
|
||||||
# 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 logging
|
|
||||||
|
|
||||||
from nemubot.prompt.reset import PromptReset
|
|
||||||
from nemubot.tools.config import load_file
|
|
||||||
|
|
||||||
logger = logging.getLogger("nemubot.prompt.builtins")
|
|
||||||
|
|
||||||
|
|
||||||
def end(toks, context, prompt):
|
def end(toks, context, prompt):
|
||||||
"""Quit the prompt for reload or exit"""
|
"""Quit the prompt for reload or exit"""
|
||||||
|
from nemubot.prompt.reset import PromptReset
|
||||||
|
|
||||||
if toks[0] == "refresh":
|
if toks[0] == "refresh":
|
||||||
raise PromptReset("refresh")
|
raise PromptReset("refresh")
|
||||||
elif toks[0] == "reset":
|
elif toks[0] == "reset":
|
||||||
|
@ -67,6 +61,8 @@ def liste(toks, context, prompt):
|
||||||
def load(toks, context, prompt):
|
def load(toks, context, prompt):
|
||||||
"""Load an XML configuration file"""
|
"""Load an XML configuration file"""
|
||||||
if len(toks) > 1:
|
if len(toks) > 1:
|
||||||
|
from nemubot.tools.config import load_file
|
||||||
|
|
||||||
for filename in toks[1:]:
|
for filename in toks[1:]:
|
||||||
load_file(filename, context)
|
load_file(filename, context)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -16,11 +16,8 @@
|
||||||
# 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 calendar
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import ipaddress
|
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from nemubot.channel import Channel
|
from nemubot.channel import Channel
|
||||||
|
@ -82,6 +79,7 @@ class IRC(SocketServer):
|
||||||
def _ctcp_dcc(msg, cmds):
|
def _ctcp_dcc(msg, cmds):
|
||||||
"""Response to DCC CTCP message"""
|
"""Response to DCC CTCP message"""
|
||||||
try:
|
try:
|
||||||
|
import ipaddress
|
||||||
ip = ipaddress.ip_address(int(cmds[3]))
|
ip = ipaddress.ip_address(int(cmds[3]))
|
||||||
port = int(cmds[4])
|
port = int(cmds[4])
|
||||||
conn = DCC(srv, msg.sender)
|
conn = DCC(srv, msg.sender)
|
||||||
|
@ -333,6 +331,7 @@ class IRCMessage:
|
||||||
|
|
||||||
# Treat special tags
|
# Treat special tags
|
||||||
if key == "time":
|
if key == "time":
|
||||||
|
import calendar, time
|
||||||
value = datetime.fromtimestamp(calendar.timegm(time.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")), timezone.utc)
|
value = datetime.fromtimestamp(calendar.timegm(time.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")), timezone.utc)
|
||||||
|
|
||||||
# Store tag
|
# Store tag
|
||||||
|
|
|
@ -16,11 +16,6 @@
|
||||||
# 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 io
|
|
||||||
import imp
|
|
||||||
import logging
|
|
||||||
import socket
|
|
||||||
import queue
|
|
||||||
|
|
||||||
# Lists for select
|
# Lists for select
|
||||||
_rlist = []
|
_rlist = []
|
||||||
|
@ -28,132 +23,12 @@ _wlist = []
|
||||||
_xlist = []
|
_xlist = []
|
||||||
|
|
||||||
|
|
||||||
# Extends from IOBase in order to be compatible with select function
|
|
||||||
class AbstractServer(io.IOBase):
|
|
||||||
|
|
||||||
"""An abstract server: handle communication with an IM server"""
|
|
||||||
|
|
||||||
def __init__(self, send_callback=None):
|
|
||||||
"""Initialize an abstract server
|
|
||||||
|
|
||||||
Keyword argument:
|
|
||||||
send_callback -- Callback when developper want to send a message
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not hasattr(self, "id"):
|
|
||||||
raise Exception("No id defined for this server. Please set one!")
|
|
||||||
|
|
||||||
self.logger = logging.getLogger("nemubot.server." + self.id)
|
|
||||||
self._sending_queue = queue.Queue()
|
|
||||||
if send_callback is not None:
|
|
||||||
self._send_callback = send_callback
|
|
||||||
else:
|
|
||||||
self._send_callback = self._write_select
|
|
||||||
|
|
||||||
|
|
||||||
# Open/close
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.open()
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
"""Generic open function that register the server un _rlist in case
|
|
||||||
of successful _open"""
|
|
||||||
self.logger.info("Opening connection to %s", self.id)
|
|
||||||
if not hasattr(self, "_open") or self._open():
|
|
||||||
_rlist.append(self)
|
|
||||||
_xlist.append(self)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Generic close function that register the server un _{r,w,x}list in
|
|
||||||
case of successful _close"""
|
|
||||||
self.logger.info("Closing connection to %s", self.id)
|
|
||||||
if not hasattr(self, "_close") or self._close():
|
|
||||||
if self in _rlist:
|
|
||||||
_rlist.remove(self)
|
|
||||||
if self in _wlist:
|
|
||||||
_wlist.remove(self)
|
|
||||||
if self in _xlist:
|
|
||||||
_xlist.remove(self)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# Writes
|
|
||||||
|
|
||||||
def write(self, message):
|
|
||||||
"""Asynchronymously send a message to the server using send_callback
|
|
||||||
|
|
||||||
Argument:
|
|
||||||
message -- message to send
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._send_callback(message)
|
|
||||||
|
|
||||||
|
|
||||||
def write_select(self):
|
|
||||||
"""Internal function used by the select function"""
|
|
||||||
try:
|
|
||||||
_wlist.remove(self)
|
|
||||||
while not self._sending_queue.empty():
|
|
||||||
self._write(self._sending_queue.get_nowait())
|
|
||||||
self._sending_queue.task_done()
|
|
||||||
|
|
||||||
except queue.Empty:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _write_select(self, message):
|
|
||||||
"""Send a message to the server safely through select
|
|
||||||
|
|
||||||
Argument:
|
|
||||||
message -- message to send
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._sending_queue.put(self.format(message))
|
|
||||||
self.logger.debug("Message '%s' appended to Queue", message)
|
|
||||||
if self not in _wlist:
|
|
||||||
_wlist.append(self)
|
|
||||||
|
|
||||||
|
|
||||||
def send_response(self, response):
|
|
||||||
"""Send a formated Message class
|
|
||||||
|
|
||||||
Argument:
|
|
||||||
response -- message to send
|
|
||||||
"""
|
|
||||||
|
|
||||||
if response is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
elif isinstance(response, list):
|
|
||||||
for r in response:
|
|
||||||
self.send_response(r)
|
|
||||||
|
|
||||||
else:
|
|
||||||
vprnt = self.printer()
|
|
||||||
response.accept(vprnt)
|
|
||||||
self.write(vprnt.pp)
|
|
||||||
|
|
||||||
|
|
||||||
# Exceptions
|
|
||||||
|
|
||||||
def exception(self):
|
|
||||||
"""Exception occurs in fd"""
|
|
||||||
self.logger.warning("Unhandle file descriptor exception on server %s",
|
|
||||||
self.id)
|
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
|
import imp
|
||||||
|
|
||||||
|
import nemubot.server.abstract
|
||||||
|
imp.reload(nemubot.server.abstract)
|
||||||
|
|
||||||
import nemubot.server.socket
|
import nemubot.server.socket
|
||||||
imp.reload(nemubot.server.socket)
|
imp.reload(nemubot.server.socket)
|
||||||
|
|
||||||
|
|
147
nemubot/server/abstract.py
Normal file
147
nemubot/server/abstract.py
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
# -*- 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 io
|
||||||
|
import logging
|
||||||
|
import queue
|
||||||
|
|
||||||
|
from nemubot.server import _rlist, _wlist, _xlist
|
||||||
|
|
||||||
|
# Extends from IOBase in order to be compatible with select function
|
||||||
|
class AbstractServer(io.IOBase):
|
||||||
|
|
||||||
|
"""An abstract server: handle communication with an IM server"""
|
||||||
|
|
||||||
|
def __init__(self, send_callback=None):
|
||||||
|
"""Initialize an abstract server
|
||||||
|
|
||||||
|
Keyword argument:
|
||||||
|
send_callback -- Callback when developper want to send a message
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not hasattr(self, "id"):
|
||||||
|
raise Exception("No id defined for this server. Please set one!")
|
||||||
|
|
||||||
|
self.logger = logging.getLogger("nemubot.server." + self.id)
|
||||||
|
self._sending_queue = queue.Queue()
|
||||||
|
if send_callback is not None:
|
||||||
|
self._send_callback = send_callback
|
||||||
|
else:
|
||||||
|
self._send_callback = self._write_select
|
||||||
|
|
||||||
|
|
||||||
|
# Open/close
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.open()
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
"""Generic open function that register the server un _rlist in case
|
||||||
|
of successful _open"""
|
||||||
|
self.logger.info("Opening connection to %s", self.id)
|
||||||
|
if not hasattr(self, "_open") or self._open():
|
||||||
|
_rlist.append(self)
|
||||||
|
_xlist.append(self)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Generic close function that register the server un _{r,w,x}list in
|
||||||
|
case of successful _close"""
|
||||||
|
self.logger.info("Closing connection to %s", self.id)
|
||||||
|
if not hasattr(self, "_close") or self._close():
|
||||||
|
if self in _rlist:
|
||||||
|
_rlist.remove(self)
|
||||||
|
if self in _wlist:
|
||||||
|
_wlist.remove(self)
|
||||||
|
if self in _xlist:
|
||||||
|
_xlist.remove(self)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Writes
|
||||||
|
|
||||||
|
def write(self, message):
|
||||||
|
"""Asynchronymously send a message to the server using send_callback
|
||||||
|
|
||||||
|
Argument:
|
||||||
|
message -- message to send
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._send_callback(message)
|
||||||
|
|
||||||
|
|
||||||
|
def write_select(self):
|
||||||
|
"""Internal function used by the select function"""
|
||||||
|
try:
|
||||||
|
_wlist.remove(self)
|
||||||
|
while not self._sending_queue.empty():
|
||||||
|
self._write(self._sending_queue.get_nowait())
|
||||||
|
self._sending_queue.task_done()
|
||||||
|
|
||||||
|
except queue.Empty:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _write_select(self, message):
|
||||||
|
"""Send a message to the server safely through select
|
||||||
|
|
||||||
|
Argument:
|
||||||
|
message -- message to send
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._sending_queue.put(self.format(message))
|
||||||
|
self.logger.debug("Message '%s' appended to Queue", message)
|
||||||
|
if self not in _wlist:
|
||||||
|
_wlist.append(self)
|
||||||
|
|
||||||
|
|
||||||
|
def send_response(self, response):
|
||||||
|
"""Send a formated Message class
|
||||||
|
|
||||||
|
Argument:
|
||||||
|
response -- message to send
|
||||||
|
"""
|
||||||
|
|
||||||
|
if response is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
elif isinstance(response, list):
|
||||||
|
for r in response:
|
||||||
|
self.send_response(r)
|
||||||
|
|
||||||
|
else:
|
||||||
|
vprnt = self.printer()
|
||||||
|
response.accept(vprnt)
|
||||||
|
self.write(vprnt.pp)
|
||||||
|
|
||||||
|
|
||||||
|
# Exceptions
|
||||||
|
|
||||||
|
def exception(self):
|
||||||
|
"""Exception occurs in fd"""
|
||||||
|
self.logger.warning("Unhandle file descriptor exception on server %s",
|
||||||
|
self.id)
|
|
@ -16,10 +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 ssl
|
from nemubot.server.abstract import AbstractServer
|
||||||
import socket
|
|
||||||
|
|
||||||
from nemubot.server import AbstractServer
|
|
||||||
|
|
||||||
|
|
||||||
class SocketServer(AbstractServer):
|
class SocketServer(AbstractServer):
|
||||||
|
@ -49,11 +46,14 @@ class SocketServer(AbstractServer):
|
||||||
# Open/close
|
# Open/close
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
# Create the socket
|
# Create the socket
|
||||||
self.socket = socket.socket()
|
self.socket = socket.socket()
|
||||||
|
|
||||||
# Wrap the socket for SSL
|
# Wrap the socket for SSL
|
||||||
if self.ssl:
|
if self.ssl:
|
||||||
|
import ssl
|
||||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
self.socket = ctx.wrap_socket(self.socket)
|
self.socket = ctx.wrap_socket(self.socket)
|
||||||
|
|
||||||
|
@ -71,6 +71,8 @@ class SocketServer(AbstractServer):
|
||||||
|
|
||||||
|
|
||||||
def _close(self):
|
def _close(self):
|
||||||
|
import socket
|
||||||
|
|
||||||
self._sending_queue.join()
|
self._sending_queue.join()
|
||||||
if self.connected:
|
if self.connected:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -16,10 +16,9 @@
|
||||||
# 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 imp
|
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
|
import imp
|
||||||
|
|
||||||
import nemubot.tools.config
|
import nemubot.tools.config
|
||||||
imp.reload(nemubot.tools.config)
|
imp.reload(nemubot.tools.config)
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,6 @@
|
||||||
# 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 logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from nemubot.tools.xmlparser import parse_file
|
|
||||||
|
|
||||||
logger = logging.getLogger("nemubot.tools.config")
|
logger = logging.getLogger("nemubot.tools.config")
|
||||||
|
|
||||||
|
@ -97,7 +94,11 @@ def load_file(filename, context):
|
||||||
filename -- the path to the file to load
|
filename -- the path to the file to load
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
|
from nemubot.tools.xmlparser import parse_file
|
||||||
|
|
||||||
config = parse_file(filename)
|
config = parse_file(filename)
|
||||||
|
|
||||||
# This is a true nemubot configuration file, load it!
|
# This is a true nemubot configuration file, load it!
|
||||||
|
|
|
@ -16,10 +16,6 @@
|
||||||
# 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/>.
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def countdown(delta, resolution=5):
|
def countdown(delta, resolution=5):
|
||||||
sec = delta.seconds
|
sec = delta.seconds
|
||||||
hours, remainder = divmod(sec, 3600)
|
hours, remainder = divmod(sec, 3600)
|
||||||
|
@ -82,10 +78,15 @@ def countdown_format(date, msg_before, msg_after, tz=None):
|
||||||
"""Replace in a text %s by a sentence incidated the remaining time
|
"""Replace in a text %s by a sentence incidated the remaining time
|
||||||
before/after an event"""
|
before/after an event"""
|
||||||
if tz is not None:
|
if tz is not None:
|
||||||
|
import os
|
||||||
oldtz = os.environ['TZ']
|
oldtz = os.environ['TZ']
|
||||||
os.environ['TZ'] = tz
|
os.environ['TZ'] = tz
|
||||||
|
|
||||||
|
import time
|
||||||
time.tzset()
|
time.tzset()
|
||||||
|
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
# Calculate time before the date
|
# Calculate time before the date
|
||||||
try:
|
try:
|
||||||
if datetime.now(timezone.utc) > date:
|
if datetime.now(timezone.utc) > date:
|
||||||
|
@ -103,6 +104,7 @@ def countdown_format(date, msg_before, msg_after, tz=None):
|
||||||
delta = date - datetime.now()
|
delta = date - datetime.now()
|
||||||
|
|
||||||
if tz is not None:
|
if tz is not None:
|
||||||
|
import os
|
||||||
os.environ['TZ'] = oldtz
|
os.environ['TZ'] = oldtz
|
||||||
|
|
||||||
return sentence_c % countdown(delta)
|
return sentence_c % countdown(delta)
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
# Extraction/Format text
|
# Extraction/Format text
|
||||||
|
|
||||||
from datetime import datetime, date
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
xtrdt = re.compile(r'''^.*? (?P<day>[0-9]{1,4}) .+?
|
xtrdt = re.compile(r'''^.*? (?P<day>[0-9]{1,4}) .+?
|
||||||
|
@ -71,6 +70,7 @@ def extractDate(msg):
|
||||||
second = result.group("second")
|
second = result.group("second")
|
||||||
|
|
||||||
if year is None:
|
if year is None:
|
||||||
|
from datetime import date
|
||||||
year = date.today().year
|
year = date.today().year
|
||||||
if hour is None:
|
if hour is None:
|
||||||
hour = 0
|
hour = 0
|
||||||
|
@ -84,6 +84,7 @@ def extractDate(msg):
|
||||||
minute = int(minute) + 1
|
minute = int(minute) + 1
|
||||||
second = 0
|
second = 0
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
return datetime(int(year), int(month), int(day),
|
return datetime(int(year), int(month), int(day),
|
||||||
int(hour), int(minute), int(second))
|
int(hour), int(minute), int(second))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -16,18 +16,9 @@
|
||||||
# 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/>.
|
||||||
|
|
||||||
from html.entities import name2codepoint
|
|
||||||
import http.client
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import socket
|
|
||||||
from urllib.parse import quote
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from urllib.request import urlopen
|
|
||||||
|
|
||||||
from nemubot import __version__
|
|
||||||
from nemubot.exception import IRCException
|
from nemubot.exception import IRCException
|
||||||
from nemubot.tools.xmlparser import parse_string
|
|
||||||
|
|
||||||
|
|
||||||
def isURL(url):
|
def isURL(url):
|
||||||
|
@ -70,11 +61,19 @@ def getPassword(url):
|
||||||
# Get real pages
|
# Get real pages
|
||||||
|
|
||||||
def getURLContent(url, timeout=15):
|
def getURLContent(url, timeout=15):
|
||||||
"""Return page content corresponding to URL or None if any error occurs"""
|
"""Return page content corresponding to URL or None if any error occurs
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
url -- the URL to get
|
||||||
|
timeout -- maximum number of seconds to wait before returning an exception
|
||||||
|
"""
|
||||||
|
|
||||||
o = urlparse(url)
|
o = urlparse(url)
|
||||||
if o.netloc == "":
|
if o.netloc == "":
|
||||||
o = urlparse("http://" + url)
|
o = urlparse("http://" + url)
|
||||||
|
|
||||||
|
import http.client
|
||||||
|
|
||||||
if o.scheme == "http":
|
if o.scheme == "http":
|
||||||
conn = http.client.HTTPConnection(o.hostname, port=o.port,
|
conn = http.client.HTTPConnection(o.hostname, port=o.port,
|
||||||
timeout=timeout)
|
timeout=timeout)
|
||||||
|
@ -85,7 +84,10 @@ def getURLContent(url, timeout=15):
|
||||||
conn = http.client.HTTPConnection(o.hostname, port=80, timeout=timeout)
|
conn = http.client.HTTPConnection(o.hostname, port=80, timeout=timeout)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
import socket
|
||||||
try:
|
try:
|
||||||
|
from nemubot import __version__
|
||||||
if o.query != '':
|
if o.query != '':
|
||||||
conn.request("GET", o.path + "?" + o.query,
|
conn.request("GET", o.path + "?" + o.query,
|
||||||
None, {"User-agent": "Nemubot v%s" % __version__})
|
None, {"User-agent": "Nemubot v%s" % __version__})
|
||||||
|
@ -141,16 +143,31 @@ def getURLContent(url, timeout=15):
|
||||||
|
|
||||||
|
|
||||||
def getXML(url, timeout=15):
|
def getXML(url, timeout=15):
|
||||||
"""Get content page and return XML parsed content"""
|
"""Get content page and return XML parsed content
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
url -- the URL to get
|
||||||
|
timeout -- maximum number of seconds to wait before returning an exception
|
||||||
|
"""
|
||||||
|
|
||||||
cnt = getURLContent(url, timeout)
|
cnt = getURLContent(url, timeout)
|
||||||
if cnt is None:
|
if cnt is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
from nemubot.tools.xmlparser import parse_string
|
||||||
return parse_string(cnt.encode())
|
return parse_string(cnt.encode())
|
||||||
|
|
||||||
|
|
||||||
def getJSON(url, timeout=15):
|
def getJSON(url, timeout=15):
|
||||||
"""Get content page and return JSON content"""
|
"""Get content page and return JSON content
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
url -- the URL to get
|
||||||
|
timeout -- maximum number of seconds to wait before returning an exception
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
cnt = getURLContent(url, timeout)
|
cnt = getURLContent(url, timeout)
|
||||||
if cnt is None:
|
if cnt is None:
|
||||||
return None
|
return None
|
||||||
|
@ -161,13 +178,27 @@ def getJSON(url, timeout=15):
|
||||||
# Other utils
|
# Other utils
|
||||||
|
|
||||||
def htmlentitydecode(s):
|
def htmlentitydecode(s):
|
||||||
"""Decode htmlentities"""
|
"""Decode htmlentities
|
||||||
|
|
||||||
|
Argument:
|
||||||
|
s -- The string to decode
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from html.entities import name2codepoint
|
||||||
|
|
||||||
return re.sub('&(%s);' % '|'.join(name2codepoint),
|
return re.sub('&(%s);' % '|'.join(name2codepoint),
|
||||||
lambda m: chr(name2codepoint[m.group(1)]), s)
|
lambda m: chr(name2codepoint[m.group(1)]), s)
|
||||||
|
|
||||||
|
|
||||||
def striphtml(data):
|
def striphtml(data):
|
||||||
"""Remove HTML tags from text"""
|
"""Remove HTML tags from text
|
||||||
|
|
||||||
|
Argument:
|
||||||
|
data -- the string to strip
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
p = re.compile(r'<.*?>')
|
p = re.compile(r'<.*?>')
|
||||||
return htmlentitydecode(p.sub('', data)
|
return htmlentitydecode(p.sub('', data)
|
||||||
.replace("(", "/(")
|
.replace("(", "/(")
|
||||||
|
|
|
@ -16,13 +16,10 @@
|
||||||
# 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 logging
|
|
||||||
import xml.sax
|
import xml.sax
|
||||||
|
|
||||||
from nemubot.tools.xmlparser import node as module_state
|
from nemubot.tools.xmlparser import node as module_state
|
||||||
|
|
||||||
logger = logging.getLogger("nemubot.tools.xmlparser")
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleStatesFile(xml.sax.ContentHandler):
|
class ModuleStatesFile(xml.sax.ContentHandler):
|
||||||
|
|
||||||
|
|
|
@ -16,10 +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 calendar
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
import logging
|
import logging
|
||||||
import xml.sax
|
|
||||||
|
|
||||||
logger = logging.getLogger("nemubot.tools.xmlparser.node")
|
logger = logging.getLogger("nemubot.tools.xmlparser.node")
|
||||||
|
|
||||||
|
@ -78,14 +75,17 @@ class ModuleState:
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
if isinstance(source, datetime):
|
if isinstance(source, datetime):
|
||||||
return source
|
return source
|
||||||
else:
|
else:
|
||||||
|
from datetime import timezone
|
||||||
try:
|
try:
|
||||||
return datetime.utcfromtimestamp(float(source)).replace(tzinfo=timezone.utc)
|
return datetime.utcfromtimestamp(float(source)).replace(tzinfo=timezone.utc)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
import time
|
||||||
return time.strptime(source[:19], "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
|
return time.strptime(source[:19], "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
@ -140,6 +140,7 @@ class ModuleState:
|
||||||
|
|
||||||
def setAttribute(self, name, value):
|
def setAttribute(self, name, value):
|
||||||
"""DOM like method"""
|
"""DOM like method"""
|
||||||
|
from datetime import datetime
|
||||||
if (isinstance(value, datetime) or isinstance(value, str) or
|
if (isinstance(value, datetime) or isinstance(value, str) or
|
||||||
isinstance(value, int) or isinstance(value, float)):
|
isinstance(value, int) or isinstance(value, float)):
|
||||||
self.attributes[name] = value
|
self.attributes[name] = value
|
||||||
|
@ -196,14 +197,17 @@ class ModuleState:
|
||||||
|
|
||||||
def save_node(self, gen):
|
def save_node(self, gen):
|
||||||
"""Serialize this node as a XML node"""
|
"""Serialize this node as a XML node"""
|
||||||
|
from datetime import datetime
|
||||||
attribs = {}
|
attribs = {}
|
||||||
for att in self.attributes.keys():
|
for att in self.attributes.keys():
|
||||||
if att[0] != "_": # Don't save attribute starting by _
|
if att[0] != "_": # Don't save attribute starting by _
|
||||||
if isinstance(self.attributes[att], datetime):
|
if isinstance(self.attributes[att], datetime):
|
||||||
|
import calendar
|
||||||
attribs[att] = str(calendar.timegm(
|
attribs[att] = str(calendar.timegm(
|
||||||
self.attributes[att].timetuple()))
|
self.attributes[att].timetuple()))
|
||||||
else:
|
else:
|
||||||
attribs[att] = str(self.attributes[att])
|
attribs[att] = str(self.attributes[att])
|
||||||
|
import xml.sax
|
||||||
attrs = xml.sax.xmlreader.AttributesImpl(attribs)
|
attrs = xml.sax.xmlreader.AttributesImpl(attribs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -220,6 +224,7 @@ class ModuleState:
|
||||||
def save(self, filename):
|
def save(self, filename):
|
||||||
"""Save the current node as root node in a XML file"""
|
"""Save the current node as root node in a XML file"""
|
||||||
with open(filename, "w") as f:
|
with open(filename, "w") as f:
|
||||||
|
import xml.sax
|
||||||
gen = xml.sax.saxutils.XMLGenerator(f, "utf-8")
|
gen = xml.sax.saxutils.XMLGenerator(f, "utf-8")
|
||||||
gen.startDocument()
|
gen.startDocument()
|
||||||
self.save_node(gen)
|
self.save_node(gen)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue