Optimize imports
This commit is contained in:
parent
2e7a4ad132
commit
e588c30044
20
bin/nemubot
20
bin/nemubot
@ -17,21 +17,12 @@
|
||||
# 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 argparse
|
||||
import imp
|
||||
import logging
|
||||
import os
|
||||
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__":
|
||||
# Parse command line arguments
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-a", "--no-connect", action="store_true",
|
||||
@ -59,11 +50,14 @@ if __name__ == "__main__":
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
import nemubot
|
||||
|
||||
if args.version:
|
||||
print(nemubot.__version__)
|
||||
sys.exit(0)
|
||||
|
||||
# Setup loggin interface
|
||||
import logging
|
||||
logger = logging.getLogger("nemubot")
|
||||
|
||||
formatter = logging.Formatter(
|
||||
@ -92,6 +86,7 @@ if __name__ == "__main__":
|
||||
logger.error("%s is not a directory", path)
|
||||
|
||||
# Create bot context
|
||||
from nemubot import datastore
|
||||
context = nemubot.Bot(modules_paths=modules_paths, data_store=datastore.XML(args.data_path),
|
||||
verbosity=args.verbose)
|
||||
|
||||
@ -99,14 +94,17 @@ if __name__ == "__main__":
|
||||
context.noautoconnect = True
|
||||
|
||||
# Load the prompt
|
||||
import nemubot.prompt
|
||||
prmpt = nemubot.prompt.Prompt()
|
||||
|
||||
# Register the hook for futur import
|
||||
from nemubot.importer import ModuleFinder
|
||||
sys.meta_path.append(ModuleFinder(context.modules_paths, context.add_module))
|
||||
|
||||
# Load requested configuration files
|
||||
for path in args.files:
|
||||
if os.path.isfile(path):
|
||||
from nemubot.prompt.builtins import load_file
|
||||
load_file(path, context)
|
||||
else:
|
||||
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__,
|
||||
os.getpid()))
|
||||
from nemubot.prompt.reset import PromptReset
|
||||
while True:
|
||||
try:
|
||||
context.start()
|
||||
@ -127,6 +126,7 @@ if __name__ == "__main__":
|
||||
break
|
||||
|
||||
try:
|
||||
import imp
|
||||
# Reload all other modules
|
||||
imp.reload(nemubot)
|
||||
imp.reload(nemubot.prompt)
|
||||
|
@ -19,10 +19,7 @@
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
from nemubot.prompt.error import PromptError
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.message import TextMessage
|
||||
from nemubot.networkbot import NetworkBot
|
||||
|
||||
nemubotversion = 3.4
|
||||
NODATA = True
|
||||
@ -37,6 +34,7 @@ def getserver(toks, context, prompt, mandatory=False, **kwargs):
|
||||
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.")
|
||||
|
||||
|
||||
@ -144,6 +142,7 @@ def send(toks, **kwargs):
|
||||
% (toks[1], srv.id))
|
||||
return 3
|
||||
|
||||
from nemubot.message import TextMessage
|
||||
srv.send_response(TextMessage(" ".join(toks[2:]), server=None,
|
||||
to=[toks[1]]))
|
||||
return 0
|
||||
|
@ -5,6 +5,7 @@
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IRCException
|
||||
from nemubot.tools import web
|
||||
|
||||
|
@ -16,476 +16,15 @@
|
||||
# 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 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'
|
||||
__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.networkbot import NetworkBot
|
||||
|
||||
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():
|
||||
import imp
|
||||
|
||||
import 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 queue
|
||||
import re
|
||||
import threading
|
||||
|
||||
logger = logging.getLogger("nemubot.consumer")
|
||||
|
@ -14,9 +14,6 @@
|
||||
# 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 nemubot.tools.xmlparser import module_state
|
||||
|
||||
|
||||
class Abstract:
|
||||
|
||||
"""Abstract implementation of a module data store, that always return an
|
||||
@ -38,6 +35,7 @@ class Abstract:
|
||||
The loaded data
|
||||
"""
|
||||
|
||||
from nemubot.tools.xmlparser import module_state
|
||||
return module_state.ModuleState("nemubotstate")
|
||||
|
||||
def save(self, module, data):
|
||||
|
@ -17,7 +17,6 @@
|
||||
import os
|
||||
|
||||
from nemubot.datastore.abstract import Abstract
|
||||
from nemubot.tools.xmlparser import parse_file
|
||||
|
||||
|
||||
class XML(Abstract):
|
||||
@ -66,6 +65,7 @@ class XML(Abstract):
|
||||
|
||||
data_file = self._get_data_file_path(module)
|
||||
if os.path.isfile(data_file):
|
||||
from nemubot.tools.xmlparser import parse_file
|
||||
return parse_file(data_file)
|
||||
else:
|
||||
return Abstract.load(self, module)
|
||||
|
@ -16,9 +16,6 @@
|
||||
# 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 nemubot.message import TextMessage, DirectAsk
|
||||
|
||||
|
||||
class IRCException(Exception):
|
||||
|
||||
def __init__(self, message, personnal=True):
|
||||
@ -28,8 +25,11 @@ class IRCException(Exception):
|
||||
|
||||
def fill_response(self, msg):
|
||||
if self.personnal:
|
||||
from nemubot.message import DirectAsk
|
||||
return DirectAsk(msg.frm, self.message,
|
||||
server=msg.server, to=msg.to_response)
|
||||
|
||||
else:
|
||||
from nemubot.message import TextMessage
|
||||
return TextMessage(self.message,
|
||||
server=msg.server, to=msg.to_response)
|
||||
|
@ -16,11 +16,6 @@
|
||||
# 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 imp
|
||||
|
||||
from nemubot.exception import IRCException
|
||||
|
||||
|
||||
def call_game(call, *args, **kargs):
|
||||
"""TODO"""
|
||||
l = list()
|
||||
@ -54,6 +49,8 @@ class AbstractHook:
|
||||
|
||||
def run(self, data1, *args):
|
||||
"""Run the hook"""
|
||||
|
||||
from nemubot.exception import IRCException
|
||||
self.times -= 1
|
||||
|
||||
try:
|
||||
@ -81,6 +78,8 @@ def hook(store, *args, **kargs):
|
||||
|
||||
|
||||
def reload():
|
||||
import imp
|
||||
|
||||
import nemubot.hooks.manager
|
||||
imp.reload(nemubot.hooks.manager)
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
import re
|
||||
|
||||
from nemubot.exception import IRCException
|
||||
from nemubot.hooks import AbstractHook
|
||||
import nemubot.message
|
||||
|
||||
|
@ -16,18 +16,10 @@
|
||||
# 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 distutils.version import LooseVersion
|
||||
from importlib.abc import Finder
|
||||
from importlib.machinery import SourceFileLoader
|
||||
import imp
|
||||
import logging
|
||||
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")
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, timezone
|
||||
import imp
|
||||
|
||||
|
||||
class AbstractMessage:
|
||||
@ -166,6 +165,8 @@ class OwnerCommand(Command):
|
||||
|
||||
|
||||
def reload():
|
||||
import imp
|
||||
|
||||
import 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
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import imp
|
||||
|
||||
|
||||
def reload():
|
||||
import imp
|
||||
|
||||
import 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
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from nemubot.tools.xmlparser import module_state
|
||||
|
||||
|
||||
def convert_legacy_store(old):
|
||||
if old == "cmd_hook" or old == "cmd_rgxp" or old == "cmd_default":
|
||||
return "in_Command"
|
||||
@ -84,6 +81,8 @@ class ModuleContext:
|
||||
return False
|
||||
|
||||
else:
|
||||
from nemubot.tools.xmlparser import module_state
|
||||
|
||||
self.data = module_state.ModuleState("nemubotstate")
|
||||
|
||||
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
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import imp
|
||||
import os
|
||||
import readline
|
||||
import shlex
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from nemubot.prompt.error import PromptError
|
||||
from nemubot.prompt.reset import PromptReset
|
||||
from nemubot.prompt import builtins
|
||||
|
||||
|
||||
@ -102,6 +97,9 @@ class Prompt:
|
||||
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 " %
|
||||
@ -134,6 +132,8 @@ def hotswap(bak):
|
||||
|
||||
|
||||
def reload():
|
||||
import imp
|
||||
|
||||
import 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
|
||||
# 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):
|
||||
"""Quit the prompt for reload or exit"""
|
||||
from nemubot.prompt.reset import PromptReset
|
||||
|
||||
if toks[0] == "refresh":
|
||||
raise PromptReset("refresh")
|
||||
elif toks[0] == "reset":
|
||||
@ -67,6 +61,8 @@ def liste(toks, context, prompt):
|
||||
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:
|
||||
|
@ -16,11 +16,8 @@
|
||||
# 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 calendar
|
||||
from datetime import datetime, timezone
|
||||
import ipaddress
|
||||
import re
|
||||
import time
|
||||
import shlex
|
||||
|
||||
from nemubot.channel import Channel
|
||||
@ -82,6 +79,7 @@ class IRC(SocketServer):
|
||||
def _ctcp_dcc(msg, cmds):
|
||||
"""Response to DCC CTCP message"""
|
||||
try:
|
||||
import ipaddress
|
||||
ip = ipaddress.ip_address(int(cmds[3]))
|
||||
port = int(cmds[4])
|
||||
conn = DCC(srv, msg.sender)
|
||||
@ -333,6 +331,7 @@ class IRCMessage:
|
||||
|
||||
# Treat special tags
|
||||
if key == "time":
|
||||
import calendar, time
|
||||
value = datetime.fromtimestamp(calendar.timegm(time.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")), timezone.utc)
|
||||
|
||||
# Store tag
|
||||
|
@ -16,11 +16,6 @@
|
||||
# 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 imp
|
||||
import logging
|
||||
import socket
|
||||
import queue
|
||||
|
||||
# Lists for select
|
||||
_rlist = []
|
||||
@ -28,132 +23,12 @@ _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)
|
||||
|
||||
|
||||
def reload():
|
||||
import imp
|
||||
|
||||
import nemubot.server.abstract
|
||||
imp.reload(nemubot.server.abstract)
|
||||
|
||||
import 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
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ssl
|
||||
import socket
|
||||
|
||||
from nemubot.server import AbstractServer
|
||||
from nemubot.server.abstract import AbstractServer
|
||||
|
||||
|
||||
class SocketServer(AbstractServer):
|
||||
@ -49,11 +46,14 @@ class SocketServer(AbstractServer):
|
||||
# Open/close
|
||||
|
||||
def _open(self):
|
||||
import os
|
||||
import socket
|
||||
# Create the socket
|
||||
self.socket = socket.socket()
|
||||
|
||||
# Wrap the socket for SSL
|
||||
if self.ssl:
|
||||
import ssl
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||
self.socket = ctx.wrap_socket(self.socket)
|
||||
|
||||
@ -71,6 +71,8 @@ class SocketServer(AbstractServer):
|
||||
|
||||
|
||||
def _close(self):
|
||||
import socket
|
||||
|
||||
self._sending_queue.join()
|
||||
if self.connected:
|
||||
try:
|
||||
|
@ -16,10 +16,9 @@
|
||||
# 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 imp
|
||||
|
||||
|
||||
def reload():
|
||||
import imp
|
||||
|
||||
import nemubot.tools.config
|
||||
imp.reload(nemubot.tools.config)
|
||||
|
||||
|
@ -17,9 +17,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from nemubot.tools.xmlparser import parse_file
|
||||
|
||||
logger = logging.getLogger("nemubot.tools.config")
|
||||
|
||||
@ -97,7 +94,11 @@ def load_file(filename, context):
|
||||
filename -- the path to the file to load
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
if os.path.isfile(filename):
|
||||
from nemubot.tools.xmlparser import parse_file
|
||||
|
||||
config = parse_file(filename)
|
||||
|
||||
# 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
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, timezone
|
||||
import time
|
||||
|
||||
|
||||
def countdown(delta, resolution=5):
|
||||
sec = delta.seconds
|
||||
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
|
||||
before/after an event"""
|
||||
if tz is not None:
|
||||
import os
|
||||
oldtz = os.environ['TZ']
|
||||
os.environ['TZ'] = tz
|
||||
|
||||
import time
|
||||
time.tzset()
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Calculate time before the date
|
||||
try:
|
||||
if datetime.now(timezone.utc) > date:
|
||||
@ -103,6 +104,7 @@ def countdown_format(date, msg_before, msg_after, tz=None):
|
||||
delta = date - datetime.now()
|
||||
|
||||
if tz is not None:
|
||||
import os
|
||||
os.environ['TZ'] = oldtz
|
||||
|
||||
return sentence_c % countdown(delta)
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
# Extraction/Format text
|
||||
|
||||
from datetime import datetime, date
|
||||
import re
|
||||
|
||||
xtrdt = re.compile(r'''^.*? (?P<day>[0-9]{1,4}) .+?
|
||||
@ -71,6 +70,7 @@ def extractDate(msg):
|
||||
second = result.group("second")
|
||||
|
||||
if year is None:
|
||||
from datetime import date
|
||||
year = date.today().year
|
||||
if hour is None:
|
||||
hour = 0
|
||||
@ -84,6 +84,7 @@ def extractDate(msg):
|
||||
minute = int(minute) + 1
|
||||
second = 0
|
||||
|
||||
from datetime import datetime
|
||||
return datetime(int(year), int(month), int(day),
|
||||
int(hour), int(minute), int(second))
|
||||
else:
|
||||
|
@ -16,18 +16,9 @@
|
||||
# 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 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.request import urlopen
|
||||
|
||||
from nemubot import __version__
|
||||
from nemubot.exception import IRCException
|
||||
from nemubot.tools.xmlparser import parse_string
|
||||
|
||||
|
||||
def isURL(url):
|
||||
@ -70,11 +61,19 @@ def getPassword(url):
|
||||
# Get real pages
|
||||
|
||||
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)
|
||||
if o.netloc == "":
|
||||
o = urlparse("http://" + url)
|
||||
|
||||
import http.client
|
||||
|
||||
if o.scheme == "http":
|
||||
conn = http.client.HTTPConnection(o.hostname, port=o.port,
|
||||
timeout=timeout)
|
||||
@ -85,7 +84,10 @@ def getURLContent(url, timeout=15):
|
||||
conn = http.client.HTTPConnection(o.hostname, port=80, timeout=timeout)
|
||||
else:
|
||||
return None
|
||||
|
||||
import socket
|
||||
try:
|
||||
from nemubot import __version__
|
||||
if o.query != '':
|
||||
conn.request("GET", o.path + "?" + o.query,
|
||||
None, {"User-agent": "Nemubot v%s" % __version__})
|
||||
@ -141,16 +143,31 @@ def getURLContent(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)
|
||||
if cnt is None:
|
||||
return None
|
||||
else:
|
||||
from nemubot.tools.xmlparser import parse_string
|
||||
return parse_string(cnt.encode())
|
||||
|
||||
|
||||
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)
|
||||
if cnt is None:
|
||||
return None
|
||||
@ -161,13 +178,27 @@ def getJSON(url, timeout=15):
|
||||
# Other utils
|
||||
|
||||
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),
|
||||
lambda m: chr(name2codepoint[m.group(1)]), s)
|
||||
|
||||
|
||||
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'<.*?>')
|
||||
return htmlentitydecode(p.sub('', data)
|
||||
.replace("(", "/(")
|
||||
|
@ -16,13 +16,10 @@
|
||||
# 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 logging
|
||||
import xml.sax
|
||||
|
||||
from nemubot.tools.xmlparser import node as module_state
|
||||
|
||||
logger = logging.getLogger("nemubot.tools.xmlparser")
|
||||
|
||||
|
||||
class ModuleStatesFile(xml.sax.ContentHandler):
|
||||
|
||||
|
@ -16,10 +16,7 @@
|
||||
# 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 calendar
|
||||
from datetime import datetime, timezone
|
||||
import logging
|
||||
import xml.sax
|
||||
|
||||
logger = logging.getLogger("nemubot.tools.xmlparser.node")
|
||||
|
||||
@ -78,14 +75,17 @@ class ModuleState:
|
||||
else:
|
||||
return None
|
||||
|
||||
from datetime import datetime
|
||||
if isinstance(source, datetime):
|
||||
return source
|
||||
else:
|
||||
from datetime import timezone
|
||||
try:
|
||||
return datetime.utcfromtimestamp(float(source)).replace(tzinfo=timezone.utc)
|
||||
except ValueError:
|
||||
while True:
|
||||
try:
|
||||
import time
|
||||
return time.strptime(source[:19], "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
|
||||
except ImportError:
|
||||
pass
|
||||
@ -140,6 +140,7 @@ class ModuleState:
|
||||
|
||||
def setAttribute(self, name, value):
|
||||
"""DOM like method"""
|
||||
from datetime import datetime
|
||||
if (isinstance(value, datetime) or isinstance(value, str) or
|
||||
isinstance(value, int) or isinstance(value, float)):
|
||||
self.attributes[name] = value
|
||||
@ -196,14 +197,17 @@ class ModuleState:
|
||||
|
||||
def save_node(self, gen):
|
||||
"""Serialize this node as a XML node"""
|
||||
from datetime import datetime
|
||||
attribs = {}
|
||||
for att in self.attributes.keys():
|
||||
if att[0] != "_": # Don't save attribute starting by _
|
||||
if isinstance(self.attributes[att], datetime):
|
||||
import calendar
|
||||
attribs[att] = str(calendar.timegm(
|
||||
self.attributes[att].timetuple()))
|
||||
else:
|
||||
attribs[att] = str(self.attributes[att])
|
||||
import xml.sax
|
||||
attrs = xml.sax.xmlreader.AttributesImpl(attribs)
|
||||
|
||||
try:
|
||||
@ -220,6 +224,7 @@ class ModuleState:
|
||||
def save(self, filename):
|
||||
"""Save the current node as root node in a XML file"""
|
||||
with open(filename, "w") as f:
|
||||
import xml.sax
|
||||
gen = xml.sax.saxutils.XMLGenerator(f, "utf-8")
|
||||
gen.startDocument()
|
||||
self.save_node(gen)
|
||||
|
Loading…
x
Reference in New Issue
Block a user