-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathzenircbot_api.py
224 lines (182 loc) · 8.58 KB
/
zenircbot_api.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
220
221
222
223
224
"""ZenIRCBot API"""
# These are at the top to ensure gevent can monkey patch before
# threading gets imported.
from gevent import monkey
monkey.patch_all()
import atexit
import json
import gevent
from redis import StrictRedis
__version__ = '2.2.7'
def load_config(name):
""" Loads a JSON file and returns an object.
:param string name: The JSON file to load.
:returns: An native object with the contents of the JSON file.
This is a helper so you don't have to do the file IO and JSON
parsing yourself.
"""
with open(name) as f:
return json.loads(f.read())
class ZenIRCBot(object):
"""Instantiates a new ZenIRCBot API object.
:param string host: Redis hostname (default: 'localhost')
:param integer port: Redis port (default: 6379)
:param integer db: Redis DB number (default: 0)
:param string name: Name for the service using this instance
:returns: ZenIRCBot instance
Takes Redis server parameters to use for instantiating Redis
clients.
"""
def __init__(self, host='localhost', port=6379, db=0, name="bot"):
self.host = host
self.port = port
self.db = db
self.service_name = name
self.redis = StrictRedis(host=self.host,
port=self.port,
db=self.db)
self.commands = [] # list of command callbacks
def send_privmsg(self, to, message):
"""Sends a message to the specified channel(s)
:param to: A list or a string, if it is a list it will send to
all the people or channels listed.
:param string message: The message to send.
This is a helper so you don't have to handle the JSON or the
envelope yourself.
"""
if isinstance(to, basestring):
to = (to,)
for channel in to:
self.get_redis_client().publish('out',
json.dumps({
'version': 1,
'type': 'privmsg',
'data': {
'to': channel,
'message': message,
}}))
def send_action(self, to, message):
"""Sends an "ACTION" message to the specified channel(s)
:param to: A list or a string, if it is a list it will send to
all the people or channels listed.
:param string message: The message to send.
This is a helper so you don't have to handle the JSON or the
envelope yourself.
"""
if isinstance(to, basestring):
to = (to,)
for channel in to:
self.get_redis_client().publish('out',
json.dumps({
'version': 1,
'type': 'privmsg_action',
'data': {
'to': channel,
'message': message,
}}))
def send_admin_message(self, message):
"""
:param string message: The message to send.
This is a helper function that sends the message to all of the
channels defined in ``admin_spew_channels``.
"""
admin_channels = self.redis.get('zenircbot:admin_spew_channels')
if admin_channels:
self.send_privmsg(admin_channels, message)
def non_blocking_redis_subscribe(self, func, args=[], kwargs={}):
pubsub = self.get_redis_client().pubsub()
pubsub.subscribe('in')
for msg in pubsub.listen():
if msg['type'] == 'message':
message = json.loads(msg['data'])
func(message=message, *args, **kwargs)
def register_commands(self, service, commands):
"""
:param string script: The script with extension that you are
registering.
:param list commands: A list of objects with name and description
attributes used to reply to
a commands query.
This will notify all ``admin_spew_channels`` of the script
coming online when the script registers itself. It will also
setup a subscription to the 'out' channel that listens for
'commands' to be sent to the bot and responds with the list of
script, command name, and command description for all
registered scripts.
"""
self.send_admin_message(service + ' online!')
if commands:
def registration_reply(message, service, commands):
if message['version'] == 1:
if message['type'] == 'directed_privmsg':
if message['data']['message'] == 'commands':
for command in commands:
self.send_privmsg(message['data']['sender'],
'%s: %s - %s' % (
service,
command['name'],
command['description']
))
elif message['data']['message'] == 'services':
self.send_privmsg(message['data']['sender'],
service)
greenlet = gevent.spawn(self.non_blocking_redis_subscribe,
func=registration_reply,
kwargs={
'service': service,
'commands': commands
})
# Ensures that the greenlet is cleaned up.
atexit.register(lambda gl: gl.kill(), greenlet)
def get_redis_client(self):
""" Get redis client using values from instantiation time."""
return StrictRedis(host=self.host,
port=self.port,
db=self.db)
def simple_command(self, commandstr, desc="a command", **options):
""" A decorator to register a command as a callback when the command
is triggered. The unction must take one argument, which is the message
text.
@zen.simple_command("ping"):
def ping(msg):
return "pong"
:param string commandstr: string to use as a command
:param string desc: optional description text
:returns: decorated function
This should be used in conjuction with ZenIRCBot.listen() which will
finish the registration process and starts listening to redis for
messages
"""
# make decorator, f is the wrapped function passed through **options
def decorator(f):
self.commands.append({'str': commandstr,
'desc': desc,
'name': f.__name__,
'callback': f,
})
return f
return decorator
def listen(self):
""" Start listening. This is blocking and should be the last line in a
service. Once this is called the service is running.
"""
# actually register commands
self.register_commands(self.service_name,
[{'name': '!'+c['str'], 'description': c['desc']} for c in self.commands]
)
# boilerplate pubsub listening
subscription = self.get_redis_client().pubsub()
subscription.subscribe('in')
for msg in subscription.listen():
if msg.get('type') == 'message':
message = json.loads(msg['data'])
if message['version'] == 1:
if message['type'] == 'directed_privmsg':
text = message['data']['message']
# Look for any command matches in registered commands
for command in self.commands:
# match command
if text.startswith(command['str']):
# do callback and send returned value to channel
self.send_privmsg(message['data']['channel'],
command['callback'](text))