Introduce nemubot v3.2
- New licence: AGPL3 instead of GPL3 - Import is now based on finder and loader instead of sys.path - Modules used hooks to treat message instead of treating all messages - Remove a lot of builtins from the prompt - Prompt: ^C and ^D have now correct feature (nothing and exit)
This commit is contained in:
parent
a2b273d09b
commit
e4d4e68c45
141
COPYING
141
COPYING
@ -1,5 +1,5 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
@ -7,17 +7,15 @@
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
@ -72,7 +60,7 @@ modification follow.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
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 General Public License for more details.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
129
bot.py
Normal file
129
bot.py
Normal file
@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 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 hooks
|
||||
from server import Server
|
||||
|
||||
class Bot:
|
||||
def __init__(self, servers=dict(), modules=dict(), mp=list()):
|
||||
self.version = 3.2
|
||||
self.version_txt = "3.2"
|
||||
|
||||
self.servers = servers
|
||||
self.modules = modules
|
||||
|
||||
self.modules_path = mp
|
||||
self.datas_path = './datas/'
|
||||
|
||||
self.hooks = hooks.MessagesHook()
|
||||
|
||||
def addServer(self, node, nick, owner, realname):
|
||||
"""Add a new server to the context"""
|
||||
srv = Server(node, nick, owner, realname)
|
||||
if srv.id not in self.servers:
|
||||
self.servers[srv.id] = srv
|
||||
if srv.autoconnect:
|
||||
srv.launch(self)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
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
|
||||
for mod in self.modules.keys():
|
||||
if self.modules[mod].name == module.name:
|
||||
self.unload_module(self.modules[mod].name)
|
||||
break
|
||||
|
||||
self.modules[module.name] = module
|
||||
return True
|
||||
|
||||
|
||||
def add_modules_path(self, path):
|
||||
"""Add a path to the modules_path array, used by module loader"""
|
||||
# The path must end by / char
|
||||
if path[len(path)-1] != "/":
|
||||
path = path + "/"
|
||||
|
||||
if path not in self.modules_path:
|
||||
self.modules_path.append(path)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def unload_module(self, name, verb=False):
|
||||
"""Unload a module"""
|
||||
if name in self.modules:
|
||||
self.modules[name].save()
|
||||
if hasattr(self.modules[name], "unload"):
|
||||
self.modules[name].unload()
|
||||
# Remove from the dict
|
||||
del self.modules[name]
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def quit(self, verb=False):
|
||||
"""Save and unload modules and disconnect servers"""
|
||||
if verb: print ("Save and unload all modules...")
|
||||
k = list(self.modules.keys())
|
||||
for mod in k:
|
||||
print (mod)
|
||||
self.unload_module(mod, verb)
|
||||
|
||||
if verb: print ("Close all servers connection...")
|
||||
k = list(self.servers.keys())
|
||||
for srv in k:
|
||||
self.servers[srv].disconnect()
|
||||
|
||||
|
||||
def hotswap(bak):
|
||||
return Bot(bak.servers, bak.modules, bak.modules_path)
|
||||
|
||||
def reload():
|
||||
import imp
|
||||
|
||||
import prompt.builtins
|
||||
imp.reload(prompt.builtins)
|
||||
|
||||
import hooks
|
||||
imp.reload(hooks)
|
||||
|
||||
import xmlparser
|
||||
imp.reload(xmlparser)
|
||||
import xmlparser.node
|
||||
imp.reload(xmlparser.node)
|
||||
|
||||
import importer
|
||||
imp.reload(importer)
|
||||
|
||||
import server
|
||||
imp.reload(server)
|
||||
|
||||
import channel
|
||||
imp.reload(channel)
|
||||
|
||||
import DCC
|
||||
imp.reload(DCC)
|
||||
|
||||
import message
|
||||
imp.reload(message)
|
151
hooks.py
Normal file
151
hooks.py
Normal file
@ -0,0 +1,151 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class MessagesHook:
|
||||
def __init__(self):
|
||||
# Store direct hook
|
||||
self.cmd_hook = dict()
|
||||
self.ask_hook = dict()
|
||||
self.msg_hook = dict()
|
||||
|
||||
# Store regexp hook
|
||||
self.cmd_rgxp = list()
|
||||
self.ask_rgxp = list()
|
||||
self.msg_rgxp = list()
|
||||
|
||||
|
||||
def add_hook(self, store, hook):
|
||||
"""Insert in the right place a hook into the given store"""
|
||||
if isinstance(store, dict) and hook.name is not None:
|
||||
if hook.name not in store:
|
||||
store[hook.name] = list()
|
||||
store[hook.name].append(hook)
|
||||
elif isinstance(store, list):
|
||||
store.append(hook)
|
||||
else:
|
||||
print ("Warning: unrecognized hook store type")
|
||||
|
||||
def register_hook(self, module, node):
|
||||
"""Create a hook from configuration node"""
|
||||
if node.name == "message" and node.hasAttribute("type"):
|
||||
if node["type"] == "cmd" or node["type"] == "all":
|
||||
if node.hasAttribute("name"):
|
||||
self.add_hook(self.cmd_hook, Hook(getattr(module,
|
||||
node["call"]),
|
||||
node["name"]))
|
||||
elif node.hasAttribute("regexp"):
|
||||
self.add_hook(self.cmd_rgxp, Hook(getattr(module,
|
||||
node["call"]),
|
||||
None, None,
|
||||
node["regexp"]))
|
||||
|
||||
if node["type"] == "ask" or node["type"] == "all":
|
||||
if node.hasAttribute("name"):
|
||||
self.add_hook(self.ask_hook, Hook(getattr(module,
|
||||
node["call"]),
|
||||
node["name"]))
|
||||
elif node.hasAttribute("regexp"):
|
||||
self.add_hook(self.ask_rgxp, Hook(getattr(module,
|
||||
node["call"]),
|
||||
None, None,
|
||||
node["regexp"]))
|
||||
|
||||
if node["type"] == "answer" or node["type"] == "all":
|
||||
if node.hasAttribute("name"):
|
||||
self.add_hook(self.msg_hook, Hook(getattr(module,
|
||||
node["call"]),
|
||||
node["name"]))
|
||||
elif node.hasAttribute("regexp"):
|
||||
self.add_hook(self.msg_rgxp, Hook(getattr(module,
|
||||
node["call"]),
|
||||
None, None,
|
||||
node["regexp"]))
|
||||
|
||||
|
||||
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 treat_cmd(self, msg):
|
||||
"""Treat a command message"""
|
||||
# First, treat simple hook
|
||||
if msg.cmd[0] in self.cmd_hook:
|
||||
for h in self.cmd_hook[msg.cmd[0]]:
|
||||
h.run(msg)
|
||||
self.check_rest_times(self.cmd_hook, h)
|
||||
|
||||
# Then, treat regexp based hook
|
||||
for hook in self.cmd_rgxp:
|
||||
if hook.is_matching(msg):
|
||||
hook.run(msg)
|
||||
self.check_rest_times(self.cmd_rgxp, hook)
|
||||
|
||||
def treat_ask(self, msg):
|
||||
"""Treat an ask message"""
|
||||
# First, treat simple hook
|
||||
if msg.content in self.ask_hook:
|
||||
for h in self.ask_hook[msg.content]:
|
||||
h.run(msg)
|
||||
self.check_rest_times(self.ask_hook, h)
|
||||
|
||||
# Then, treat regexp based hook
|
||||
for hook in self.ask_rgxp:
|
||||
if hook.is_matching(msg):
|
||||
hook.run(msg)
|
||||
self.check_rest_times(self.ask_rgxp, hook)
|
||||
|
||||
def treat_answer(self, msg):
|
||||
"""Treat a normal message"""
|
||||
# First, treat simple hook
|
||||
if msg.content in self.msg_hook:
|
||||
for h in self.msg_hook[msg.cmd[0]]:
|
||||
h.run(msg)
|
||||
self.check_rest_times(self.msg_hook, h)
|
||||
|
||||
# Then, treat regexp based hook
|
||||
for hook in self.msg_rgxp:
|
||||
if hook.is_matching(msg):
|
||||
hook.run(msg)
|
||||
self.check_rest_times(self.msg_rgxp, hook)
|
||||
|
||||
|
||||
class Hook:
|
||||
"""Class storing hook informations"""
|
||||
def __init__(self, call, name=None, data=None, regexp=None):
|
||||
self.name = name
|
||||
self.call = call
|
||||
self.regexp = regexp
|
||||
self.data = data
|
||||
self.times = -1
|
||||
|
||||
def is_matching(self, strcmp):
|
||||
"""Test if the current hook correspond to the message"""
|
||||
return (self.name is not None and strcmp == self.name) or (
|
||||
self.regexp is not None and re.match(self.regexp, strcmp))
|
||||
|
||||
def run(self, msg):
|
||||
"""Run the hook"""
|
||||
if self.times > 0:
|
||||
self.times -= 1
|
||||
return self.call(self.data, msg)
|
229
importer.py
Normal file
229
importer.py
Normal file
@ -0,0 +1,229 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 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 importlib.abc import Finder
|
||||
from importlib.abc import SourceLoader
|
||||
import imp
|
||||
import os
|
||||
import sys
|
||||
import xmlparser
|
||||
|
||||
class ModuleFinder(Finder):
|
||||
def __init__(self, context, prompt):
|
||||
self.context = context
|
||||
self.prompt = prompt
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
#print ("looking for", fullname, "in", path)
|
||||
# Search only for new nemubot modules (packages init)
|
||||
if path is None:
|
||||
for mpath in self.context.modules_path:
|
||||
#print ("looking for", fullname, "in", mpath)
|
||||
if os.path.isfile(mpath + fullname + ".xml"):
|
||||
return ModuleLoader(self.context, self.prompt, fullname,
|
||||
mpath, mpath + fullname + ".xml")
|
||||
elif (os.path.isfile(mpath + fullname + ".py") or
|
||||
os.path.isfile(mpath + fullname + "/__init__.py")):
|
||||
return ModuleLoader(self.context, self.prompt,
|
||||
fullname, mpath, None)
|
||||
#print ("not found")
|
||||
return None
|
||||
|
||||
|
||||
class ModuleLoader(SourceLoader):
|
||||
def __init__(self, context, prompt, fullname, path, config_path):
|
||||
self.context = context
|
||||
self.prompt = prompt
|
||||
self.name = fullname
|
||||
self.config_path = config_path
|
||||
|
||||
if config_path is not None:
|
||||
self.config = xmlparser.parse_file(config_path)
|
||||
if self.config.hasAttribute("name"):
|
||||
self.name = self.config["name"]
|
||||
|
||||
if os.path.isfile(path + fullname + ".py"):
|
||||
self.source_path = path + self.name + ".py"
|
||||
self.package = False
|
||||
elif os.path.isfile(path + fullname + "/__init__.py"):
|
||||
self.source_path = path + self.name + "/__init__.py"
|
||||
self.package = True
|
||||
else:
|
||||
raise ImportError
|
||||
|
||||
def get_filename(self, fullname):
|
||||
"""Return the path to the source file as found by the finder."""
|
||||
return self.source_path
|
||||
|
||||
def get_data(self, path):
|
||||
"""Return the data from path as raw bytes."""
|
||||
with open(path, 'rb') as file:
|
||||
return file.read()
|
||||
|
||||
def path_mtime(self, path):
|
||||
st = os.stat(path)
|
||||
return int(st.st_mtime)
|
||||
|
||||
def set_data(self, path, data):
|
||||
"""Write bytes data to a file."""
|
||||
parent, filename = os.path.split(path)
|
||||
path_parts = []
|
||||
# Figure out what directories are missing.
|
||||
while parent and not os.path.isdir(parent):
|
||||
parent, part = os.path.split(parent)
|
||||
path_parts.append(part)
|
||||
# Create needed directories.
|
||||
for part in reversed(path_parts):
|
||||
parent = os.path.join(parent, part)
|
||||
try:
|
||||
os.mkdir(parent)
|
||||
except FileExistsError:
|
||||
# Probably another Python process already created the dir.
|
||||
continue
|
||||
except PermissionError:
|
||||
# If can't get proper access, then just forget about writing
|
||||
# the data.
|
||||
return
|
||||
try:
|
||||
with open(path, 'wb') as file:
|
||||
file.write(data)
|
||||
except (PermissionError, FileExistsError):
|
||||
pass
|
||||
|
||||
def get_code(self, fullname):
|
||||
return SourceLoader.get_code(self, fullname)
|
||||
|
||||
def get_source(self, fullname):
|
||||
return SourceLoader.get_source(self, fullname)
|
||||
|
||||
def is_package(self, fullname):
|
||||
return self.package
|
||||
|
||||
def load_module(self, fullname):
|
||||
module = self._load_module(fullname, sourceless=True)
|
||||
|
||||
# Remove the module from sys list
|
||||
del sys.modules[fullname]
|
||||
|
||||
# If the module was already loaded, then reload it
|
||||
if hasattr(module, '__LOADED__'):
|
||||
reload(module)
|
||||
|
||||
# Check that is a valid nemubot module
|
||||
if not hasattr(module, "nemubotversion"):
|
||||
raise ImportError("Module `%s' is not a nemubot module."%self.name)
|
||||
# Check module version
|
||||
if module.nemubotversion != self.context.version:
|
||||
raise ImportError("Module `%s' is not compatible with this "
|
||||
"version." % self.name)
|
||||
|
||||
# Set module common functions and datas
|
||||
module.__LOADED__ = True
|
||||
|
||||
# Set module common functions and datas
|
||||
module.DEBUG = False
|
||||
module.name = fullname
|
||||
module.print = lambda msg: print("[%s] %s"%(module.name, msg))
|
||||
module.print_debug = lambda msg: mod_print_dbg(module, msg)
|
||||
|
||||
if not hasattr(module, "NODATA"):
|
||||
module.DATAS = xmlparser.parse_file(self.context.datas_path
|
||||
+ module.name + ".xml")
|
||||
module.save = lambda: mod_save(module, self.context.datas_path)
|
||||
else:
|
||||
module.DATAS = None
|
||||
module.save = lambda: False
|
||||
module.CONF = self.config
|
||||
module.has_access = lambda msg: mod_has_access(module,
|
||||
module.CONF, msg)
|
||||
|
||||
# Load dependancies
|
||||
if module.CONF is not None and module.CONF.hasNode("dependson"):
|
||||
module.MODS = dict()
|
||||
for depend in module.CONF.getNodes("dependson"):
|
||||
for md in MODS:
|
||||
if md.name == depend["name"]:
|
||||
mod.MODS[md.name] = md
|
||||
break
|
||||
if depend["name"] not in module.MODS:
|
||||
print ("\033[1;31mERROR:\033[0m in module `%s', module "
|
||||
"`%s' require by this module but is not loaded."
|
||||
% (module.name, depend["name"]))
|
||||
return
|
||||
|
||||
# Add the module to the global modules list
|
||||
if self.context.add_module(module):
|
||||
|
||||
# Launch the module
|
||||
if hasattr(module, "load"):
|
||||
module.load()
|
||||
|
||||
# Register hooks
|
||||
register_hooks(module, self.context, self.prompt)
|
||||
|
||||
print (" Module `%s' successfully loaded." % module.name)
|
||||
else:
|
||||
raise ImportError("An error occurs while importing `%s'."
|
||||
% module.name)
|
||||
return module
|
||||
|
||||
|
||||
def add_cap_hook(prompt, module, cmd):
|
||||
if hasattr(module, cmd["call"]):
|
||||
prompt.add_cap_hook(cmd["name"], getattr(module, cmd["call"]))
|
||||
else:
|
||||
print ("Warning: In module `%s', no function `%s' defined for `%s' "
|
||||
"command hook." % (module.name, cmd["call"], cmd["name"]))
|
||||
|
||||
def register_hooks(module, context, prompt):
|
||||
"""Register all available hooks"""
|
||||
if module.CONF is not None:
|
||||
# Register command hooks
|
||||
if module.CONF.hasNode("command"):
|
||||
for cmd in module.CONF.getNodes("command"):
|
||||
if cmd.hasAttribute("name") and cmd.hasAttribute("call"):
|
||||
add_cap_hook(prompt, module, cmd)
|
||||
|
||||
# Register message hooks
|
||||
if module.CONF.hasNode("message"):
|
||||
for msg in module.CONF.getNodes("message"):
|
||||
context.hooks.register_hook(module, msg)
|
||||
|
||||
##########################
|
||||
# #
|
||||
# Module functions #
|
||||
# #
|
||||
##########################
|
||||
|
||||
def mod_print_dbg(mod, msg):
|
||||
if mod.DEBUG:
|
||||
print("{%s} %s"%(mod.name, msg))
|
||||
|
||||
def mod_save(mod, datas_path):
|
||||
mod.DATAS.save(datas_path + "/" + mod.name + ".xml")
|
||||
mod.print ("Saving!")
|
||||
|
||||
def mod_has_access(mod, config, msg):
|
||||
if config is not None and config.hasNode("channel"):
|
||||
for chan in config.getNodes("channel"):
|
||||
if (chan["server"] is None or chan["server"] == msg.srv.id) and (
|
||||
chan["channel"] is None or chan["channel"] == msg.channel):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return True
|
89
message.py
89
message.py
@ -1,18 +1,30 @@
|
||||
# coding=utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 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
|
||||
from datetime import timedelta
|
||||
import imp
|
||||
import re
|
||||
import shlex
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
|
||||
from credits import Credits
|
||||
import credits
|
||||
dcc = __import__("DCC")
|
||||
imp.reload(dcc)
|
||||
from credits import Credits
|
||||
import DCC
|
||||
import xmlparser
|
||||
|
||||
CREDITS = {}
|
||||
filename = ""
|
||||
@ -145,13 +157,13 @@ class Message:
|
||||
return False
|
||||
return self.srv.accepted_channel(self.channel)
|
||||
|
||||
def treat(self, mods):
|
||||
def treat(self, hooks):
|
||||
if self.cmd == "PING":
|
||||
self.pong ()
|
||||
elif self.cmd == "PRIVMSG" and self.ctcp:
|
||||
self.parsectcp ()
|
||||
elif self.cmd == "PRIVMSG" and self.authorize():
|
||||
self.parsemsg (mods)
|
||||
self.parsemsg (hooks)
|
||||
elif self.channel in self.srv.channels:
|
||||
if self.cmd == "353":
|
||||
self.srv.channels[self.channel].parse353(self)
|
||||
@ -185,7 +197,7 @@ class Message:
|
||||
elif self.content == '\x01USERINFO\x01':
|
||||
self.srv.send_ctcp(self.sender, "USERINFO %s" % (self.srv.realname))
|
||||
elif self.content == '\x01VERSION\x01':
|
||||
self.srv.send_ctcp(self.sender, "VERSION nemubot v3")
|
||||
self.srv.send_ctcp(self.sender, "VERSION nemubot v%d"%VERSION)
|
||||
elif self.content[:9] == '\x01DCC CHAT':
|
||||
words = self.content[1:len(self.content) - 1].split(' ')
|
||||
ip = self.srv.toIP(int(words[3]))
|
||||
@ -201,45 +213,26 @@ class Message:
|
||||
self.srv.send_ctcp(self.sender, "ERRMSG Unknown or unimplemented CTCP request")
|
||||
|
||||
def reparsemsg(self):
|
||||
if self.mods is not None:
|
||||
self.parsemsg(self.mods)
|
||||
if self.hooks is not None:
|
||||
self.parsemsg(self.hooks)
|
||||
else:
|
||||
print ("Can't reparse message")
|
||||
|
||||
def parsemsg (self, mods):
|
||||
def parsemsg (self, hooks):
|
||||
#Treat all messages starting with 'nemubot:' as distinct commands
|
||||
if self.content.find("%s:"%self.srv.nick) == 0:
|
||||
#Remove the bot name
|
||||
self.content = self.content[len(self.srv.nick)+1:].strip()
|
||||
messagel = self.content.lower()
|
||||
|
||||
#Is it a simple response?
|
||||
if re.match(".*(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", messagel) is not None:
|
||||
self.send_chn ("%s: pong"%(self.nick))
|
||||
# Treat ping
|
||||
if re.match(".*(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)",
|
||||
messagel) is not None:
|
||||
self.send_chn ("%s: pong"%(self.nick))
|
||||
|
||||
elif re.match(".*(quel(le)? heure est[ -]il|what time is it)", messagel) is not None:
|
||||
now = datetime.now()
|
||||
self.send_chn ("%s: j'envoie ce message à %02d:%02d:%02d."%(self.nick, now.hour, now.minute, now.second))
|
||||
|
||||
elif re.match(".*di[st] (a|à) ([a-zA-Z0-9_]+) (.+)$", messagel) is not None:
|
||||
result = re.match(".*di[st] (a|à) ([a-zA-Z0-9_]+) (qu(e |'))?(.+)$", self.content)
|
||||
self.send_chn ("%s: %s"%(result.group(2), result.group(5)))
|
||||
elif re.match(".*di[st] (.+) (a|à) ([a-zA-Z0-9_]+)$", messagel) is not None:
|
||||
result = re.match(".*di[st] (.+) (à|a) ([a-zA-Z0-9_]+)$", self.content)
|
||||
self.send_chn ("%s: %s"%(result.group(3), result.group(1)))
|
||||
|
||||
elif re.match(".*di[st] sur (#[a-zA-Z0-9]+) (.+)$", self.content) is not None:
|
||||
result = re.match(".*di[st] sur (#[a-zA-Z0-9]+) (.+)$", self.content)
|
||||
self.send_msg(result.group(1), result.group(2))
|
||||
elif re.match(".*di[st] (.+) sur (#[a-zA-Z0-9]+)$", self.content) is not None:
|
||||
result = re.match(".*di[st] (.+) sur (#[a-zA-Z0-9]+)$", self.content)
|
||||
self.send_msg(result.group(2), result.group(1))
|
||||
|
||||
#Try modules
|
||||
# Ask hooks
|
||||
else:
|
||||
for im in mods:
|
||||
if im.has_access(self) and im.parseask(self):
|
||||
return
|
||||
hooks.treat_ask(self)
|
||||
|
||||
#Owner commands
|
||||
elif self.content[0] == '`' and self.sender == self.srv.owner:
|
||||
@ -264,7 +257,7 @@ class Message:
|
||||
|
||||
#Messages stating with !
|
||||
elif self.content[0] == '!' and len(self.content) > 1:
|
||||
self.mods = mods
|
||||
self.hooks = hooks
|
||||
try:
|
||||
self.cmd = shlex.split(self.content[1:])
|
||||
except ValueError:
|
||||
@ -297,19 +290,13 @@ class Message:
|
||||
conn = dcc.DCC(self.srv, self.sender)
|
||||
conn.send_file("bot_sample.xml")
|
||||
else:
|
||||
for im in mods:
|
||||
if im.has_access(self) and im.parseanswer(self):
|
||||
return
|
||||
hooks.treat_cmd(self)
|
||||
|
||||
else:
|
||||
for im in mods:
|
||||
if im.has_access(self) and im.parselisten(self):
|
||||
return
|
||||
#Assume the message starts with nemubot:
|
||||
if self.private:
|
||||
for im in mods:
|
||||
if im.has_access(self) and im.parseask(self):
|
||||
return
|
||||
hooks.treat_answer(self)
|
||||
# Assume the message starts with nemubot:
|
||||
if self.private:
|
||||
hooks.treat_ask(self)
|
||||
|
||||
# def parseOwnerCmd(self, cmd):
|
||||
|
||||
|
94
nemubot.py
94
nemubot.py
@ -1,42 +1,76 @@
|
||||
#!/usr/bin/python3
|
||||
# coding=utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 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 sys
|
||||
import os
|
||||
import imp
|
||||
import traceback
|
||||
|
||||
servers = dict()
|
||||
import bot
|
||||
import prompt
|
||||
from prompt.builtins import load_file
|
||||
import importer
|
||||
|
||||
prompt = __import__ ("prompt")
|
||||
if __name__ == "__main__":
|
||||
# Create bot context
|
||||
context = bot.Bot()
|
||||
|
||||
#Add modules dir path
|
||||
if os.path.isdir("./modules/"):
|
||||
modules_path = os.path.realpath(os.path.abspath("./modules/"))
|
||||
if modules_path not in sys.path:
|
||||
sys.path.insert(0, modules_path)
|
||||
# Load the prompt
|
||||
prmpt = prompt.Prompt()
|
||||
|
||||
#Load given files
|
||||
if len(sys.argv) >= 2:
|
||||
for arg in sys.argv[1:]:
|
||||
if os.path.isfile(arg):
|
||||
prompt.load_file(arg, servers)
|
||||
elif os.path.isdir(arg):
|
||||
sys.path.insert(1, arg)
|
||||
# Register the hook for futur import
|
||||
import sys
|
||||
sys.meta_path.append(importer.ModuleFinder(context, prmpt))
|
||||
|
||||
print ("Nemubot ready, my PID is %i!" % (os.getpid()))
|
||||
while prompt.launch(servers):
|
||||
try:
|
||||
if prompt.MODS is None:
|
||||
imp.reload(prompt)
|
||||
else:
|
||||
mods = prompt.MODS
|
||||
imp.reload(prompt)
|
||||
prompt.MODS = mods
|
||||
except:
|
||||
print ("Unable to reload the prompt due to errors. Fix them before trying to reload the prompt.")
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
sys.stdout.write (traceback.format_exception_only(exc_type, exc_value)[0])
|
||||
#Add modules dir path
|
||||
if os.path.isdir("./modules/"):
|
||||
context.add_modules_path(
|
||||
os.path.realpath(os.path.abspath("./modules/")))
|
||||
|
||||
print ("Bye")
|
||||
sys.exit(0)
|
||||
# Parse command line arguments
|
||||
if len(sys.argv) >= 2:
|
||||
for arg in sys.argv[1:]:
|
||||
if os.path.isdir(arg):
|
||||
context.add_modules_path(arg)
|
||||
else:
|
||||
load_file(arg, context)
|
||||
|
||||
print ("Nemubot v%s ready, my PID is %i!" % (context.version_txt,
|
||||
os.getpid()))
|
||||
while prmpt.run(context):
|
||||
try:
|
||||
# Reload context
|
||||
imp.reload(bot)
|
||||
context = bot.hotswap(context)
|
||||
# Reload prompt
|
||||
imp.reload(prompt)
|
||||
prmpt = prompt.hotswap(prmpt)
|
||||
# Reload all other modules
|
||||
bot.reload()
|
||||
print ("\033[1;32mContext reloaded\033[0m, now in Nemubot %s" %
|
||||
context.version_txt)
|
||||
except:
|
||||
print ("\033[1;31mUnable to reload the prompt due to errors.\033[0"
|
||||
"m Fix them before trying to reload the prompt.")
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
sys.stderr.write (traceback.format_exception_only(exc_type,
|
||||
exc_value)[0])
|
||||
|
||||
print ("Bye")
|
||||
sys.exit(0)
|
||||
|
105
prompt/__init__.py
Normal file
105
prompt/__init__.py
Normal file
@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 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 imp
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from . import builtins
|
||||
|
||||
class Prompt:
|
||||
def __init__(self, hc=dict(), hl=dict()):
|
||||
self.selectedServer = None
|
||||
|
||||
self.HOOKS_CAPS = hc
|
||||
self.HOOKS_LIST = hl
|
||||
|
||||
def add_cap_hook(self, name, call, data=None):
|
||||
self.HOOKS_CAPS[name] = (lambda d, t, c, p: call(d, t, c, p), data)
|
||||
|
||||
|
||||
def lex_cmd(self, line):
|
||||
"""Return an array of tokens"""
|
||||
ret = list()
|
||||
try:
|
||||
cmds = shlex.split(line)
|
||||
bgn = 0
|
||||
for i in range(0, len(cmds)):
|
||||
if cmds[i] == ';':
|
||||
if i != bgn:
|
||||
cmds[bgn] = cmds[bgn].lower()
|
||||
ret.append(cmds[bgn:i])
|
||||
bgn = i + 1
|
||||
|
||||
if bgn != len(cmds):
|
||||
cmds[bgn] = cmds[bgn].lower()
|
||||
ret.append(cmds[bgn:len(cmds)])
|
||||
|
||||
return ret
|
||||
except:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
sys.stderr.write (traceback.format_exception_only(
|
||||
exc_type, exc_value)[0])
|
||||
return ret
|
||||
|
||||
def exec_cmd(self, toks, context):
|
||||
"""Execute the command"""
|
||||
if toks[0] in builtins.CAPS:
|
||||
return builtins.CAPS[toks[0]](toks, context, self)
|
||||
elif toks[0] in self.HOOKS_CAPS:
|
||||
(f,d) = self.HOOKS_CAPS[toks[0]]
|
||||
return f(d, toks, context, self)
|
||||
else:
|
||||
print ("Unknown command: `%s'" % toks[0])
|
||||
return ""
|
||||
|
||||
def getPS1(self):
|
||||
"""Get the PS1 associated to the selected server"""
|
||||
if self.selectedServer is None:
|
||||
return "nemubot"
|
||||
else:
|
||||
return self.selectedServer.id
|
||||
|
||||
def run(self, context):
|
||||
"""Launch the prompt"""
|
||||
ret = ""
|
||||
while ret != "quit" and ret != "reset" and ret != "refresh":
|
||||
sys.stdout.write("\033[0;33m%s§\033[0m " % self.getPS1())
|
||||
sys.stdout.flush()
|
||||
|
||||
try:
|
||||
line = sys.stdin.readline()
|
||||
if len(line) <= 0:
|
||||
line = "quit"
|
||||
print ("quit")
|
||||
cmds = self.lex_cmd(line.strip())
|
||||
for toks in cmds:
|
||||
try:
|
||||
ret = self.exec_cmd(toks, context)
|
||||
except:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
traceback.print_exception(exc_type, exc_value, exc_traceback)
|
||||
except KeyboardInterrupt:
|
||||
print ("")
|
||||
return ret != "quit"
|
||||
|
||||
|
||||
def hotswap(prompt):
|
||||
return Prompt(prompt.HOOKS_CAPS, prompt.HOOKS_LIST)
|
140
prompt/builtins.py
Normal file
140
prompt/builtins.py
Normal file
@ -0,0 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 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 os
|
||||
import xmlparser
|
||||
|
||||
def end(toks, context, prompt):
|
||||
"""Quit the prompt for reload or exit"""
|
||||
if toks[0] == "refresh":
|
||||
return "refresh"
|
||||
elif toks[0] == "reset":
|
||||
return "reset"
|
||||
else:
|
||||
context.quit()
|
||||
return "quit"
|
||||
|
||||
|
||||
def liste(toks, context, prompt):
|
||||
"""Show some lists"""
|
||||
if len(toks) > 1:
|
||||
for l in toks[1:]:
|
||||
l = l.lower()
|
||||
if l == "server" or l == "servers":
|
||||
for srv in context.servers.keys():
|
||||
print (" - %s ;" % srv)
|
||||
else:
|
||||
print (" > No server loaded")
|
||||
elif l == "mod" or l == "mods" or l == "module" or l == "modules":
|
||||
for mod in context.modules.keys():
|
||||
print (" - %s ;" % mod)
|
||||
else:
|
||||
print (" > No module loaded")
|
||||
elif l in prompt.HOOKS_LIST:
|
||||
(f,d) = prompt.HOOKS_LIST[l]
|
||||
f(d, context, prompt)
|
||||
else:
|
||||
print (" Unknown list `%s'" % l)
|
||||
else:
|
||||
print (" Please give a list to show: servers, ...")
|
||||
|
||||
|
||||
def load_file(filename, context):
|
||||
if os.path.isfile(filename):
|
||||
config = xmlparser.parse_file(filename)
|
||||
|
||||
# This is a true nemubot configuration file, load it!
|
||||
if (config.getName() == "nemubotconfig"
|
||||
or config.getName() == "config"):
|
||||
# Preset each server in this file
|
||||
for server in config.getNodes("server"):
|
||||
if context.addServer(server, config["nick"],
|
||||
config["owner"], config["realname"]):
|
||||
print (" Server `%s:%s' successfully added."
|
||||
% (server["server"], server["port"]))
|
||||
else:
|
||||
print (" Server `%s:%s' already added, skiped."
|
||||
% (server["server"], server["port"]))
|
||||
|
||||
# Load files asked by the configuration file
|
||||
for load in config.getNodes("load"):
|
||||
load_file(load["path"], context)
|
||||
|
||||
# This is a nemubot module configuration file, load the module
|
||||
elif config.getName() == "nemubotmodule":
|
||||
__import__(config["name"])
|
||||
|
||||
# Other formats
|
||||
else:
|
||||
print (" Can't load `%s'; this is not a valid nemubot "
|
||||
"configuration file." % filename)
|
||||
|
||||
# Unexisting file, assume a name was passed, import the module!
|
||||
else:
|
||||
__import__(filename)
|
||||
|
||||
|
||||
def load(toks, context, prompt):
|
||||
"""Load an XML configuration file"""
|
||||
if len(toks) > 1:
|
||||
for filename in toks[1:]:
|
||||
load_file(filename, context)
|
||||
else:
|
||||
print ("Not enough arguments. `load' takes a filename.")
|
||||
return
|
||||
|
||||
|
||||
def select(toks, context, prompt):
|
||||
"""Select the current server"""
|
||||
if (len(toks) == 2 and toks[1] != "None"
|
||||
and toks[1] != "nemubot" and toks[1] != "none"):
|
||||
if toks[1] in context.servers:
|
||||
prompt.selectedServer = context.servers[toks[1]]
|
||||
else:
|
||||
print ("select: server `%s' not found." % toks[1])
|
||||
else:
|
||||
prompt.selectedServer = None
|
||||
return
|
||||
|
||||
|
||||
def unload(toks, context, prompt):
|
||||
"""Unload a module"""
|
||||
if len(toks) == 2 and toks[1] == "all":
|
||||
for name in context.modules.keys():
|
||||
context.unload_module(name)
|
||||
elif len(toks) > 1:
|
||||
for name in toks[1:]:
|
||||
if context.unload_module(name):
|
||||
print (" Module `%s' successfully unloaded." % name)
|
||||
else:
|
||||
print (" No module `%s' loaded, can't unload!" % name)
|
||||
else:
|
||||
print ("Not enough arguments. `unload' takes a module name.")
|
||||
|
||||
|
||||
#Register build-ins
|
||||
CAPS = {
|
||||
'quit': end, #Disconnect all server and quit
|
||||
'exit': end, #Alias for quit
|
||||
'reset': end, #Reload the prompt
|
||||
'refresh': end, #Reload the prompt but save modules
|
||||
'load': load, #Load a servers or module configuration file
|
||||
'unload': unload, #Unload a module and remove it from the list
|
||||
'select': select, #Select a server
|
||||
'list': liste, #Show lists
|
||||
}
|
41
server.py
41
server.py
@ -1,16 +1,30 @@
|
||||
import imp
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 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 socket
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import time
|
||||
|
||||
message = __import__("message")
|
||||
imp.reload(message)
|
||||
channel = __import__("channel")
|
||||
imp.reload(channel)
|
||||
dcc = __import__("DCC")
|
||||
imp.reload(dcc)
|
||||
import channel
|
||||
import DCC
|
||||
import message
|
||||
import xmlparser
|
||||
|
||||
class Server(threading.Thread):
|
||||
def __init__(self, node, nick, owner, realname, socket = None):
|
||||
@ -216,22 +230,19 @@ class Server(threading.Thread):
|
||||
else:
|
||||
return False
|
||||
|
||||
def update_mods(self, mods):
|
||||
self.mods = mods
|
||||
|
||||
def launch(self, mods):
|
||||
def launch(self, context, verb=True):
|
||||
"""Connect to the server if it is no yet connected"""
|
||||
self.context = context
|
||||
if not self.connected:
|
||||
self.stop = False
|
||||
self.mods = mods
|
||||
self.start()
|
||||
else:
|
||||
elif verb:
|
||||
print (" Already connected.")
|
||||
|
||||
def treat_msg(self, line, private = False):
|
||||
try:
|
||||
msg = message.Message (self, line, private)
|
||||
msg.treat (self.mods)
|
||||
msg.treat(self.context.hooks)
|
||||
except:
|
||||
print ("\033[1;31mERROR:\033[0m occurred during the processing of the message: %s" % line)
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
|
@ -1,12 +1,26 @@
|
||||
#!/usr/bin/python3
|
||||
# coding=utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 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 os
|
||||
import imp
|
||||
import xml.sax
|
||||
|
||||
module_state = __import__("module_state")
|
||||
imp.reload(module_state)
|
||||
from . import node as module_state
|
||||
|
||||
class ModuleStatesFile(xml.sax.ContentHandler):
|
||||
def startDocument(self):
|
Loading…
Reference in New Issue
Block a user