1
0
Fork 0

Optimize imports

This commit is contained in:
nemunaire 2015-02-21 13:51:40 +01:00 committed by nemunaire
parent 2e7a4ad132
commit e588c30044
29 changed files with 731 additions and 925 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -18,7 +18,6 @@
import logging
import queue
import re
import threading
logger = logging.getLogger("nemubot.consumer")

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -18,7 +18,6 @@
import re
from nemubot.exception import IRCException
from nemubot.hooks import AbstractHook
import nemubot.message

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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])

View File

@ -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;%d\033[0m " %
@ -134,6 +132,8 @@ def hotswap(bak):
def reload():
import imp
import nemubot.prompt.builtins
imp.reload(nemubot.prompt.builtins)

View File

@ -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:

View File

@ -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

View File

@ -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
View 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)

View File

@ -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:

View File

@ -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)

View File

@ -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!

View File

@ -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)

View File

@ -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:

View File

@ -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("&#x28;", "/(")

View File

@ -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):

View File

@ -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)