This commit is contained in:
Ben Harris committed 2018-10-09 02:50:10 -04:00
1 parent a8a5de36f2
commit 58753c7234
38 files changed
+4549 -2523

No files matched your search

+48 -28
View File
@@ -8,15 +8,20 @@ import re
p = inflect.engine()
file_pattern = "/home/*/madlibs/*.madlib"
file_regex = re.compile(r'^/home/(.*?)/.*/([^/]*)\.madlib$')
word_regex = re.compile(r'{{(.*?)(#.*?)?(\|.*)?}}')
word_replace_regex = re.compile(r'^(.*)#(\d*)$')
file_regex = re.compile(r"^/home/(.*?)/.*/([^/]*)\.madlib$")
word_regex = re.compile(r"{{(.*?)(#.*?)?(\|.*)?}}")
word_replace_regex = re.compile(r"^(.*)#(\d*)$")
# Take a file path and return a tuple with the title and original path
def munge_story(file_name):
match = re.match(file_regex, file_name)
count = count_words_file(file_name)
return ("'" + match.group(2).replace('_', ' ') + "' by ~" + match.group(1), match.group(0), count)
return (
"'" + match.group(2).replace("_", " ") + "' by ~" + match.group(1),
match.group(0),
count,
)
# Retrive a list of all madlib story files
def find_stories(limit=999, shuffle=False):
@@ -27,25 +32,28 @@ def find_stories(limit=999, shuffle=False):
files = files[:limit]
return map(munge_story, files)
# Count the number of replacable words in the story
def count_words(story):
# words that have a '#' part and match should only be counted once
count = 0
repeats = []
for match in re.finditer(word_regex, story):
if match.group(2) is None: #the '#' part
if match.group(2) is None: # the '#' part
count += 1
elif match.group(1) + match.group(2) not in repeats:
count += 1
repeats.append(match.group(1) + match.group(2))
return count
# Count the number of replacable words in the story when given a file path
def count_words_file(storyPath):
with open(storyPath, "r") as storyFile:
story = storyFile.read()
return count_words(story)
# Get the next replaceable word in the story. Includes full token and just the word
# If you specify rand as True, it will get a random word instead of the next one
def find_next_word(story, rand=False):
@@ -58,19 +66,27 @@ def find_next_word(story, rand=False):
return (match.group(0), match.group(1))
# Using a query phrase, replace a word and return the entire story body
def replace_word(story, query, word):
rquery = word_regex.search(query)
# '{{foo#bar|baz|bang}}' => ('foo', '#bar', '|baz|bang')
pipes = [p.lower() for p in (rquery.group(3) if rquery.group(3) is not None else '').strip('|').split('|')]
pipes = [
p.lower()
for p in (rquery.group(3) if rquery.group(3) is not None else "")
.strip("|")
.split("|")
]
munged_word = process_pipes(word, pipes)
story = story.replace(query, munged_word, 1)
if rquery.group(2) is not None: #if there is a '#' part we replace all instances
print "Looking for {{" + rquery.group(1) + rquery.group(2) + ".*?}}"
others = re.findall(r'{{' + rquery.group(1) + rquery.group(2) + '.*?}}', story)
if rquery.group(2) is not None: # if there is a '#' part we replace all instances
print("Looking for {{" + rquery.group(1) + rquery.group(2) + ".*?}}")
others = re.findall(r"{{" + rquery.group(1) + rquery.group(2) + ".*?}}", story)
if len(others) > 0:
story = replace_word(story, others[0], word) #iteratively replace the next instance
story = replace_word(
story, others[0], word
) # iteratively replace the next instance
return story
@@ -80,17 +96,18 @@ def process_pipes(word, pipes):
for pipe in pipes:
try:
word = {
'upper': lambda word: word.upper(),
'lower': lambda word: word.lower(),
'title': lambda word: word.title(),
'numeric': lambda word: p.number_to_words(word),
'ordinal': lambda word: p.ordinal(word),
collections.defaultdict: lambda word: word
}[pipe](word)
"upper": lambda word: word.upper(),
"lower": lambda word: word.lower(),
"title": lambda word: word.title(),
"numeric": lambda word: p.number_to_words(word),
"ordinal": lambda word: p.ordinal(word),
collections.defaultdict: lambda word: word,
}[pipe](word)
except Exception:
pass # just keep going if an error occurs processing a pipe
pass # just keep going if an error occurs processing a pipe
return word
# A helper function that will split the story up into chat-printable lengths
def yield_lines(line, max_size):
words = []
@@ -101,17 +118,18 @@ def yield_lines(line, max_size):
while len(word) > max_size:
splitsize = max_size - words_length - 1
words.append(word[:splitsize] + "-")
yield ' '.join(words)
yield " ".join(words)
words = []
words_length = 0
word = word[splitsize:]
if words_length + len(word) + 1 > max_size:
yield ' '.join(words)
yield " ".join(words)
words = []
words_length = 0
words.append(word)
words_length += len(word) + 1 # For the space
yield ' '.join(words) # For any words left over
words_length += len(word) + 1 # For the space
yield " ".join(words) # For any words left over
# Open a story file and ask for the parts
def make_story(storyFile):
@@ -119,23 +137,24 @@ def make_story(storyFile):
story = storyFile.read()
match = word_regex.search(story)
while match is not None:
word = raw_input('Give me {}: '.format(match.group(1)))
print "Replacing '{}' with '{}'".format(match.group(0), word)
print story
word = raw_input("Give me {}: ".format(match.group(1)))
print("Replacing '{}' with '{}'".format(match.group(0), word))
print(story)
story = re.sub(match.group(0), word, story, 1)
match = word_regex.search(story)
print story
print(story)
def start():
stories = find_stories(20, True)
if len(stories) == 0:
print "Sorry, no stories found. :/"
print("Sorry, no stories found. :/")
return
input = -1
while input < 0 or input >= len(stories):
for idx, story in enumerate(stories):
print "[{}] {}".format(idx, story[0])
print("[{}] {}".format(idx, story[0]))
input = raw_input("Which story would you like?: ")
try:
# Try to convert the string input into a number
@@ -147,5 +166,6 @@ def start():
# Call make_story with the file path of the selected story
make_story(stories[input][1])
if __name__ == "__main__":
start()
+102 -35
View File
@@ -15,33 +15,56 @@ import operator
from .. import util
import madlib
class State:
idle = 0 # When the bot is started and after it has finished a story
thinking = 1 # I intentionally speed throttle this bot so it feels a little more natural
story_selection = 2 # When the bot is waiting for a user input for story selection
word_input = 3 # When the bot is waiting for user input after prompted for a word
idle = 0 # When the bot is started and after it has finished a story
thinking = (
1
) # I intentionally speed throttle this bot so it feels a little more natural
story_selection = 2 # When the bot is waiting for a user input for story selection
word_input = 3 # When the bot is waiting for user input after prompted for a word
# Globals
MAX_LINE = 80 # The maximum number of characters in a line
MAX_STORIES = 5 # The maximum number of stories a user can pick from
THROTTLE_FACTOR = 1 # A factor by which all sleep event durations will be multiplied
MAX_LINE = 80 # The maximum number of characters in a line
MAX_STORIES = 5 # The maximum number of stories a user can pick from
THROTTLE_FACTOR = 1 # A factor by which all sleep event durations will be multiplied
# Allow madlbibbot to run multiple simultaneous stories
state = {} # The current state of the bot
stories = {} # The list of stories available to users
story = {} # The madlib story currently being worked on
nextword = {} # The word that the bot is currently expecting data for
state = {} # The current state of the bot
stories = {} # The list of stories available to users
story = {} # The madlib story currently being worked on
nextword = {} # The word that the bot is currently expecting data for
parser = OptionParser()
parser.add_option("-s", "--server", dest="server", default='127.0.0.1',
help="the server to connect to", metavar="SERVER")
parser.add_option("-c", "--channel", dest="channel", default='#madlibs',
help="the channel to join", metavar="CHANNEL")
parser.add_option("-n", "--nick", dest="nick", default='madlibbot',
help="the nick to use", metavar="NICK")
parser.add_option(
"-s",
"--server",
dest="server",
default="127.0.0.1",
help="the server to connect to",
metavar="SERVER",
)
parser.add_option(
"-c",
"--channel",
dest="channel",
default="#madlibs",
help="the channel to join",
metavar="CHANNEL",
)
parser.add_option(
"-n",
"--nick",
dest="nick",
default="madlibbot",
help="the nick to use",
metavar="NICK",
)
(options, args) = parser.parse_args()
def resetGlobals(channel=""):
global state
global stories
@@ -59,6 +82,7 @@ def resetGlobals(channel=""):
story[channel] = ""
nextword[channel] = {}
def get_stories(channel, botnick):
global state
global stories
@@ -73,9 +97,13 @@ def get_stories(channel, botnick):
for idx, story in enumerate(stories[channel]):
sendmsg(channel, "[{}] {} ({} words)".format(idx, story[0], story[2]))
time.sleep(0.5 * THROTTLE_FACTOR)
sendmsg(channel, "Please select a story by index by saying '{}: <number>':".format(botnick))
sendmsg(
channel,
"Please select a story by index by saying '{}: <number>':".format(botnick),
)
state[channel] = State.story_selection
# Handle user input when the bot is directly addressed
def handle_bot_msg(channel, msg, botnick):
global state
@@ -100,11 +128,13 @@ def handle_bot_msg(channel, msg, botnick):
else:
state[channel] = State.idle
# Handle how to quit the game
def quit_game(channel):
resetGlobals(channel)
sendmsg(channel, "Ok, quitting the current game.")
# Handle user input when we are in story selection mode
def handle_story_selection(channel, msg, botnick):
global stories
@@ -115,16 +145,19 @@ def handle_story_selection(channel, msg, botnick):
sendmsg(channel, "Selection out of bounds. Try again!")
return
time.sleep(1 * THROTTLE_FACTOR)
sendmsg(channel, "Give me a second to load up {}".format(stories[channel][imsg][0]))
sendmsg(
channel, "Give me a second to load up {}".format(stories[channel][imsg][0])
)
with open(stories[channel][imsg][1], "r") as storyFile:
story[channel] = storyFile.read()
stories[channel] = {} # Clear out the saved selectable stories in memory
stories[channel] = {} # Clear out the saved selectable stories in memory
story_start(channel, botnick)
except ValueError:
sendmsg(channel, "Invalid selection. Try again!")
state[channel] = State.story_selection
# Handle when a story is being started
def story_start(channel, botnick):
global story
@@ -132,12 +165,18 @@ def story_start(channel, botnick):
global nextword
state[channel] = State.thinking
sendmsg(channel, "Alright! Let's get started! Say '{}: <word>' to give me words.".format(botnick))
sendmsg(
channel,
"Alright! Let's get started! Say '{}: <word>' to give me words.".format(
botnick
),
)
nextword[channel] = madlib.find_next_word(story[channel], True)
time.sleep(0.5 * THROTTLE_FACTOR)
sendmsg(channel, "Give me {}:".format(nextword[channel][1]))
state[channel] = State.word_input
# Handle user input when we have asked the user for input and are expecting a
# response
def handle_story_step(channel, msg):
@@ -146,7 +185,7 @@ def handle_story_step(channel, msg):
global nextword
state[channel] = State.thinking
word = nextword[channel] #madlib.find_next_word(story[channel])
word = nextword[channel] # madlib.find_next_word(story[channel])
if word is not None:
story[channel] = madlib.replace_word(story[channel], nextword[channel][0], msg)
time.sleep(1 * THROTTLE_FACTOR)
@@ -155,62 +194,86 @@ def handle_story_step(channel, msg):
if nextword[channel] is None:
finish_story(channel)
return
#else
# else
count = madlib.count_words(story[channel])
sendmsg(channel, "Thanks! Now give me {} ({} words left)".format(nextword[channel][1], count))
sendmsg(
channel,
"Thanks! Now give me {} ({} words left)".format(nextword[channel][1], count),
)
state[channel] = State.word_input
# Finish the story
def finish_story(channel):
global state
global story
sendmsg(channel, "Ok, here's the story...");
sendmsg(channel, '=' * MAX_LINE)
sendmsg(channel, "Ok, here's the story...")
sendmsg(channel, "=" * MAX_LINE)
for line in story[channel].splitlines():
for part in madlib.yield_lines(line, MAX_LINE):
time.sleep(0.6 * THROTTLE_FACTOR)
sendmsg(channel, part)
padlen = (MAX_LINE - 9)/2
padlen = (MAX_LINE - 9) / 2
mod = (MAX_LINE - 9) % 2
sendmsg(channel, '=' * padlen + ' THE END ' + '=' * (padlen + mod))
sendmsg(channel, "=" * padlen + " THE END " + "=" * (padlen + mod))
story[channel] = ""
state[channel] = State.idle
# System things
def ping(pong):
ircsock.send("PONG {}\n".format(pong))
def sendmsg(chan , msg):
def sendmsg(chan, msg):
ircsock.send("PRIVMSG {} :{}\n".format(chan, msg))
def joinchan(chan):
global state
state[chan] = State.idle
ircsock.send("JOIN {}\n".format(chan))
def rollcall(channel, botnick):
global state
if channel not in state:
state[channel] = State.idle
if state[channel] == State.idle:
sendmsg(channel, "Do you like MadLibs? Start a collaborative story by saying '{}: startgame'".format(botnick))
sendmsg(
channel,
"Do you like MadLibs? Start a collaborative story by saying '{}: startgame'".format(
botnick
),
)
else:
sendmsg(channel, "A game is already in progress. Say '{}: <word>' to provide me with the next word or '{}: !quit' to stop the current game".format(botnick, botnick))
sendmsg(
channel,
"A game is already in progress. Say '{}: <word>' to provide me with the next word or '{}: !quit' to stop the current game".format(
botnick, botnick
),
)
def connect(server, channel, botnick):
ircsock.connect((server, 6667))
ircsock.send("USER {} {} {} :krowbar\n".format(botnick, botnick, botnick)) # user authentication
ircsock.send(
"USER {} {} {} :krowbar\n".format(botnick, botnick, botnick)
) # user authentication
ircsock.send("NICK {}\n".format(botnick))
joinchan(channel)
def listen(botnick):
botmsgre = re.compile('^{}\:?\s*(.*)$'.format(botnick)) # re to strip the bot's name from a message
botmsgre = re.compile(
"^{}\:?\s*(.*)$".format(botnick)
) # re to strip the bot's name from a message
while 1:
ircmsg = ircsock.recv(2048)
ircmsg = ircmsg.strip('\n\r')
ircmsg = ircmsg.strip("\n\r")
if ircmsg[:4] == "PING":
ping(ircmsg.split(" ")[1])
@@ -227,7 +290,10 @@ def listen(botnick):
channel = split[3]
message = split[4]
if message.startswith("!rollcall") == True or message.startswith("!help") == True:
if (
message.startswith("!rollcall") == True
or message.startswith("!help") == True
):
rollcall(channel, botnick)
elif message.startswith(botnick) == True:
botmsg = botmsgre.match(message).group(1)
@@ -236,6 +302,7 @@ def listen(botnick):
sys.stdout.flush()
time.sleep(1 * THROTTLE_FACTOR)
ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect(options.server, options.channel, options.nick)
listen(options.nick)
-16
View File
@@ -1,16 +0,0 @@
import time
import re
def format_message(message):
pattern = r'^:.*\!~(.*)@.* (.*) (.*) :(.*)'
now = int(time.time())
matches = re.match(pattern, message)
if not matches:
return ''
nick = matches.group(1).strip()
command = matches.group(2).strip()
channel = matches.group(3).strip()
message = matches.group(4).strip()
return "%s\t%s\t%s\t%s\t%s" % (now, nick, command, channel, message)