-
Notifications
You must be signed in to change notification settings - Fork 0
/
spiffybot.py
executable file
·313 lines (256 loc) · 10.2 KB
/
spiffybot.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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Brian Cottingham
# spiffyech@gmail.com
#This file is part of Spiffybot.
#
#Spiffybot is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#Spiffybot 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 General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with Spiffybot. If not, see <http://www.gnu.org/licenses/>.
import os
import random
import re
import readline
from sqlite3 import dbapi2 as sqlite
import string
import sys
import time
import traceback
import commands
from createDB import createDB
import irclib
import ircTools
from misc import misc
#from tell import tell
from modules import *
# IRC connection information
network = 'irc.freenode.net'
port = 6667
channels = ['##bottest',]
nick = 'spiffybot'
realName = 'spiffybot'
# Enable debug mode if the appropriate command line parameter was passed
if len(sys.argv) > 1 and sys.argv[1] == "--debug":
DEBUG = True
else:
DEBUG = False
# Open the database
dbName = "logs.db"
if not os.path.exists(dbName):
createDB(dbName)
dbConn = sqlite.connect(dbName)
cursor = dbConn.cursor()
def main():
# Create an IRC object
irc = irclib.IRC()
if DEBUG:
irclib.DEBUG = True # Uncomment this to dump all irclib events to stdout
# Create a server object, connect and join the channels
global server
server = irc.server()
server.connect(network, port, nick, ircname = realName)
joinChannels()
# Add handler functions for various IRC events
irc.add_global_handler("pubmsg", handleMessage)
# irc.add_global_handler("ctcp", handleMessage) # Commented out until I fix bug that prevents this from logging properly
irc.add_global_handler("privmsg", handleMessage)
irc.add_global_handler("join", handleJoin)
irc.add_global_handler("part", handlePart)
irc.add_global_handler("quit", handleQuit)
irc.add_global_handler("kick", handleKick)
irc.add_global_handler("topic", handleTopic)
irc.add_global_handler("nicknameinuse", changeNick)
# Fork off our child process for operator input
pid = os.fork()
if pid:
termInput(server)
pid = os.fork()
if pid:
watchLoop(server)
# In the parent process, start listening for connections
while 1:
if not DEBUG: # Debug CLI arg disables crash message, enables regular Python exception printing, shows irclib raw data
try:
irc.process_forever()
except KeyboardInterrupt:
break
except:
printException()
server.privmsg("spiffytech", "I crashed!")
else:
irc.process_forever()
class ircEvent:
def __init__(self, connection, event, args):
self.connection = connection
self.eventType = event.eventtype()
self.nick = nick
self.user = event.sourceuser() # User who instigated an event (join, part, pubmsg, etc.)
self.sender = event.source().split("!")[0]
self.args = args
if event.target() == self.nick: # private messages
self.channel = self.sender
else:
self.channel = event.target()
def reply(self, message):
self.connection.privmsg(self.channel, message)
def setNick(self, newNick):
self.connection.nick(newNick)
global nick
nick = newNick
def printException(maxTBlevel=5):
'''This code is copy/pasted. I don't know how it works, and it doesn't work completely.'''
cla, exc, trbk = sys.exc_info()
excName = cla.__name__
try:
excArgs = exc.__dict__["args"]
except KeyError:
excArgs = "<no args>"
excTb = traceback.format_tb(trbk, maxTBlevel)
print excName
print excArgs
print "\n".join(excTb)
def changeNick(connection=None, event=None, newNick=None):
'''If the bot's nick is used, this function generates another nick'''
if connection == None and event == None: # Called within newBot.py, not from an event handler
connection.nick(newNick)
global nick
nick = newNick
else:
newNick = ""
if len(connection.nickname) < 15:
newNick = connection.nickname + "_"
connection.nick(newNick)
joinChannels(connection)
nick = newNick
else:
chars = string.letters + string.numbers
random.seed(time.time)
for i in range(0, random.randint(2, len(string.digits)-1)):
newNick += chars[random.randint(0, len("".letters)-1)]
connection.nick(newNick)
joinChannels(connection)
nick = newNick
########## Functions to handle channel user connection events ##########
# All of these functions just call recordEvent, but are here in case I ever want do do more than that when handling events
def handleJoin(connection, event):
event = ircEvent(connection, event, args=None)
tell.deliverMessages(event)
recordEvent(event)
def handlePart(connection, event):
event = ircEvent(connection, event, args=None)
recordEvent(event)
def handleQuit(connection, event):
event = ircEvent(connection, event, args=None)
recordEvent(event)
def handleKick(connection, event):
event = ircEvent(connection, event, args=None)
recordEvent(event)
def recordEvent(event):
'''Log channel all connection events to the database. There's no real reason for it presently; just doing it for kicks and giggles.'''
global dbConn
global cursor
user = event.user.decode("utf-8")
alteredTime = str(time.time())
cursor.execute("insert into connevents values (?, ?, ?, ?)", (user, event.eventType, event.channel, alteredTime))
dbConn.commit()
########################################################################
def handleTopic(connection, event):
'''Log topic changes'''
# TODO: Log topic when first entering a channel
global dbConn
global cursor
alterer = event.sourceuser().decode("utf-8") # Who changed the topic
topic = event.arguments()[0].decode("utf-8") # New topic
alteredTime = str(time.time())
cursor.execute("insert into topic_history values (?, ?, ?, ?)", (topic, alteredTime, alterer, event.target()))
dbConn.commit()
def handleMessage(connection, event):
'''Someone sent a message to a channel!'''
# Parse the raw IRC data contents
sender = event.sourceuser().decode("utf-8") # Who sent the message
message = event.arguments()[0].decode("utf-8") # Get the channel's new message and split it for parsing
# BEFORE ANYTHING ELSE, record the message in our logs
global dbConn
global cursor
cursor.execute("insert into messages values (?, ?, ?, ?)", (sender, event.target(), message, unicode(time.time())))
dbConn.commit()
# First, see if this triggers a message delivery for whoever just spoke
tell.deliverMessages(ircEvent(connection, event, args=None))
# # Next, check for echoes
# ircTools.echo(ircEvent(connection, event, None))
# See if the message corresponds to any (not-a-command) patterns we care about
for command in commands.anyMessage:
if DEBUG:
print command[0]
r = re.search(command[0], message, re.IGNORECASE)
if r != None:
try:
args = r.group("args").strip()
except:
args = None
execString = command[1] + "(ircEvent(connection, event, args))" # Using a string instead of storing the function facilitates the planned automatic module loading feature.
eval(execString)
# Next, see if the message is something we care about (i.e., a command)
if re.match(nick + "[:\-, ]", message) or event.eventtype() == "privmsg": # If it's a command for us:
# ^^ startswith: "spiffybot: calc" || privmsg part ensures this code is triggered if the message is a PM
if not event.eventtype() == "privmsg":
message = message.partition(" ")[2] # No nick at the front of a private message
# Run the command
foundMatch = False
for command in commands.cmds:
print command[0]
r = re.search(command[0], message, re.IGNORECASE)
if r != None:
foundMatch = True
args = r.group("args").strip()
execString = command[1] + "(ircEvent(connection, event, args))" # Using a string instead of storing the function facilitates the planned automatic module loading feature.
eval(execString)
if foundMatch == False:
connection.privmsg(event.target(), "Maybe") # Respond with this if we don't have anything else to respond with
def cmdJoin(event):
event.connection.join(event.args)
def cmdNick(event):
event.setNick(event.args)
def cmdPart(event):
event.connection.part(event.channel)
def updateCommands():
'''Reload the main commands file'''
# Currently broken
reload(commands)
def joinChannels(connection=None, event=None):
# Join all specified channels at program launch
# In Separate function to facilitate joins after nick collision
for channel in channels:
server.join(channel)
def termInput(conn):
'''Child process that reads input from the user and sends it to the channels'''
if not "channel" in locals():
channel = channels[0]
while 1:
msg = raw_input("Talk to channel: ") # Get a message from the user
if msg.startswith("="): # Set the channel we want to talk to
# Examples: "=#ncsulug", "=#ncsulug Send this message!"
msg = msg.split()
channel = msg[0].split("=")[1]
if len(msg) > 1:
conn.privmsg(channel, " ".join(msg[1:]))
continue
conn.privmsg(channel, msg)
def watchLoop(connection):
while 1:
time.sleep(3)
# tell.deliverMessages(ircEvent(connection, event=None, args=None))
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print "Exiting..."