forked from eisenbes/LPMCBot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbotlib.py
More file actions
423 lines (379 loc) · 17 KB
/
botlib.py
File metadata and controls
423 lines (379 loc) · 17 KB
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
import sys
import os
import random
import socket
import re
import ConfigParser
from sys import argv, exc_info
from os import environ, makedirs
from math import *
from time import strftime
from tttlib import *
# Admin name(s) for certain commands
# Usage:
# if sender in ADMINS:
# myfunc()
ADMINS = ["SlimTim10", "Z_Mass", "intothev01d", "naxir"]
def split_privmsg(privmsg):
# Split the received PRIVMSG message into two useful parts
# Example message:
# :SlimTim10!~SlimTim10@127-0-0-1.network.com PRIVMSG #channel :Hello?
parts = privmsg[1:].split(':', 1)
# The information part of the message (sender, "PRIVMSG", channel/nickname)
info = parts[0].split(' ')
msg = parts[1].rstrip() # The message part (e.g., "Hello?")
# The sender of the message (e.g., "SlimTim10")
sender = info[0].split('!')[0]
return info, msg, sender
def parsemsg(info, msg, sender):
# The string to be returned
ret = ''
# Treat messages starting with '!' as commands (e.g., "!say hi")
if msg[0] == '!':
# Split command message into two parts: bot command and following text
cmd = msg.split(' ', 1)
# The '!say' command makes the bot say something,
# assuming a message is provided
if cmd[0] == '!say' and len(cmd) > 1:
# Bot will respond to !say command via private message if
# privmsg is not "PRIVMSG + #channel + cmd"
if info[2][0] != '#':
ret = 'PRIVMSG ' + sender + ' :' + cmd[1] + '\n'
# Send the message to where the '!say' command was sent
else:
ret = 'PRIVMSG ' + info[2] + ' :' + cmd[1] + '\n'
# --- Utilities ---
# The following is the implementation of commands which alter usermodes
# !<command> <nickname>
# This command will change the mode of the sender if no paramter is given, otherwise
# it will modify the specified user. (admin command)
# Syntax of message sent to irc server: MODE #channel +-ov nickname
modeCommands = { '!op': '+o', '!deop': '-o', '!voice': '+v', '!devoice': '-v' }
if cmd[0] in modeCommands.keys() and sender in ADMINS:
# Begin forming return string by writing: MODE channelname
ret = 'MODE ' + info[2]
# Next, specify the mode
ret += ' ' + modeCommands[cmd[0]] + ' '
# Finally, specify target
if len(cmd) == 1:
ret += sender
else:
ret += cmd[1]
return ret + "\n" # we should be all done now.
# The '!die' command makes the bot quit (admin command)
if cmd[0] == '!die' and sender in ADMINS:
ret = 'QUIT\n'
# The '!add_admin' command adds an admin
if cmd[0] == "!add_admin" and sender in ADMINS:
try:
user_to_add = cmd[1]
ADMINS.append(user_to_add)
ret = 'PRIVMSG ' + info[2] + \
' :'+user_to_add+' added as admin\n'
except:
ret = 'PRIVMSG ' + info[2] + \
' :Command help: Specify a user\n'
# The '!remove_admin' command removes an admin
if cmd[0] == "!remove_admin" and sender in ADMINS:
try:
user_to_remove = cmd[1]
ADMINS.remove(user_to_remove)
ret = 'PRIVMSG ' + info[2] + \
' :'+user_to_remove+' removed as admin\n'
except:
ret = 'PRIVMSG ' + info[2] + \
' :Command help: Specify an admin\n'
# The '!list_admins' lists all the Admins
if cmd[0] == '!list_admins':
ret = 'PRIVMSG ' + info[2] + \
' :Admins:'+ ", ".join(ADMINS) + '\n'
# --- End Utilities ---
# The '!calc' command evaluates basic mathematical expressions
if cmd[0] == '!calc':
#try evaluating user input
try:
# exlude __builtins__ to prevent access to globals that aren't
# needed and create dictionary of all math functions from the math module
# functions compatible from 2.5.2 and up
safe_dict = {'__builtins__':None, 'abs':abs, 'acos':acos,
'asin':asin, 'atan':atan, 'atan2':atan2, 'ceil':ceil,
'cos':cos, 'cosh':cosh, 'degrees':degrees, 'e':e,
'exp':exp, 'fabs':fabs, 'floor':floor, 'fmod':fmod,
'frexp':frexp, 'hypot':hypot, 'ldexp':ldexp, 'log':log,
'log10':log10, 'modf':modf, 'pi':pi, 'pow':pow,
'radians':radians, 'sin':sin, 'sinh':sinh, 'sqrt':sqrt,
'tan':tan, 'tanh':tanh}
# if command is !calc math print list of available math functions
if cmd[1] == 'math':
ret = 'PRIVMSG ' + info[2] + \
' :' + str(safe_dict.keys()) + '\n'
# otherwise evaluate user input while passing in safe globals
# dictionary and no locals
else:
user_input = eval(cmd[1],safe_dict,{})
ret = 'PRIVMSG ' + info[2] + \
' :' + str(user_input) + '\n'
# throws exception on garbage input
except:
ret = 'PRIVMSG ' + info[2] + \
' :Command help: Enter only numbers and valid mathematical functions ' + \
' Example: !calc 2+2 or !calc abs(-2) ' + \
' For a list of available math fuctions use !calc math\n'
# The '!insult' command prints out a randomly selected insult from a list.
if cmd[0] == '!insult':
# Tupple containing all the insults.
# More can be added without modifying any other code.
insults = ("Hey, you be quiet now!",
"What are you? An idiot!?",
"I know where you live..",
"You cur!",
"You insolent cad!",
"I challenge you to a duel at dawn!",
"So what, wanna fight about it?",
"I fart in your general direction.",
"Your mother was a hamster and your father smelt of elderberries.",
"You're a jerk, a complete kneebiter!")
# Pick a random element in the insults tuple
choice = random.choice(insults)
# Return the insult
ret = 'PRIVMSG ' + info[2] + ' :' + choice + '\n'
# To-do: add helpful comments to this command's code
# The !rps command initializes a game of rock-paper-scissors.
if cmd[0] == '!rps':
try:
user_rps = int(cmd[1])
if user_rps < 0 or user_rps > 2:
raise Exception("Invalid")
else:
rps_names = ['rock', 'paper', 'scissors']
bot_rps = random.randint(0, 2)
ret = 'PRIVMSG ' + info[2] + ' :Player chose ' + \
rps_names[user_rps] + '. LPMCBot chose ' + \
rps_names[bot_rps] + '. '
if user_rps == bot_rps:
ret += 'Tie game.\n'
elif user_rps == (bot_rps + 1) % 3:
ret += 'Player wins!\n'
else:
ret += 'Player loses.\n'
except:
ret = 'PRIVMSG ' + info[2] + \
' :Command help: 0 = Rock, 1 = Paper, 2 = Scissors. ' + \
'Example: !rps 1\n'
# To-do: add A.I. for the bot rather than random picking
# Difficulty: hard
if cmd[0] == '!ttt':
try:
user_ttt = int(cmd[1])
if user_ttt < 0 or user_ttt > 9:
raise Exception("Invalid")
winner = 0
availableSpaces = []
for i in range(len(TICTACTOE)): #this checks for open spaces
if (TICTACTOE[i] == '_'): #it's run after every cmd
availableSpaces.append(i+1)
if (user_ttt == 0): #this initializes the board
for i in range(len(TICTACTOE)):
TICTACTOE.pop(0)
for i in range(9):
TICTACTOE.append('_')
ret = 'PRIVMSG ' + info[2] + \
' :' + 'New Game started! To play, type !ttt followed ' + \
'by a number. Example: "!ttt 3" for top right corner.' + '\n'
elif (user_ttt in availableSpaces): #this handles the player's move
TICTACTOE[user_ttt - 1] = 'O'
availableSpaces.remove(user_ttt)
output_lines = []
for i in range(9):
output_lines.append(TICTACTOE[i])
winner = tttWinCheck()
if (len(availableSpaces) and winner == 0): #the following handles bot's move
print str(TICTACTOE)
botPick = tttAI()
TICTACTOE[botPick - 1] = 'X'
availableSpaces.remove(botPick)
for i in range(9):
output_lines[i] += TICTACTOE[i]
if (winner == 0):winner = tttWinCheck()
# Output game board in 3 lines of messages
ret = 'PRIVMSG ' + info[2] + ' :'
for i in range(0, 9, 3):
for j in range(len(output_lines[0])):
for k in range(3):
ret = ret + output_lines[k+i][j] + ' '
ret = ret + ' '
ret = ret + '\nPRIVMSG ' + info[2] + ' :'
ret += '\n'
if (len(availableSpaces) == 0 and len(TICTACTOE) > 0 and winner == 0):
ret = 'PRIVMSG ' + info[2] + ' : Tie game! Start a new game with !ttt 0.\n'
for i in range(len(TICTACTOE)):
TICTACTOE.pop(0)
elif(len(TICTACTOE)):
ret = 'PRIVMSG ' + info[2] + ' : Space is already taken.' + '\n'
if (winner != 0):
ret = ret + 'PRIVMSG ' + info[2] + ' : The winner is ' + winner + '!\n'
for i in range(len(TICTACTOE)):
TICTACTOE.pop(0)
except Exception, err:
#How-To stuff
print str(err)
ret = 'PRIVMSG ' + info[2] + \
' :Command help: Tic-Tac-Toe. ' + \
'To start a new game: !ttt 0\n'
# The following shall look for websites posted in chat. It will then
# obtain the Title of the page and return a message to the user to be sent
# The following is a regex to match basic websites.
# group(1) shall be the server address (e.g. www.reddit.com)
# group(2) shall be the page to request (e.g. /r/lpmc)
# Note: This regex will not match the following example sites:
# http://m.reddit.com, http://www.google.co.uk
website = re.match('^http://((?:www\.)?\w+\.\w{2,3})(/.*)?',msg)
if website:
# creates socket object
sock = socket.socket()
try:
# Connect to webserver on port 80
sock.connect((website.group(1), 80))
# Connection will timeout after 5 seconds. This was initially set
# to 1 second, but frequent timeouts were experienced.
sock.settimeout(5)
# if no specific page is specified after domain, request /
if website.group(2) == None:
sock.send('GET / HTTP/1.0\r\n')
else:
sock.send('GET ' +website.group(2)+' HTTP/1.0\r\n')
sock.send('HOST: ' + website.group(1) + '\r\n\r\n')
# This will store the full page when we are finished
source = ''
# buff shall store 512 bytes of the page at a time
buff = sock.recv(512)
# keep receiving information, storing into the buffer, and then
# concatenating source until buff == ''
while buff != '':
source += buff
buff = sock.recv(512)
# find the title of the page.
titleRegex = re.compile('.*<title>(.+)</title>.*',
re.DOTALL).match(source)
# and finally, set ret to equal our message reporting our findings.
ret = 'PRIVMSG ' + info[2] + ' :Title: ' + titleRegex.group(1) + '\n'
except:
print "Error:", exc_info()[0] # in case of exception, print error.
pass
sock.close()
return ret # Return the appropriate string
def setConfig():
"""Return a dict containing bot config values."""
# This function will set the config values one of three ways
# Default values as a backup, if there are environment variables
# set those will be used, unless the first argument passed to lpmcbot.py
# is -i or --interactive, then the user will be prompted for the settings.
# Check if an argument was passed
flags = ''
long_args = ''
try:
# If first char is -, assume it is a group of flags, ex: '-il'
if argv[1][0] == '-' and argv[1][1] != '-':
# Remove the '-'
flags = argv[1][1:]
# Is the first arg a long arg? ex: '--log'
elif argv[1][:2] == '--':
long_args = argv
except IndexError:
pass
# -- Startup settings --
if 'i' in flags or '--interactive' in long_args:
# Prompt for values
print "Welcome to the LPMC Bot Interative startup.\n"
print "A few settings must be entered before we can start.\n"
NICK = raw_input("NICK: ")
USER = raw_input("USER: ")
REALNAME = NICK
CHANNEL = raw_input("CHANNEL: ")
# Add User to ADMINS list (to let User use '!die' command)
add_admin = raw_input("ADMIN: ")
ADMINS.append(add_admin)
print "Thank you. Starting up the bot.\n"
# -- Config file settings --
elif ('c' in flags or '--config' in long_args) and len(argv) >= 3:
config_name = argv[2]
config = ConfigParser.RawConfigParser()
config.read(config_name)
NICK = config.get('MAIN', 'NICK')
USER = config.get('MAIN', 'USER')
REALNAME = config.get('MAIN', 'REALNAME')
CHANNEL = config.get('MAIN', 'CHANNEL')
del ADMINS[:]
ADMINS.extend(config.get('MAIN', 'ADMINS').replace(' ', '').split(','))
print "Startup Settings retrieved from config file"
else:
try:
# Check for environment variables
NICK = environ['NICK']
USER = environ['USER']
REALNAME = environ['NICK']
CHANNEL = environ['CHANNEL']
print "Initializing using environment variables.\n"
except KeyError:
# Fall back on defaults
NICK = "LPMCBot"
USER = "LPMCbot"
REALNAME = "LPMCBot"
CHANNEL = "#LPMCBot"
print "Initializing using default values.\n"
# -- Logging settings --
if 'l' in flags or '--log' in long_args:
LOG = True
else:
try:
# Check for LOG env var seperate from essential settings
LOG = environ['LOG']
except KeyError:
LOG = False
config_values = {'NICK': NICK,
'USER': USER,
'REALNAME': REALNAME,
'CHANNEL': CHANNEL,
'LOG': LOG}
return config_values
# -- Logging functions --
def open_log_file(server, channel):
"""Open the log file and return it"""
# Get the name of the server and create a log directory
log_dir = 'log_files/%s' % server.split('.')[1]
try:
makedirs(log_dir)
except OSError as e:
if e.errno == 17:
# The dir already exists
pass
else:
raise e
# Create path to file using channel (without '#')
# Example: "log_files/freenode/lpmc.log"
file_path = '%s/%s.log' % (log_dir, channel[1:])
log = open(file_path, 'a')
return log
def write_log_header(bot_nick, log_file):
"""Create a header and write to the log file"""
# Header message
msg = strftime('\t* %c *\n') + '\t* Logged in as: %s *\n' % bot_nick
# Create border
longest = len(max(msg.split('\n'), key=len))
border = '\t%s\n' % ('*' * (longest - 1))
# Assemble the header
header = border + msg + border
log_file.write(header)
def log_event(msg, sender, log_file):
"""Log an event to current channel's log file."""
# Create a timestamp, example format: '02:45 PM |'
timestamp = strftime('%I:%M %p |\t')
# Avoid logging garbage
if 'PREFIX=(ov)@+' in msg:
pass
else:
log_file.write(timestamp + sender + '\t' + msg + '\n')
def end_log_session(log_file):
"""Delimit each log session and close file."""
log_file.write('\n\t\t***** ***** *****\n\n')
log_file.close()