A simple Cards Against Humanity bot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
3.1 KiB

import threading
import constants
import botcmd
class LineParsingError(Exception): None
# parse_line(line) → prefix, command, arguments
# Split the line into its component parts
def parse_line(line):
def read_byte():
# Read one byte and advance the index
nonlocal line, index
if eol():
raise LineParsingError
byte = line[index]
index += 1
return byte
def peek_byte():
# Look at current byte, don't advance index
nonlocal line, index
if eol():
raise LineParsingError
return line[index]
def eol():
# Test if we've reached the end of the line
nonlocal line, index
return index >= len(line)
def skip_space():
# Skip until we run into a non-space character or eol.
while not eol() and peek_byte() == ord(' '):
read_byte()
def read_until_space():
nonlocal line, index
if eol():
raise LineParsingError
# Try to find a space
until = line[index:].find(b' ')
if until == -1:
# Space not found, read until end of line
until = len(line)
else:
# Space found, add current index to it to get right index
until += index
# Slice line upto the point of next space / end and update index
data = line[index:until]
index = until
return data
def read_until_end():
nonlocal line, index
if eol():
raise LineParsingError
# Read all of the data, and make index point to eol
data = line[index:]
index = len(line)
return data
index = 0
prefix = None
command = None
arguments = []
if peek_byte() == ord(':'):
read_byte()
prefix = read_until_space()
skip_space()
command = read_until_space()
skip_space()
while not eol():
if peek_byte() == ord(':'):
read_byte()
argument = read_until_end()
else:
argument = read_until_space()
arguments.append(argument)
skip_space()
return prefix, command, arguments
class LineHandlerThread(threading.Thread):
def __init__(self, line, *, irc):
self.line = line
self.irc = irc
threading.Thread.__init__(self)
def run(self):
try:
prefix, command, arguments = parse_line(self.line)
except LineParsingError:
irc.error("Cannot parse line: " + self.line.decode(encoding = 'utf-8', errors = 'replace'))
if command.upper() == b'PRIVMSG':
# PRIVMSG should have two parameters: recipient and the message
assert len(arguments) == 2
recipients, message = arguments
# Prefix contains the nick of the sender, delimited from user and host by '!'
nick = prefix.split(b'!')[0]
# Recipients are in a comma-separate list
for recipient in recipients.split(b','):
# 'channel' is bit of a misnomer. This is where we'll send the response to
# Usually it's the channel, but in queries it's not
channel = recipient if recipient[0] == ord('#') else nick
# Delegate rest to botcmd.handle_message
botcmd.handle_message(prefix = prefix, message = message, nick = nick, channel = channel, irc = self.irc)
else:
# Delegate to botcmd.handle_nonmessage
botcmd.handle_nonmessage(prefix = prefix, command = command, arguments = arguments, irc = self.irc)
def handle_line(line, *, irc):
# Spawn a thread to handle the line
LineHandlerThread(line, irc = irc).start()