Compare commits
No commits in common. "master" and "master" have entirely different histories.
|
@ -1,11 +1,10 @@
|
||||||
[server]
|
[server]
|
||||||
host = irc.libera.chat
|
host = irc.freenode.net
|
||||||
port = 6667
|
port = 6667
|
||||||
nick = taash-e-aakramak
|
nick = taash-e-aakramak
|
||||||
username = o3-base
|
username = o3-base
|
||||||
realname = IRC bot based on o3-base
|
realname = IRC bot based on o3-base
|
||||||
channels = ###cards
|
channels = ###cards
|
||||||
|
|
||||||
[auth]
|
[nickserv]
|
||||||
user =
|
|
||||||
password =
|
password =
|
||||||
|
|
10
botcmd.py
10
botcmd.py
|
@ -9,6 +9,7 @@ import gameloop
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
nickserv_pass = None
|
||||||
irc_chan = None
|
irc_chan = None
|
||||||
|
|
||||||
game_channel = None
|
game_channel = None
|
||||||
|
@ -371,7 +372,8 @@ def parse_command(message, nick, irc):
|
||||||
# Runs before even logger is brought up, and blocks further bringup until it's done
|
# Runs before even logger is brought up, and blocks further bringup until it's done
|
||||||
# config is a configpatser.ConfigParser object containig contents of bot.conf
|
# config is a configpatser.ConfigParser object containig contents of bot.conf
|
||||||
def initialize(*, config):
|
def initialize(*, config):
|
||||||
global irc_chan
|
global nickserv_pass, irc_chan
|
||||||
|
nickserv_pass = config['nickserv']['password']
|
||||||
irc_chan = config['server']['channels'].split()[0].encode()
|
irc_chan = config['server']['channels'].split()[0].encode()
|
||||||
|
|
||||||
# on_connect(*, irc)
|
# on_connect(*, irc)
|
||||||
|
@ -380,6 +382,12 @@ def initialize(*, config):
|
||||||
# Blocks the bot until it's done, including PING/PONG handling
|
# Blocks the bot until it's done, including PING/PONG handling
|
||||||
# irc is the IRC API object
|
# irc is the IRC API object
|
||||||
def on_connect(*, irc):
|
def on_connect(*, irc):
|
||||||
|
global nickserv_pass
|
||||||
|
|
||||||
|
if nickserv_pass != '':
|
||||||
|
irc.msg(b'nickserv', b'IDENTIFY ' + nickserv_pass.encode())
|
||||||
|
time.sleep(30) # One day I will do this correctly. Today is not the day
|
||||||
|
|
||||||
stop_gameloop()
|
stop_gameloop()
|
||||||
start_gameloop(irc)
|
start_gameloop(irc)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ class logmessage_types(enum.Enum):
|
||||||
sent, received, internal, status = range(4)
|
sent, received, internal, status = range(4)
|
||||||
|
|
||||||
class internal_submessage_types(enum.Enum):
|
class internal_submessage_types(enum.Enum):
|
||||||
quit, error, server = range(3)
|
quit, error = range(2)
|
||||||
|
|
||||||
class controlmessage_types(enum.Enum):
|
class controlmessage_types(enum.Enum):
|
||||||
quit, reconnect, send_line, ping, ping_timeout = range(5)
|
quit, reconnect, send_line, ping, ping_timeout = range(5)
|
||||||
|
|
15
dump_deck.py
15
dump_deck.py
|
@ -3,17 +3,8 @@ import sys
|
||||||
import cardcast_api
|
import cardcast_api
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) == 3:
|
code = sys.argv[1]
|
||||||
url = sys.argv[1]
|
info = cardcast_api.info(code)
|
||||||
code = sys.argv[2]
|
|
||||||
elif len(sys.argv) == 2:
|
|
||||||
code = sys.argv[1]
|
|
||||||
url = None
|
|
||||||
else:
|
|
||||||
print(f'Usage {sys.argv[0]} [url] code', file = sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
info = cardcast_api.info(code, base_url = url)
|
|
||||||
print('%s: %s by %s (%s black, %s white)' % (
|
print('%s: %s by %s (%s black, %s white)' % (
|
||||||
info['code'],
|
info['code'],
|
||||||
info['name'],
|
info['name'],
|
||||||
|
@ -24,7 +15,7 @@ def main():
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
calls, responses = cardcast_api.cards(code, base_url = url)
|
calls, responses = cardcast_api.cards(code)
|
||||||
|
|
||||||
for i in calls:
|
for i in calls:
|
||||||
print('_'.join(i))
|
print('_'.join(i))
|
||||||
|
|
|
@ -27,7 +27,6 @@ Namespace = namedtuple('Namespace', ['url', 'supports_random'])
|
||||||
|
|
||||||
deck_namespaces = {
|
deck_namespaces = {
|
||||||
'bslsk05': Namespace('https://dl.puckipedia.com/', False),
|
'bslsk05': Namespace('https://dl.puckipedia.com/', False),
|
||||||
'colondeck': Namespace('https://puck.moe/_/cards/', False),
|
|
||||||
'ahti': Namespace('https://ahti.space/cards/', True)
|
'ahti': Namespace('https://ahti.space/cards/', True)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,7 +440,7 @@ def game(send, notice, voice, devoice, get_event):
|
||||||
|
|
||||||
deck_add_handler('ahti', 'Base')
|
deck_add_handler('ahti', 'Base')
|
||||||
deck_add_handler('bslsk05', 'offtopiadeck')
|
deck_add_handler('bslsk05', 'offtopiadeck')
|
||||||
deck_add_handler('colondeck', 'colondeck')
|
deck_add_handler('bslsk05', 'colondeck')
|
||||||
|
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
deck_add_random_handler('ahti')
|
deck_add_random_handler('ahti')
|
||||||
|
@ -451,7 +450,7 @@ def game(send, notice, voice, devoice, get_event):
|
||||||
|
|
||||||
deck_add_handler('ahti', 'Base')
|
deck_add_handler('ahti', 'Base')
|
||||||
deck_add_handler('bslsk05', 'offtopiadeck')
|
deck_add_handler('bslsk05', 'offtopiadeck')
|
||||||
deck_add_handler('colondeck', 'colondeck')
|
deck_add_handler('bslsk05', 'colondeck')
|
||||||
|
|
||||||
elif rest[0] != 'empty':
|
elif rest[0] != 'empty':
|
||||||
send('Unknown preset %s' % rest[0])
|
send('Unknown preset %s' % rest[0])
|
||||||
|
@ -1143,7 +1142,7 @@ def game(send, notice, voice, devoice, get_event):
|
||||||
send('Everyone has chosen. %s, now\'s your time to choose.' % czar.nick)
|
send('Everyone has chosen. %s, now\'s your time to choose.' % czar.nick)
|
||||||
|
|
||||||
# Display the cards
|
# Display the cards
|
||||||
choosers = random.sample(list(card_choices.keys()), k = len(card_choices))
|
choosers = random.sample(card_choices.keys(), k = len(card_choices))
|
||||||
for index, player_bot in enumerate(choosers):
|
for index, player_bot in enumerate(choosers):
|
||||||
send('%i: %s' % (index, combine_cards(round_call_card, card_choices[player_bot])))
|
send('%i: %s' % (index, combine_cards(round_call_card, card_choices[player_bot])))
|
||||||
|
|
||||||
|
|
76
ircbot.py
76
ircbot.py
|
@ -2,7 +2,6 @@
|
||||||
import configparser
|
import configparser
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import sys
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
@ -17,8 +16,7 @@ import line_handling
|
||||||
Server = namedtuple('Server', ['host', 'port', 'nick', 'username', 'realname', 'channels'])
|
Server = namedtuple('Server', ['host', 'port', 'nick', 'username', 'realname', 'channels'])
|
||||||
|
|
||||||
class LoggerThread(threading.Thread):
|
class LoggerThread(threading.Thread):
|
||||||
def __init__(self, interactive_console, logging_channel, dead_notify_channel):
|
def __init__(self, logging_channel, dead_notify_channel):
|
||||||
self.interactive_console = interactive_console
|
|
||||||
self.logging_channel = logging_channel
|
self.logging_channel = logging_channel
|
||||||
self.dead_notify_channel = dead_notify_channel
|
self.dead_notify_channel = dead_notify_channel
|
||||||
|
|
||||||
|
@ -31,18 +29,17 @@ class LoggerThread(threading.Thread):
|
||||||
# Lines that were sent between server and client
|
# Lines that were sent between server and client
|
||||||
if message_type == logmessage_types.sent:
|
if message_type == logmessage_types.sent:
|
||||||
assert len(message_data) == 1
|
assert len(message_data) == 1
|
||||||
if self.interactive_console: print('>' + message_data[0])
|
print('>' + message_data[0])
|
||||||
|
|
||||||
elif message_type == logmessage_types.received:
|
elif message_type == logmessage_types.received:
|
||||||
assert len(message_data) == 1
|
assert len(message_data) == 1
|
||||||
if self.interactive_console: print('<' + message_data[0])
|
print('<' + message_data[0])
|
||||||
|
|
||||||
# Messages that are from internal components
|
# Messages that are from internal components
|
||||||
elif message_type == logmessage_types.internal:
|
elif message_type == logmessage_types.internal:
|
||||||
if message_data[0] == internal_submessage_types.quit:
|
if message_data[0] == internal_submessage_types.quit:
|
||||||
assert len(message_data) == 1
|
assert len(message_data) == 1
|
||||||
print('--- Quit')
|
print('--- Quit')
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
self.dead_notify_channel.send((controlmessage_types.quit,))
|
self.dead_notify_channel.send((controlmessage_types.quit,))
|
||||||
break
|
break
|
||||||
|
@ -50,28 +47,18 @@ class LoggerThread(threading.Thread):
|
||||||
elif message_data[0] == internal_submessage_types.error:
|
elif message_data[0] == internal_submessage_types.error:
|
||||||
assert len(message_data) == 2
|
assert len(message_data) == 2
|
||||||
print('--- Error', message_data[1])
|
print('--- Error', message_data[1])
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
elif message_data[0] == internal_submessage_types.server:
|
|
||||||
assert len(message_data) == 2
|
|
||||||
assert len(message_data[1]) == 2
|
|
||||||
print(f'--- Connecting to server {message_data[1][0]}:{message_data[1][1]}')
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print('--- ???', message_data)
|
print('--- ???', message_data)
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
# Messages about status from the bot code
|
# Messages about status from the bot code
|
||||||
elif message_type == logmessage_types.status:
|
elif message_type == logmessage_types.status:
|
||||||
assert len(message_data) == 2
|
assert len(message_data) == 2
|
||||||
print('*', end='')
|
print('*', end='')
|
||||||
print(*message_data[0], **message_data[1])
|
print(*message_data[0], **message_data[1])
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print('???', message_type, message_data)
|
print('???', message_type, message_data)
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
# API(serverthread_object)
|
# API(serverthread_object)
|
||||||
# Create a new API object corresponding to given ServerThread object
|
# Create a new API object corresponding to given ServerThread object
|
||||||
|
@ -155,12 +142,11 @@ class API:
|
||||||
self.serverthread_object.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, message))
|
self.serverthread_object.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, message))
|
||||||
|
|
||||||
|
|
||||||
# ServerThread(server, auth, control_channel, cron_control_channel, logging_channel)
|
# ServerThread(server, control_channel, cron_control_channel, logging_channel)
|
||||||
# Creates a new server main loop thread
|
# Creates a new server main loop thread
|
||||||
class ServerThread(threading.Thread):
|
class ServerThread(threading.Thread):
|
||||||
def __init__(self, server, auth, control_channel, cron_control_channel, logging_channel):
|
def __init__(self, server, control_channel, cron_control_channel, logging_channel):
|
||||||
self.server = server
|
self.server = server
|
||||||
self.auth = auth
|
|
||||||
self.control_channel = control_channel
|
self.control_channel = control_channel
|
||||||
self.cron_control_channel = cron_control_channel
|
self.cron_control_channel = cron_control_channel
|
||||||
self.logging_channel = logging_channel
|
self.logging_channel = logging_channel
|
||||||
|
@ -197,10 +183,7 @@ class ServerThread(threading.Thread):
|
||||||
time.sleep(wait)
|
time.sleep(wait)
|
||||||
|
|
||||||
with self.server_socket_write_lock:
|
with self.server_socket_write_lock:
|
||||||
if self.server_socket is not None:
|
self.server_socket.sendall(line + b'\r\n')
|
||||||
self.server_socket.sendall(line + b'\r\n')
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Don't log PINGs or PONGs
|
# Don't log PINGs or PONGs
|
||||||
if not (len(line) >= 5 and (line[:5] == b'PING ' or line[:5] == b'PONG ')):
|
if not (len(line) >= 5 and (line[:5] == b'PING ' or line[:5] == b'PONG ')):
|
||||||
|
@ -306,7 +289,6 @@ class ServerThread(threading.Thread):
|
||||||
while True:
|
while True:
|
||||||
# Connect to given server
|
# Connect to given server
|
||||||
address = (self.server.host, self.server.port)
|
address = (self.server.host, self.server.port)
|
||||||
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.server, address))
|
|
||||||
try:
|
try:
|
||||||
self.server_socket = socket.create_connection(address)
|
self.server_socket = socket.create_connection(address)
|
||||||
except (ConnectionRefusedError, socket.gaierror):
|
except (ConnectionRefusedError, socket.gaierror):
|
||||||
|
@ -345,16 +327,11 @@ class ServerThread(threading.Thread):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Run initialization
|
# Run initialization
|
||||||
|
|
||||||
# Use server-password based authentication if it's set up
|
|
||||||
user, password = self.auth
|
|
||||||
if user is not None:
|
|
||||||
self.send_line_raw(b'PASS %s:%s' % (user.encode(), password.encode()))
|
|
||||||
|
|
||||||
# Set up nick and username
|
|
||||||
self.api.nick(self.server.nick.encode('utf-8'))
|
|
||||||
self.send_line_raw(b'USER %s a a :%s' % (self.server.username.encode('utf-8'), self.server.realname.encode('utf-8')))
|
self.send_line_raw(b'USER %s a a :%s' % (self.server.username.encode('utf-8'), self.server.realname.encode('utf-8')))
|
||||||
|
|
||||||
|
# Set up nick
|
||||||
|
self.api.nick(self.server.nick.encode('utf-8'))
|
||||||
|
|
||||||
# Run the on_connect hook, to allow further setup
|
# Run the on_connect hook, to allow further setup
|
||||||
botcmd.on_connect(irc = self.api)
|
botcmd.on_connect(irc = self.api)
|
||||||
|
|
||||||
|
@ -374,7 +351,6 @@ class ServerThread(threading.Thread):
|
||||||
|
|
||||||
# Tell the server we're quiting
|
# Tell the server we're quiting
|
||||||
self.send_line_raw(b'QUIT :%s exiting normally' % self.server.username.encode('utf-8'))
|
self.send_line_raw(b'QUIT :%s exiting normally' % self.server.username.encode('utf-8'))
|
||||||
|
|
||||||
self.server_socket.close()
|
self.server_socket.close()
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -382,9 +358,7 @@ class ServerThread(threading.Thread):
|
||||||
else:
|
else:
|
||||||
# Tell server we're reconnecting
|
# Tell server we're reconnecting
|
||||||
self.send_line_raw(b'QUIT :Reconnecting')
|
self.send_line_raw(b'QUIT :Reconnecting')
|
||||||
with self.server_socket_write_lock:
|
self.server_socket.close()
|
||||||
self.server_socket.close()
|
|
||||||
self.server_socket = None
|
|
||||||
|
|
||||||
except (BrokenPipeError, TimeoutError) as err:
|
except (BrokenPipeError, TimeoutError) as err:
|
||||||
# Connection broke, log it and try to reconnect
|
# Connection broke, log it and try to reconnect
|
||||||
|
@ -397,20 +371,20 @@ class ServerThread(threading.Thread):
|
||||||
# Tell cron we're quiting
|
# Tell cron we're quiting
|
||||||
cron.quit(cron_control_channel)
|
cron.quit(cron_control_channel)
|
||||||
|
|
||||||
# spawn_serverthread(server, auth, cron_control_channel, logging_channel) → control_channel
|
# spawn_serverthread(server, cron_control_channel, logging_channel) → control_channel
|
||||||
# Creates a ServerThread for given server and returns the channel for controlling it
|
# Creates a ServerThread for given server and returns the channel for controlling it
|
||||||
def spawn_serverthread(server, auth, cron_control_channel, logging_channel):
|
def spawn_serverthread(server, cron_control_channel, logging_channel):
|
||||||
thread_control_socket, spawner_control_socket = socket.socketpair()
|
thread_control_socket, spawner_control_socket = socket.socketpair()
|
||||||
control_channel = channel.Channel()
|
control_channel = channel.Channel()
|
||||||
ServerThread(server, auth, control_channel, cron_control_channel, logging_channel).start()
|
ServerThread(server, control_channel, cron_control_channel, logging_channel).start()
|
||||||
return control_channel
|
return control_channel
|
||||||
|
|
||||||
# spawn_loggerthread(interactive_console) → logging_channel, dead_notify_channel
|
# spawn_loggerthread() → logging_channel, dead_notify_channel
|
||||||
# Spawn logger thread and returns the channel it logs and the channel it uses to notify about quiting
|
# Spawn logger thread and returns the channel it logs and the channel it uses to notify about quiting
|
||||||
def spawn_loggerthread(interactive_console):
|
def spawn_loggerthread():
|
||||||
logging_channel = channel.Channel()
|
logging_channel = channel.Channel()
|
||||||
dead_notify_channel = channel.Channel()
|
dead_notify_channel = channel.Channel()
|
||||||
LoggerThread(interactive_console, logging_channel, dead_notify_channel).start()
|
LoggerThread(logging_channel, dead_notify_channel).start()
|
||||||
return logging_channel, dead_notify_channel
|
return logging_channel, dead_notify_channel
|
||||||
|
|
||||||
# read_config() → config, server
|
# read_config() → config, server
|
||||||
|
@ -426,28 +400,20 @@ def read_config():
|
||||||
realname = config['server']['realname']
|
realname = config['server']['realname']
|
||||||
channels = config['server']['channels'].split()
|
channels = config['server']['channels'].split()
|
||||||
|
|
||||||
user = None
|
|
||||||
password = None
|
|
||||||
if 'auth' in config:
|
|
||||||
user = config['auth']['user']
|
|
||||||
password = config['auth']['password']
|
|
||||||
|
|
||||||
server = Server(host = host, port = port, nick = nick, username = username, realname = realname, channels = channels)
|
server = Server(host = host, port = port, nick = nick, username = username, realname = realname, channels = channels)
|
||||||
|
|
||||||
return config, server, (user, password)
|
return config, server
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
interactive_console = sys.stdin.isatty()
|
config, server = read_config()
|
||||||
|
|
||||||
config, server, auth = read_config()
|
|
||||||
|
|
||||||
botcmd.initialize(config = config)
|
botcmd.initialize(config = config)
|
||||||
|
|
||||||
cron_control_channel = cron.start()
|
cron_control_channel = cron.start()
|
||||||
logging_channel, dead_notify_channel = spawn_loggerthread(interactive_console)
|
logging_channel, dead_notify_channel = spawn_loggerthread()
|
||||||
control_channel = spawn_serverthread(server, auth, cron_control_channel, logging_channel)
|
control_channel = spawn_serverthread(server, cron_control_channel, logging_channel)
|
||||||
|
|
||||||
while interactive_console:
|
while True:
|
||||||
message = dead_notify_channel.recv(blocking = False)
|
message = dead_notify_channel.recv(blocking = False)
|
||||||
if message is not None:
|
if message is not None:
|
||||||
if message[0] == controlmessage_types.quit:
|
if message[0] == controlmessage_types.quit:
|
||||||
|
|
Loading…
Reference in New Issue