forked from michael-ranieri/Misty
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmisty_core.py
207 lines (155 loc) · 6.87 KB
/
misty_core.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
#!/usr/bin/env python
# Copyright (C) 2011 Michael Ranieri <michael.d.ranieri at gmail.com>
# Twisted imports
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol, defer
from twisted.python import log
from twisted.enterprise import adbapi
# System imports
import time, sys, random, os, re
# Misty Imports
import lighthouse
import settings_local as settings
# END OF IMPORTS
def _getMessage(txn, arg):
txn.execute("SELECT * FROM Messages WHERE %s") % arg
result = txn.fetchall()
if result:
return result
else:
return None
# Get a message from PostgreSQL Asynchronously
def getMessage(arg):
return cp.runInteraction(_getMessage, arg)
def _setMessage(txn, message, user, channel, id):
txn.execute('INSERT INTO Messages VALUES (%s, %s, %s, %s)', (message, user, channel, id))
return
# Store a message into PostgreSQL Asynchronously
def setMessage(message, user, channel, id):
return cp.runInteraction(_setMessage, message, user, channel, id)
# Main class for Misty Bot. Handles messages, connections, etc.
class Misty(irc.IRCClient):
"""A asynchronous IRC Bot."""
nickname = settings.NICKNAME # nickname for Misty in irc channel
password = settings.PASSWORD # password to join irc server
def connectionMade(self):
irc.IRCClient.connectionMade(self)
self.reload()
def connectionLost(self, reason):
irc.IRCClient.connectionLost(self, reason)
# Reloads the lighthouse module, which points to the isles.
def reload(self):
reload(lighthouse)
self.isles = lighthouse.isles
# callbacks for events
def signedOn(self):
"""Called when Misty has successfully signed on to a server."""
self.join(self.factory.channel)
def joined(self, channel):
"""Called when Misty has joined a channel."""
msg = "Hi! I'm Misty. Nice to meet all of you!"
self.msg(channel, msg)
def privmsg(self, user, channel, msg):
"""This will get called when the bot receives a message from IRC server."""
user = user.split('!', 1)[0]
# Appends a random int to the end of a timestamp in the form of seconds since Epoch.
# This does have a very small chance of making a non unique id.
randint = str(random.getrandbits(50))
timestamp = str(int(time.time()))
id = timestamp + "!" + randint
# Stores the current message into PostgreSQL
setMessage(msg, user, channel, id)
# Reload lighthouse
if msg.startswith(self.nickname + ":reload"):
msg = "Finding more Isles in the Mist."
self.msg(channel, msg)
self.reload()
return
if msg.startswith(self.nickname + ":"):
msg = "%s: Hi, I'm %s. Michael Ranieri created me." % (user, self.nickname)
self.msg(channel, msg)
return
# Check to see if message should be sent to an isle
# Isles must return a tuple (bool, string, string)
for isle in self.isles:
goto, location, filename = isle(msg, user, channel)
if goto == True and filename != None and location != None:
# Initialize Process Controller
MistyProcess = MistyProcessController()
p = reactor.spawnProcess(
MistyProcess, # Process Controller
settings.PATH_TO_ISLES + location, # Full Path of Isle
[filename, msg, user, channel], # Filename of Isle
{'HOME': os.environ['HOME']}) # ENV to run Isle
isleResult = MistyProcess.deferred
isleResult.addCallback(self.callbackMessage, channel)
# method to switch callbacks argument ordering
def callbackMessage(self, msg, channel):
self.msg(channel,msg)
# Creates instances of Misty for each connection
class MistyFactory(protocol.ClientFactory):
"""A Factory for Misty instances
A new protocol instance of Misty will be created each time we connect to the server(s)
"""
# Misty class will be the protocol to build when new connection is made
protocol = Misty
def __init__(self, channel):
self.channel = channel
def startedConnecting(self, connector):
"""Called when Misty is trying to connect to server"""
print "Trying to connect"
def clientConnectionLost(self, connector, reason):
"""If Misty gets disconnecte, reconnect to server."""
connector.connect()
def clientConnectionFailed(self, connector, reason):
print "Could not connect to server:", reason
reactor.stop()
# Controls the subprocess for each Isle
class MistyProcessController(protocol.ProcessProtocol):
"""A Process Controller that uses ProcessProtocol to handle pipes asynchronously"""
def __init__(self):
self.data = ""
self.errors = ""
self.deferred = defer.Deferred()
# Misty sends msg through arg instead of Stdin
# so we immediately tell the process to close Stdin
def connectionMade(self):
self.transport.closeStdin()
# outRecieved() is called with the output from each Isle process.
def outReceived(self, data):
self.data += data
# any errors recieved from Isle process is caught here.
def errReceived(self, data):
self.errors += data
# called when process closes its Stdin
def inConnectionLost(self):
pass
# This is called when the Isle process has finished and closes its Stdout
def outConnectionLost(self):
pass
# This is called when the Isle process has closed the error output.
def errConnectionLost(self):
if self.errors != "":
print "Errors:"
print self.errors
def processExited(self, reason):
pass
# This is called after the Isle process has ended
def processEnded(self, reason):
self.deferred.callback(self.data)
if __name__ == '__main__':
# create factory protocol and application
mf = MistyFactory(settings.CHANNEL)
# create connection pool for misty to log messages to database
cp = adbapi.ConnectionPool("pyPgSQL.PgSQL",
None,
settings.DATABASE_USER,
settings.DATABASE_PASSWORD,
settings.DATABASE_URL,
settings.DATABASE_NAME)
# connect factory to this host and port
reactor.connectTCP(settings.SERVER,
settings.PORT,
mf)
# run Misty
reactor.run()