-
Notifications
You must be signed in to change notification settings - Fork 0
/
hubot.py
221 lines (178 loc) · 7 KB
/
hubot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import sys
from config import CHATROOM_PRESENCE
from errbot import botcmd, BotPlugin
import coffeescript
import re
import logging
from spidermonkey import Runtime, JSError, Object
from os import path, sep
from random import choice
import urllib, urllib2
import json
def load_nodejsdep(name):
package = json.load(urllib.urlopen("http://registry.npmjs.org/"+name))
last_version = max(package['versions'].keys()) # hackish
file = package['versions'][last_version]['dist']['tarball']
def numerotatedJS(js):
return '\n'.join(('%3i: %s' % (line, text) for line, text in enumerate(js.split('\n'), 1)))
class JSONStub(object):
def stringify(self, str):
return json.dumps(str)
def parse(self, str):
return json.loads(str)
class HubotHttp(object):
def __init__(self, url):
self.url = url
self.query_dict = None
def query(self, coffee_dict):
self.query_dict = {k: coffee_dict[k] for k in coffee_dict}
return self
def get(self):
err = None
response = None
res = None
try:
url = self.url + '?' + urllib.urlencode(self.query_dict) if self.query_dict else self.url
response = urllib2.urlopen(url).read()
except urllib2.URLError as error:
err = error.reason
return lambda f : f(err, res, response)
class HubotMessage(object):
"""Emulates the behavior of a hubot message
"""
def __init__(self, callback, mess, match):
self.callback = callback
self.mess = mess
matchs = [mess, ]
matchs.extend(match.groups())
self.match = matchs
def send(self, msg):
logging.debug("Hubot send: " + msg)
room = CHATROOM_PRESENCE[0]
self.callback.send(room, msg, message_type='groupchat')
def random(self, array):
return choice(array)
def reply(self, text):
logging.debug("Hubot reply: " + text)
self.callback.send(self.mess.getFrom(), text, self.mess)
def http(self, url):
return HubotHttp(url)
class HubotModule(object):
"""Emulates the behavior of a hubot module
"""
exports = None
def config_get_attr(self, name):
return self.cb.config.get(name, None)
class HubotEnv(object):
"""Emulates the behavior of a hubot environment
"""
def __init__(self, cb):
self.cb = cb
__getattr__ = config_get_attr
__getitem__ = config_get_attr
class HubotProcess(object):
"""Emulates the behavior of a hubot process
"""
def __init__(self, cb):
self.env = HubotEnv(cb)
class Hubot(BotPlugin):
# Store here the patterns to listen to
hear_matchers = {}
js_cache = {}
def activate(self):
super(Hubot, self).activate()
self.process = HubotProcess(self)
self.rt = Runtime()
if not self.get('scripts', None):
self['scripts'] = {}
else:
for name, snippet in self['scripts'].iteritems():
logging.debug("Inserting %s... " % name)
self.add_snippet(name, snippet)
def callback_message(self, conn, mess):
logging.debug("Hubot is hearing [%s]" % mess.getBody())
try:
for pattern in self.hear_matchers:
match = re.match(pattern, mess.getBody())
if match:
self.hear_matchers[pattern](HubotMessage(self, mess, match))
except JSError as jse:
logging.exception("Error interpreting Javascript")
exc_type, exc_value, exc_traceback = sys.exc_info()
tb_next = exc_traceback
js_error = '\n\n Guessed stacktrack from JS:'
while tb_next:
code = tb_next.tb_frame.f_code
if code.co_name == 'JavaScript code':
js = self.js_cache[code.co_filename]
ln = code.co_firstlineno
lines = js.split('\n')
js_error += '\n\n ' + lines[ln - 2 ] + '\n-->' + lines[ln - 1] + '\n ' + lines[ln]
tb_next = tb_next.tb_next
self.send(mess.getFrom(), str(jse) + js_error, mess)
def hear(self, pattern, function):
"""The hubot callback to register a listening function
"""
pattern = repr(pattern)
first_slash = pattern.index('/')
last_slash = pattern.rindex('/')
regexp = pattern[first_slash+1:last_slash]
modifiers = pattern[last_slash:]
logging.debug("Registering a hubot snippet %s -> %s" % (regexp, repr(function)))
self.hear_matchers[regexp] = function
def respond(self, pattern, function):
"""The hubot callback to register a listening function to himself only
TODO dissociate from hear
"""
pattern = repr(pattern)
first_slash = pattern.index('/')
last_slash = pattern.rindex('/')
regexp = pattern[first_slash+1:last_slash]
modifiers = pattern[last_slash:]
logging.debug("Registering a hubot snippet %s -> %s" % (regexp, repr(function)))
self.hear_matchers[regexp] = function
def add_snippet(self, name, coffee):
#logging.debug("Trying to insert this gloubiboulga [%s]" % coffee)
logging.debug("Creating a face Hubot context...")
def require(module_name):
logging.debug("Trying to load " + module_name)
module = HubotModule()
cx = self.rt.new_context()
cx.add_global("module", module)
cx.add_global("process", self.process)
cx.add_global("require", require)
cx.add_global("JSON", JSONStub())
logging.debug("Compiling coffeescript...")
js = coffeescript.compile(coffee, bare=True)
nummed_js = numerotatedJS(js)
self.js_cache[name] = nummed_js
logging.debug("Translated JS:\n" + nummed_js)
logging.debug("Executing Hubot script...")
cx.execute(code = js, filename = name)
module.exports(self) # triggers the listening callbacks
@botcmd
def hubot_add(self, mess, args):
"""Adds a hubot script in the bot
takes an url has parameter directly from the row github file for example : !hubot add https://raw.github.com/github/hubot-scripts/master/src/scripts/botsnack.coffee
"""
script_name = args.split('/')[-1].replace('.coffee', '')
res = urllib2.urlopen(args)
script = res.read()
logging.debug("Adding script %s -> %s" % (script_name, script))
copy = self['scripts']
copy[script_name] = script
self['scripts'] = copy
self.add_snippet(script_name, script)
return 'Script %s added.' % script_name
@botcmd
def hubot_del(self, mess, args):
"""remove a hubot script in from the bot. You need to restart the hubot plugin to make it effective.
takes the name of the script for example : !hubot del botsnack
"""
copy = self['scripts']
copy.pop(args)
self['scripts'] = copy
return 'Done'
@botcmd
def hubot_list(self, mess, args):
return '\n'.join(self['scripts'].keys())