oonbotti2/ircbot.py

149 lines
4.8 KiB
Python
Raw Normal View History

2017-09-04 19:49:01 +00:00
#!/usr/bin/env python3
2017-09-04 21:10:10 +00:00
import select
2017-09-04 19:49:01 +00:00
import socket
import threading
from collections import namedtuple
import channel
from constants import logmessage_types, internal_submessage_types, controlmessage_types
2017-09-04 19:49:01 +00:00
Server = namedtuple('Server', ['host', 'port'])
# ServerThread(server, control_socket)
# Creates a new server main loop thread
class ServerThread(threading.Thread):
def __init__(self, server, control_channel, logging_channel):
2017-09-04 19:49:01 +00:00
self.server = server
self.control_channel = control_channel
self.logging_channel = logging_channel
2017-09-04 19:49:01 +00:00
self.server_socket_write_lock = threading.Lock()
2017-09-04 19:49:01 +00:00
threading.Thread.__init__(self)
2017-09-04 21:10:10 +00:00
def send_line_raw(self, line):
# Sanitize line just in case
line = line.replace(b'\r', b'').replace(b'\n', b'')[:510]
with self.server_socket_write_lock:
self.server_socket.sendall(line + b'\r\n')
2017-09-04 21:10:10 +00:00
self.logging_channel.send((logmessage_types.sent, line.decode(encoding = 'utf-8', errors = 'replace')))
2017-09-04 21:10:10 +00:00
def handle_line(self, line):
# TODO: implement line handling
self.logging_channel.send((logmessage_types.received, line.decode(encoding = 'utf-8', errors = 'replace')))
2017-09-04 21:10:10 +00:00
def mainloop(self):
# Register both the server socket and the control channel to or polling object
2017-09-04 21:10:10 +00:00
poll = select.poll()
poll.register(self.server_socket, select.POLLIN)
poll.register(self.control_channel, select.POLLIN)
2017-09-04 21:10:10 +00:00
# Keep buffer for input
2017-09-04 21:10:10 +00:00
server_input_buffer = bytearray()
quitting = False
while not quitting:
# Wait until we can do something
for fd, event in poll.poll():
# Server
if fd == self.server_socket.fileno():
# Ready to receive, read into buffer and handle full messages
if event | select.POLLIN:
data = self.server_socket.recv(1024)
server_input_buffer.extend(data)
# Try to see if we have a full line ending with \r\n in the buffer
# If yes, handle it
while b'\r\n' in server_input_buffer:
2017-09-04 21:10:10 +00:00
# Newline was found, split buffer
line, _, server_input_buffer = server_input_buffer.partition(b'\r\n')
self.handle_line(line)
# Control
elif fd == self.control_channel.fileno():
command_type, *arguments = self.control_channel.recv()
if command_type == controlmessage_types.quit:
2017-09-04 21:10:10 +00:00
quitting = True
elif command_type == controlmessage_types.send_line:
assert len(arguments) == 1
irc_command, space, arguments = arguments[0].encode('utf-8').partition(b' ')
line = irc_command.upper() + space + arguments
self.send_line_raw(line)
else:
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error))
2017-09-04 21:10:10 +00:00
else:
assert False #unreachable
2017-09-04 19:49:01 +00:00
def run(self):
# Connect to given server
address = (self.server.host, self.server.port)
2017-09-04 21:10:10 +00:00
try:
self.server_socket = socket.create_connection(address)
except ConnectionRefusedError:
# Tell controller we failed
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error))
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.quit))
return
2017-09-04 21:10:10 +00:00
# Run initialization
# TODO: read nick/username/etc. from a config
self.send_line_raw(b'NICK HynneFlip')
self.send_line_raw(b'USER HynneFlip a a :HynneFlip IRC bot')
# Run mainloop
self.mainloop()
# Tell the server we're quiting
self.send_line_raw(b'QUIT :HynneFlip exiting normally')
self.server_socket.close()
2017-09-04 19:49:01 +00:00
2017-09-04 21:10:10 +00:00
# Tell controller we're quiting
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.quit))
2017-09-04 19:49:01 +00:00
# spawn_serverthread(server) → control_channel, logging_channel
# Creates a ServerThread for given server and returns the channels for controlling and monitoring it
2017-09-04 19:49:01 +00:00
def spawn_serverthread(server):
thread_control_socket, spawner_control_socket = socket.socketpair()
control_channel = channel.Channel()
logging_channel = channel.Channel()
ServerThread(server, control_channel, logging_channel).start()
return (control_channel, logging_channel)
2017-09-04 19:49:01 +00:00
if __name__ == '__main__':
control_channel, logging_channel = spawn_serverthread(Server('irc.freenode.net', 6667))
2017-09-04 21:10:10 +00:00
while True:
cmd = input(': ')
if cmd == '':
2017-09-05 08:03:41 +00:00
while True:
data = logging_channel.recv(blocking = False)
if data == None:
break
message_type, message_data = data
if message_type == logmessage_types.sent:
print('>' + message_data)
elif message_type == logmessage_types.received:
print('<' + message_data)
elif message_type == logmessage_types.internal:
if message_data == internal_submessage_types.quit:
print('--- Quit')
elif message_data == internal_submessage_types.error:
print('--- Error')
else:
print('--- ???', message_data)
else:
print('???', message_type, message_data)
2017-09-04 21:10:10 +00:00
elif cmd == 'q':
control_channel.send((controlmessage_types.quit,))
2017-09-04 21:10:10 +00:00
break
elif len(cmd) > 0 and cmd[0] == '/':
control_channel.send((controlmessage_types.send_line, cmd[1:]))