Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

6 changed files with 39 additions and 76 deletions

View File

@ -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 =

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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])))

View File

@ -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: