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
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@ -7,17 +7,15 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
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
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
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
|
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
|
software for all its users.
|
||||||
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.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
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
|
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.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
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:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
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
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@ -72,7 +60,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
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
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
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
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
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
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
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
|
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,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
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
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
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
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
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.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
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
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
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>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
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/>.
|
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.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
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
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
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".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
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.
|
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/>.
|
<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
|
85
message.py
85
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 datetime
|
||||||
from datetime import timedelta
|
|
||||||
import imp
|
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import string
|
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from credits import Credits
|
|
||||||
import credits
|
import credits
|
||||||
dcc = __import__("DCC")
|
from credits import Credits
|
||||||
imp.reload(dcc)
|
import DCC
|
||||||
|
import xmlparser
|
||||||
|
|
||||||
CREDITS = {}
|
CREDITS = {}
|
||||||
filename = ""
|
filename = ""
|
||||||
@ -145,13 +157,13 @@ class Message:
|
|||||||
return False
|
return False
|
||||||
return self.srv.accepted_channel(self.channel)
|
return self.srv.accepted_channel(self.channel)
|
||||||
|
|
||||||
def treat(self, mods):
|
def treat(self, hooks):
|
||||||
if self.cmd == "PING":
|
if self.cmd == "PING":
|
||||||
self.pong ()
|
self.pong ()
|
||||||
elif self.cmd == "PRIVMSG" and self.ctcp:
|
elif self.cmd == "PRIVMSG" and self.ctcp:
|
||||||
self.parsectcp ()
|
self.parsectcp ()
|
||||||
elif self.cmd == "PRIVMSG" and self.authorize():
|
elif self.cmd == "PRIVMSG" and self.authorize():
|
||||||
self.parsemsg (mods)
|
self.parsemsg (hooks)
|
||||||
elif self.channel in self.srv.channels:
|
elif self.channel in self.srv.channels:
|
||||||
if self.cmd == "353":
|
if self.cmd == "353":
|
||||||
self.srv.channels[self.channel].parse353(self)
|
self.srv.channels[self.channel].parse353(self)
|
||||||
@ -185,7 +197,7 @@ class Message:
|
|||||||
elif self.content == '\x01USERINFO\x01':
|
elif self.content == '\x01USERINFO\x01':
|
||||||
self.srv.send_ctcp(self.sender, "USERINFO %s" % (self.srv.realname))
|
self.srv.send_ctcp(self.sender, "USERINFO %s" % (self.srv.realname))
|
||||||
elif self.content == '\x01VERSION\x01':
|
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':
|
elif self.content[:9] == '\x01DCC CHAT':
|
||||||
words = self.content[1:len(self.content) - 1].split(' ')
|
words = self.content[1:len(self.content) - 1].split(' ')
|
||||||
ip = self.srv.toIP(int(words[3]))
|
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")
|
self.srv.send_ctcp(self.sender, "ERRMSG Unknown or unimplemented CTCP request")
|
||||||
|
|
||||||
def reparsemsg(self):
|
def reparsemsg(self):
|
||||||
if self.mods is not None:
|
if self.hooks is not None:
|
||||||
self.parsemsg(self.mods)
|
self.parsemsg(self.hooks)
|
||||||
else:
|
else:
|
||||||
print ("Can't reparse message")
|
print ("Can't reparse message")
|
||||||
|
|
||||||
def parsemsg (self, mods):
|
def parsemsg (self, hooks):
|
||||||
#Treat all messages starting with 'nemubot:' as distinct commands
|
#Treat all messages starting with 'nemubot:' as distinct commands
|
||||||
if self.content.find("%s:"%self.srv.nick) == 0:
|
if self.content.find("%s:"%self.srv.nick) == 0:
|
||||||
#Remove the bot name
|
#Remove the bot name
|
||||||
self.content = self.content[len(self.srv.nick)+1:].strip()
|
self.content = self.content[len(self.srv.nick)+1:].strip()
|
||||||
messagel = self.content.lower()
|
messagel = self.content.lower()
|
||||||
|
|
||||||
#Is it a simple response?
|
# Treat ping
|
||||||
if re.match(".*(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)", messagel) is not None:
|
if re.match(".*(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)",
|
||||||
|
messagel) is not None:
|
||||||
self.send_chn ("%s: pong"%(self.nick))
|
self.send_chn ("%s: pong"%(self.nick))
|
||||||
|
|
||||||
elif re.match(".*(quel(le)? heure est[ -]il|what time is it)", messagel) is not None:
|
# Ask hooks
|
||||||
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
|
|
||||||
else:
|
else:
|
||||||
for im in mods:
|
hooks.treat_ask(self)
|
||||||
if im.has_access(self) and im.parseask(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
#Owner commands
|
#Owner commands
|
||||||
elif self.content[0] == '`' and self.sender == self.srv.owner:
|
elif self.content[0] == '`' and self.sender == self.srv.owner:
|
||||||
@ -264,7 +257,7 @@ class Message:
|
|||||||
|
|
||||||
#Messages stating with !
|
#Messages stating with !
|
||||||
elif self.content[0] == '!' and len(self.content) > 1:
|
elif self.content[0] == '!' and len(self.content) > 1:
|
||||||
self.mods = mods
|
self.hooks = hooks
|
||||||
try:
|
try:
|
||||||
self.cmd = shlex.split(self.content[1:])
|
self.cmd = shlex.split(self.content[1:])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -297,19 +290,13 @@ class Message:
|
|||||||
conn = dcc.DCC(self.srv, self.sender)
|
conn = dcc.DCC(self.srv, self.sender)
|
||||||
conn.send_file("bot_sample.xml")
|
conn.send_file("bot_sample.xml")
|
||||||
else:
|
else:
|
||||||
for im in mods:
|
hooks.treat_cmd(self)
|
||||||
if im.has_access(self) and im.parseanswer(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for im in mods:
|
hooks.treat_answer(self)
|
||||||
if im.has_access(self) and im.parselisten(self):
|
# Assume the message starts with nemubot:
|
||||||
return
|
|
||||||
#Assume the message starts with nemubot:
|
|
||||||
if self.private:
|
if self.private:
|
||||||
for im in mods:
|
hooks.treat_ask(self)
|
||||||
if im.has_access(self) and im.parseask(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
# def parseOwnerCmd(self, cmd):
|
# def parseOwnerCmd(self, cmd):
|
||||||
|
|
||||||
|
92
nemubot.py
92
nemubot.py
@ -1,42 +1,76 @@
|
|||||||
#!/usr/bin/python3
|
#!/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 sys
|
||||||
import os
|
import os
|
||||||
import imp
|
import imp
|
||||||
import traceback
|
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
|
# Load the prompt
|
||||||
if os.path.isdir("./modules/"):
|
prmpt = prompt.Prompt()
|
||||||
modules_path = os.path.realpath(os.path.abspath("./modules/"))
|
|
||||||
if modules_path not in sys.path:
|
|
||||||
sys.path.insert(0, modules_path)
|
|
||||||
|
|
||||||
#Load given files
|
# Register the hook for futur import
|
||||||
if len(sys.argv) >= 2:
|
import sys
|
||||||
|
sys.meta_path.append(importer.ModuleFinder(context, prmpt))
|
||||||
|
|
||||||
|
#Add modules dir path
|
||||||
|
if os.path.isdir("./modules/"):
|
||||||
|
context.add_modules_path(
|
||||||
|
os.path.realpath(os.path.abspath("./modules/")))
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
if len(sys.argv) >= 2:
|
||||||
for arg in sys.argv[1:]:
|
for arg in sys.argv[1:]:
|
||||||
if os.path.isfile(arg):
|
if os.path.isdir(arg):
|
||||||
prompt.load_file(arg, servers)
|
context.add_modules_path(arg)
|
||||||
elif os.path.isdir(arg):
|
|
||||||
sys.path.insert(1, arg)
|
|
||||||
|
|
||||||
print ("Nemubot ready, my PID is %i!" % (os.getpid()))
|
|
||||||
while prompt.launch(servers):
|
|
||||||
try:
|
|
||||||
if prompt.MODS is None:
|
|
||||||
imp.reload(prompt)
|
|
||||||
else:
|
else:
|
||||||
mods = prompt.MODS
|
load_file(arg, context)
|
||||||
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])
|
|
||||||
|
|
||||||
print ("Bye")
|
print ("Nemubot v%s ready, my PID is %i!" % (context.version_txt,
|
||||||
sys.exit(0)
|
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 socket
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
import time
|
|
||||||
|
|
||||||
message = __import__("message")
|
import channel
|
||||||
imp.reload(message)
|
import DCC
|
||||||
channel = __import__("channel")
|
import message
|
||||||
imp.reload(channel)
|
import xmlparser
|
||||||
dcc = __import__("DCC")
|
|
||||||
imp.reload(dcc)
|
|
||||||
|
|
||||||
class Server(threading.Thread):
|
class Server(threading.Thread):
|
||||||
def __init__(self, node, nick, owner, realname, socket = None):
|
def __init__(self, node, nick, owner, realname, socket = None):
|
||||||
@ -216,22 +230,19 @@ class Server(threading.Thread):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update_mods(self, mods):
|
def launch(self, context, verb=True):
|
||||||
self.mods = mods
|
|
||||||
|
|
||||||
def launch(self, mods):
|
|
||||||
"""Connect to the server if it is no yet connected"""
|
"""Connect to the server if it is no yet connected"""
|
||||||
|
self.context = context
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
self.stop = False
|
self.stop = False
|
||||||
self.mods = mods
|
|
||||||
self.start()
|
self.start()
|
||||||
else:
|
elif verb:
|
||||||
print (" Already connected.")
|
print (" Already connected.")
|
||||||
|
|
||||||
def treat_msg(self, line, private = False):
|
def treat_msg(self, line, private = False):
|
||||||
try:
|
try:
|
||||||
msg = message.Message (self, line, private)
|
msg = message.Message (self, line, private)
|
||||||
msg.treat (self.mods)
|
msg.treat(self.context.hooks)
|
||||||
except:
|
except:
|
||||||
print ("\033[1;31mERROR:\033[0m occurred during the processing of the message: %s" % line)
|
print ("\033[1;31mERROR:\033[0m occurred during the processing of the message: %s" % line)
|
||||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
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 os
|
||||||
import imp
|
import imp
|
||||||
import xml.sax
|
import xml.sax
|
||||||
|
|
||||||
module_state = __import__("module_state")
|
from . import node as module_state
|
||||||
imp.reload(module_state)
|
|
||||||
|
|
||||||
class ModuleStatesFile(xml.sax.ContentHandler):
|
class ModuleStatesFile(xml.sax.ContentHandler):
|
||||||
def startDocument(self):
|
def startDocument(self):
|
Loading…
Reference in New Issue
Block a user