A botlib.py => botlib.py +156 -0
@@ 0,0 1,156 @@
+#!/bin/env python
+
+import socket
+from sys import argv, stderr, exit
+import os
+from time import sleep
+
+lastmsg = 0
+
+sok = None
+isok = None
+osok = None
+
+argv0 = "bot.py"
+def usage():
+ global argv0
+ print(f"usage: {argv0} HOST PORT", file=stderr)
+ print(file=stderr)
+ print("'advanced' 'AI' for nanochat", file=stderr)
+ exit(-1)
+
+
+### "AI" ###
+
+def split_list(words, pwords):
+ terms = []
+ acc = []
+ for i in range(len(words)):
+ if len(acc) == 0 and pwords[i] in ('and', 'or'):
+ continue
+ if words[i][-1] == ',':
+ acc.append(words[i].lstrip(','))
+ terms.append(acc)
+ acc = []
+ else:
+ acc.append(words[i])
+
+ if len(acc) != 0:
+ terms.append(acc)
+
+ return terms
+
+def strip_direct_address(name, special, words, pwords):
+ if name not in pwords:
+ return False
+
+ # ignore an initial greeting
+ if pwords[0] in ['hey', 'oh', 'yo', 'ok', 'okay']:
+ idx = 1
+ else:
+ idx = 0
+
+ # "bot, hi"
+ if pwords[idx] == name and words[idx][-1] in ',:':
+ # strip direct address and greeting
+ for _ in range(idx+1):
+ words.pop(0)
+ pwords.pop(0)
+ return True
+
+ # lol
+ if len(words) < 2:
+ words.pop()
+ pwords.pop()
+ return True
+ if len(words) == True and words[-2] in special:
+ words.pop()
+ pwords.pop()
+ return True
+
+ # trailing address
+ if pwords[-1] == name and words[-2][-1] == ',':
+ words.pop()
+ pwords.pop()
+ words[-1] = words[-1].lstrip(',') # strip comma
+ return True
+
+ return False
+
+
+def handle_line(line, name, msgfn, actfn):
+ # we assume nobody's nick is longer than 20 chars, to
+ # hopefully avoid picking up actions that include colons.
+ # this may need tweaking!
+ try:
+ colon_pos = line.index(':', 0, 20)
+ except:
+ colon_pos = len(line)
+
+ if colon_pos+1 >= len(line):
+ words, pwords = parse_words(line)
+ if len(words) > 0:
+ actfn(line, words, pwords)
+ else:
+ nick, line = line.split(':', 1)
+ if line.startswith('/') or nick.endswith('http') or nick.endswith('https'):
+ return # picked up a URL by accident
+
+ if nick == name:
+ return
+
+ words, pwords = parse_words(line)
+ if len(words) >= 2:
+ msgfn(nick, line, words, pwords)
+
+def parse_words(line):
+ words = [*filter(lambda x: x, line.split(' '))]
+ pwords = [word.rstrip(',!.:~?') for word in words] # [n]ormalized words
+ return words, pwords
+
+### NETCODE ###
+
+def readln():
+ global isok
+ return isok.readline().decode('latin-1').strip()
+
+def readk():
+ return int(readln())
+
+def writeln(line):
+ global osok
+ osok.write(f"{line}\n")
+ osok.flush()
+
+def send(msg):
+ writeln(f"SEND {msg}")
+ return readk()
+
+def readmany():
+ global lastmsg
+ ret = []
+ k = readk()
+ for i in range(k):
+ ret.append(readln())
+ lastmsg = readk()
+
+ return ret
+
+### BOOT ###
+
+def parse_args():
+ global sok, isok, osok, argv, argv0, lastmsg
+ if len(argv) < 1:
+ usage()
+ argv0 = argv[0]
+ argv = argv[1:]
+ if len(argv) != 2:
+ usage()
+
+ sok = socket.create_connection((argv[0], int(argv[1])))
+ isok = sok.makefile('rb')
+ osok = sok.makefile('w')
+
+ writeln("LAST 0")
+ readln()
+ lastmsg = readk()
A clairen.py => clairen.py +73 -0
@@ 0,0 1,73 @@
+#!/bin/env python
+
+import botlib
+from time import sleep
+
+NAME = "clairen"
+
+# used for focus simulation
+# i is just the iterator in the current download, k is the length
+# k - i - 1 is how many messages there are after the current message, or its depth
+# the chances of missing a message, using the direct address, etc, depend on this
+i = k = depth = 0
+
+last16 = []
+
+### "AI" ###
+
+def log_hi(nick):
+ global last16
+ last16[-1][0] = 'hi'
+
+def direct_address(nick, words, pwords):
+ print(words, pwords)
+ if is_hi(words, pwords):
+ log_hi(nick)
+ elif is_gm(words, pwords):
+ log_gm(nick)
+ else:
+ huh(nick)
+
+def gossip(nick, line):
+ pass
+
+def chatting(nick, line):
+ pass
+
+
+def handle_msg(nick, words, pwords):
+ global NAME, last16
+
+ last16 += [('msg', nick, words, pwords)]
+ if nick == NAME:
+ return
+
+ # things that can be of the form "<word> clairen"
+ nocomma = ['hi', 'hey', 'o/', 'hello', 'thanks', 'ty', 'tysm']
+
+ if botlib.strip_direct_address(NAME, nocomma, words, pwords):
+ direct_address(nick, words, pwords)
+ elif NAME in pwords: # "gossip" is messages mentioning us
+ gossip(nick, words, pwords)
+ else:
+ chatting(nick, words, pwords)
+
+def handle_action(line):
+ global last16
+ last16 += None
+
+
+### BOOT ###
+
+botlib.parse_args()
+
+while True:
+ sleep(5)
+ writeln(f"SKIP {botlib.lastmsg}")
+ lines = botlib.readmany()
+
+ for i, line in enumerate(lines):
+ depth = k - i - 1
+ botlib.handle_line(line, handle_msg, handle_action)
+
+
A dee.py => dee.py +198 -0
@@ 0,0 1,198 @@
+#!/bin/env python
+
+import botlib
+from time import sleep
+from random import randint
+import re
+from copy import copy
+
+NAME = "dee"
+
+### "AI" ###
+
+def send(msg):
+ global NAME
+ botlib.send(f'{NAME}: {msg}')
+
+
+class DiceParseException(Exception):
+ pass
+
+def parse_number(word):
+ return int(word)
+
+'''
+dice = 'then'? spec offset?
+
+spec = 'another' num? 'more'?
+spec = 'another'? num? 'more'? size
+spec = 'another'? num 'more'
+
+size = 'd' num
+
+offset = op num
+op = 'plus' | 'minus' | '+' | '-'
+
+num = int() or human-readable number
+
+
+'''
+
+last_dice = 6
+last_line = ['1', 'd6']
+def parse_dice(nick, words):
+ global last_dice, last_line
+
+ if words[0] == 'then':
+ words.pop(0)
+ if len(words) == 0:
+ return 0, 0, 0
+
+ if words[0] == 'again':
+ return parse_dice(nick, last_line)
+ else:
+ last_line = copy(words)
+
+ has_another = False
+ if words[0] == 'another':
+ has_another = True
+ words.pop(0)
+ if len(words) == 0:
+ return 1, last_dice, 0
+
+ can_more = True
+ try:
+ count = parse_number(words[0])
+ words.pop(0)
+ except (IndexError, ValueError):
+ count = 1
+ can_more = False
+
+ if len(words) == 0:
+ if has_another:
+ return count, last_dice, 0
+ else:
+ raise DiceParseException(f'roll {count} what?')
+
+ if d := re.match('d([0-9]+|[A-Za-z]+)', words[0]):
+ last_dice = parse_number(d.group(1))
+ words.pop(0)
+ if len(words) > 0 and words[0] == 'more':
+ words.pop(0)
+ if len(words) == 0:
+ return count, last_dice, 0
+ elif words[0] == 'more':
+ if not can_more:
+ raise DiceParseException(f'roll how many more d{last_dice}?')
+ words.pop(0)
+ if len(words) == 0:
+ return count, last_dice, 0
+
+ if words[0] in ('plus', 'add', 'minus', 'sub', 'subtract'):
+ try:
+ offset = parse_number(words[1])
+ except (IndexError, ValueError):
+ raise DiceParseException(f'{words[0]} what?')
+
+ if words[0] not in ('plus', 'add'):
+ offset *= -1
+
+ return count, last_dice, offset
+
+ raise DiceParseException(f'what?')
+
+def dice(nick, words, pwords):
+ result = nick
+
+ terms = botlib.split_list(words, pwords)
+ print(f'{terms=}')
+ for term in terms:
+ try:
+ count, size, offset = parse_dice(nick, term)
+ except DiceParseException as e:
+ send(f'{nick}, {e.args[0]}')
+ return
+
+ if count == 0:
+ continue
+ if count > 50:
+ send(f'{nick}, roll them yourself!')
+ return
+
+ rollsum = offset
+
+ # string formatting
+ if count == 1:
+ result += f', 1 d{size}'
+ else:
+ result += f', {count} d{size}s'
+ if offset > 0:
+ offset = f' + {offset}'
+ elif offset < 0:
+ offset = f' - {-offset}'
+ else:
+ offset = ''
+ result += f'{offset}:'
+
+ for i in range(count):
+ if size < 1:
+ roll = size
+ else:
+ roll = randint(1, size)
+ rollsum += roll
+ if i > 0:
+ result += f' + {roll}'
+ else:
+ result += f' {roll}'
+
+ if count > 1 or offset != '':
+ result += f'{offset} = {rollsum}'
+
+ if result == nick:
+ send(f'{nick}, done, i guess')
+ elif len(result) < 100:
+ send(result)
+ else:
+ send(f'{nick}, roll them yourself!')
+
+
+def handle_msg(nick, line, words, pwords):
+ global NAME
+
+ print(nick, line, words, pwords)
+
+ if not botlib.strip_direct_address(NAME, [], words, pwords):
+ return
+
+ cmd = pwords[0]
+ pwords.pop(0)
+ words.pop(0)
+
+ if len(pwords) < 1:
+ send(f'{nick}, {cmd} what?')
+ elif cmd == 'roll':
+ dice(nick, words, pwords)
+ elif cmd == 'pick':
+ return
+ pick(nick, words, pwords)
+ elif cmd in ('shuf', 'shuffle'):
+ return
+ shuf(nick, words, pwords)
+
+def handle_action(line):
+ pass
+
+### BOOT ###
+
+botlib.parse_args()
+
+while True:
+ sleep(5)
+ botlib.writeln(f"SKIP {botlib.lastmsg}")
+ lines = botlib.readmany()
+
+ for line in lines:
+ print(line)
+ botlib.handle_line(line, NAME, handle_msg, handle_action)
+
+