Add eval/exec to RPC

This commit is contained in:
dpedu 2015-08-08 15:04:45 -07:00
parent c2e2199b02
commit 5a511944bd
4 changed files with 605 additions and 510 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ build
pyircbot.egg-info pyircbot.egg-info
dev dev
docs/builder/build.sh docs/builder/build.sh
examples/config.test.json

View File

@ -47,7 +47,7 @@ We can retrieve an arbitrary property from a module:
[True, {'apikey': 'deadbeefcafe', 'defaultUnit': 'f'}] [True, {'apikey': 'deadbeefcafe', 'defaultUnit': 'f'}]
>>> >>>
Or run a method in a module, passing args: Run a method in a module, passing args:
.. code-block:: python .. code-block:: python
@ -55,6 +55,71 @@ Or run a method in a module, passing args:
[True, {'definition': "Loyal, unlike its predecessor", 'word': 'rhobot2', 'by': 'xMopxShell'}] [True, {'definition': "Loyal, unlike its predecessor", 'word': 'rhobot2', 'by': 'xMopxShell'}]
>>> >>>
Or simply pass a string to eval() or exec() to do anything. In this case,
retrieving a full stack trace of the bot, which is useful during module
development:
.. code-block:: python
>>> print( rpc.eval("self.bot.irc.trace()")[1] )
*** STACKTRACE - START ***
# ThreadID: 140289192748800
File: "/usr/lib/python3.4/threading.py", line 888, in _bootstrap
self._bootstrap_inner()
File: "/usr/lib/python3.4/threading.py", line 920, in _bootstrap_inner
self.run()
File: "/usr/lib/python3.4/threading.py", line 1184, in run
self.finished.wait(self.interval)
File: "/usr/lib/python3.4/threading.py", line 552, in wait
signaled = self._cond.wait(timeout)
File: "/usr/lib/python3.4/threading.py", line 293, in wait
gotit = waiter.acquire(True, timeout)
# ThreadID: 140289297204992
File: "/usr/lib/python3.4/threading.py", line 888, in _bootstrap
self._bootstrap_inner()
File: "/usr/lib/python3.4/threading.py", line 920, in _bootstrap_inner
self.run()
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/pyircbot/rpc.py", line 51, in run
self.server.serve()
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/pyircbot/jsonrpc.py", line 1110, in serve
self.__transport.serve( self.handle, n )
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/pyircbot/jsonrpc.py", line 851, in serve
result = handler(data)
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/pyircbot/jsonrpc.py", line 1086, in handle
result = self.funcs[method]( *params )
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/pyircbot/rpc.py", line 167, in eval
return (True, eval(code))
File: "<string>", line 1, in <module>
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/pyircbot/irccore.py", line 288, in trace
for filename, lineno, name, line in traceback.extract_stack(stack):
# ThreadID: 140289333405504
File: "/usr/local/bin/pyircbot", line 5, in <module>
pkg_resources.run_script('pyircbot==4.0.0-r02', 'pyircbot')
File: "/usr/lib/python3/dist-packages/pkg_resources.py", line 528, in run_script
self.require(requires)[0].run_script(script_name, ns)
File: "/usr/lib/python3/dist-packages/pkg_resources.py", line 1394, in run_script
execfile(script_filename, namespace, namespace)
File: "/usr/lib/python3/dist-packages/pkg_resources.py", line 55, in execfile
exec(compile(open(fn).read(), fn, 'exec'), globs, locs)
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/EGG-INFO/scripts/pyircbot", line 32, in <module>
bot.loop()
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/pyircbot/pyircbot.py", line 68, in loop
self.irc.loop()
File: "/usr/local/lib/python3.4/dist-packages/pyircbot-4.0.0_r02-py3.4.egg/pyircbot/irccore.py", line 56, in loop
asyncore.loop(map=self.asynmap)
File: "/usr/lib/python3.4/asyncore.py", line 208, in loop
poll_fun(timeout, map)
File: "/usr/lib/python3.4/asyncore.py", line 145, in poll
r, w, e = select.select(r, w, e, timeout)
*** STACKTRACE - END ***
>>>
Careful, you can probably crash the bot by tweaking the wrong things. Only Careful, you can probably crash the bot by tweaking the wrong things. Only
basic types can be passed over the RPC connection. Trying to access anything basic types can be passed over the RPC connection. Trying to access anything
extra results in an error: extra results in an error:

View File

@ -11,6 +11,7 @@ import asynchat
import asyncore import asyncore
import logging import logging
import traceback import traceback
import sys
from socket import SHUT_RDWR from socket import SHUT_RDWR
try: try:
@ -279,7 +280,19 @@ class IRCCore(asynchat.async_chat):
@staticmethod @staticmethod
def trace(): def trace():
"""Return the stack trace of the bot as a string""" """Return the stack trace of the bot as a string"""
return traceback.format_exc() result = ""
result += "\n*** STACKTRACE - START ***\n"
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# ThreadID: %s" % threadId)
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(" %s" % (line.strip()))
for line in code:
result += line + "\n"
result += "\n*** STACKTRACE - END ***\n"
return result
" Data Methods " " Data Methods "
def get_nick(self): def get_nick(self):

View File

@ -41,6 +41,8 @@ class BotRPC(Thread):
self.server.register_function( self.setPluginVar ) self.server.register_function( self.setPluginVar )
self.server.register_function( self.getPluginVar ) self.server.register_function( self.getPluginVar )
self.server.register_function( self.quit ) self.server.register_function( self.quit )
self.server.register_function( self.eval )
self.server.register_function( self.exec )
self.start() self.start()
@ -157,6 +159,20 @@ class BotRPC(Thread):
setattr(plugin, moduleVarName, value) setattr(plugin, moduleVarName, value)
return (True, "Var set") return (True, "Var set")
def eval(self, code):
"""Execute arbitrary python code on the bot
:param code: Python code to pass to eval
:type code: str"""
return (True, eval(code))
def exec(self, code):
"""Execute arbitrary python code on the bot
:param code: Python code to pass to exec
:type code: str"""
return (True, exec(code))
def quit(self, message): def quit(self, message):
"""Tell the bot to quit IRC and exit """Tell the bot to quit IRC and exit