From 772995c0ff444dc807a4c5a93cfeb34aae1bf6ba Mon Sep 17 00:00:00 2001 From: Russell Date: Tue, 29 Jan 2019 10:04:31 -0500 Subject: [PATCH] Add files I forgot --- Code/irc/bot_launcher.py | 64 ++++++ Code/irc/madlibbot/util.py | 1 + Code/irc/tildebot_plugins/rollcall.py | 7 + Code/irc/tildebot_plugins/tilde.py | 41 ++++ Code/irc/topicbot_plugins/topic.py | 32 +++ Code/irc/util/__init__.py | 0 Code/irc/util/acronymFinder.py | 99 +++++++++ Code/irc/util/banter.py | 60 ++++++ Code/irc/util/defineWord.py | 34 +++ Code/irc/util/dict_puzzle.py | 53 +++++ Code/irc/util/duckduckgo.py | 198 +++++++++++++++++ Code/irc/util/evil.py | 6 + Code/irc/util/nameFix.py | 27 +++ Code/irc/util/newBanter.py | 55 +++++ Code/irc/util/numberwang.py | 191 +++++++++++++++++ Code/irc/util/puzzle.py | 187 ++++++++++++++++ Code/irc/util/quote_puzzle.py | 27 +++ Code/irc/util/rhymesWith.py | 40 ++++ Code/irc/util/textcaptcha.py | 11 + Code/irc/util/tilde.py | 282 +++++++++++++++++++++++++ Code/irc/util/topic.py | 95 +++++++++ Code/irc/util/tumblr.py | 49 +++++ Code/irc/util/welch.py | 10 + Code/irc/util/whoSaid.py | 61 ++++++ Code/irc/util/wikiphilosophy.py | 87 ++++++++ Code/irc/util/xkcdApropos.py | 31 +++ Code/irc/wangbot_plugins/numberwang.py | 43 ++++ madlibs/baseball.madlib | 13 ++ madlibs/feature_test.madlib | 3 + 29 files changed, 1807 insertions(+) create mode 100755 Code/irc/bot_launcher.py create mode 120000 Code/irc/madlibbot/util.py create mode 100644 Code/irc/tildebot_plugins/rollcall.py create mode 100755 Code/irc/tildebot_plugins/tilde.py create mode 100644 Code/irc/topicbot_plugins/topic.py create mode 100644 Code/irc/util/__init__.py create mode 100644 Code/irc/util/acronymFinder.py create mode 100644 Code/irc/util/banter.py create mode 100644 Code/irc/util/defineWord.py create mode 100644 Code/irc/util/dict_puzzle.py create mode 100755 Code/irc/util/duckduckgo.py create mode 100644 Code/irc/util/evil.py create mode 100644 Code/irc/util/nameFix.py create mode 100644 Code/irc/util/newBanter.py create mode 100644 Code/irc/util/numberwang.py create mode 100644 Code/irc/util/puzzle.py create mode 100644 Code/irc/util/quote_puzzle.py create mode 100644 Code/irc/util/rhymesWith.py create mode 100644 Code/irc/util/textcaptcha.py create mode 100755 Code/irc/util/tilde.py create mode 100644 Code/irc/util/topic.py create mode 100644 Code/irc/util/tumblr.py create mode 100644 Code/irc/util/welch.py create mode 100644 Code/irc/util/whoSaid.py create mode 100644 Code/irc/util/wikiphilosophy.py create mode 100644 Code/irc/util/xkcdApropos.py create mode 100644 Code/irc/wangbot_plugins/numberwang.py create mode 100644 madlibs/baseball.madlib create mode 100644 madlibs/feature_test.madlib diff --git a/Code/irc/bot_launcher.py b/Code/irc/bot_launcher.py new file mode 100755 index 0000000..380ccf0 --- /dev/null +++ b/Code/irc/bot_launcher.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 + +import argparse +from pinhook.bot import Bot + +parser = argparse.ArgumentParser() + +parser.add_argument( + "-s", + "--server", + dest="server", + default="127.0.0.1", + help="the server to connect to", + metavar="SERVER", +) +parser.add_argument( + "-p", + "--port", + dest="port", + type=int, + default=6667, + help="the port to connect to", + metavar="PORT", +) +parser.add_argument( + "-c", + "--channels", + dest="channels", + nargs="+", + default=["#bot_test"], + help="the channels to join", + metavar="CHANNELS", +) +parser.add_argument( + "-n", + "--nick", + dest="nick", + default="banterbot", + help="the nick to use", + metavar="NICK", +) +parser.add_argument( + "-o", + "--owner", + dest="owner", + default="krowbar", + help="the owner of this bot", + metavar="OWNER", +) + +args = parser.parse_args() +print(args) + +bot = Bot( + channels = args.channels, + nickname = args.nick, + ops = [ args.owner ], + plugin_dir = "{}_plugins".format(args.nick), + server = args.server, + port = args.port + ) + +if __name__ == "__main__": + bot.start() diff --git a/Code/irc/madlibbot/util.py b/Code/irc/madlibbot/util.py new file mode 120000 index 0000000..7f16d68 --- /dev/null +++ b/Code/irc/madlibbot/util.py @@ -0,0 +1 @@ +../util.py \ No newline at end of file diff --git a/Code/irc/tildebot_plugins/rollcall.py b/Code/irc/tildebot_plugins/rollcall.py new file mode 100644 index 0000000..5a91c38 --- /dev/null +++ b/Code/irc/tildebot_plugins/rollcall.py @@ -0,0 +1,7 @@ +#!/usr/bin/python3 + +import pinhook.plugin + +@pinhook.plugin.register('!rollcall') +def rollcall_plugin(msg): + return pinhook.plugin.message("tildebot reporting! I respond to !tilde !tildescore") diff --git a/Code/irc/tildebot_plugins/tilde.py b/Code/irc/tildebot_plugins/tilde.py new file mode 100755 index 0000000..86dae1d --- /dev/null +++ b/Code/irc/tildebot_plugins/tilde.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 + +import pinhook.plugin +import util.tilde + +@pinhook.plugin.register('!tildescore') +def tildescore_plugin(msg): + return pinhook.plugin.message(util.tilde.show_tildescore(msg.nick)) + +@pinhook.plugin.register('!jackpot') +def jackpot_plugin(msg): + return pinhook.plugin.message(util.tilde.show_jackpot()) + +# ADMIN PLUGIN +@pinhook.plugin.register('!debug') +def debug_plugin(msg): + if msg.nick not in msg.ops: + return + if msg.arg: + util.tilde.DEBUG = (msg.arg.lower == 'true' or msg.arg.lower == 't') + return pinhook.plugin.message("DEBUG set to '{}'".format(util.tilde.DEBUG)) + +# ADMIN PLUGIN +@pinhook.plugin.register('!tilde_requests') +def tilde_requests_plugin(msg): + if msg.nick not in msg.ops and not util.tilde.DEBUG: + return + return pinhook.plugin.message("Outstanding requests: {}".format(",".join(util.tilde.challenges) if util.tilde.challenges else "(none)")) + +@pinhook.plugin.register('!tilde') +def tilde_plugin(msg): + if msg.channel != util.tilde.GOOD_CHAN and not util.tilde.DEBUG: + return pinhook.plugin.message("{} is a meanie and gets no tildes. **Tildebot now only gives out tildes in the {} channel.**".format(msg.nick, util.tilde.GOOD_CHAN)) + if msg.nick not in util.tilde.challenges: + return pinhook.plugin.message(util.tilde.challenge(msg.channel, msg.nick, msg.timestamp)) + +@pinhook.plugin.listener('tilde_guess') +def tilde_guess_plugin(msg): + if msg.nick in util.tilde.challenges and (msg.channel == util.tilde.GOOD_CHAN or util.tilde.DEBUG): + return pinhook.plugin.message(util.tilde.challenge_response(msg.nick, msg.timestamp, msg.text)) + diff --git a/Code/irc/topicbot_plugins/topic.py b/Code/irc/topicbot_plugins/topic.py new file mode 100644 index 0000000..d7e36bb --- /dev/null +++ b/Code/irc/topicbot_plugins/topic.py @@ -0,0 +1,32 @@ +#!/usr/bin/python3 + +import pinhook.plugin +from util import topic + +import inflect + +@pinhook.plugin.listener('topic') +def topic_listener(msg): + if msg.text.startswith("TOPIC"): + topic.count_topic(msg.channel, msg.nick, msg.time, msg.text) + +@pinhook.plugin.register('!topic') +def topic_plugin(msg): + return pinhook.plugin.message(topic.get_topic(msg.channel, msg.nick, msg.time)) + +@pinhook.plugin.register('!settopic') +def set_topic_plugin(msg): + return pinhook.plugin.action("TOPIC {} {}".format(msg.channel, msg.arg)) + +@pinhook.plugin.register('!randomtopic') +def set_topic_plugin(msg): + return pinhook.plugin.message(topic.random_topic(msg.channel, msg.nick, msg.time, msg.nick == msg.owner)) + +@pinhook.plugin.register('!suggesttopic') +def suggest_topic_plugin(msg): + return pinhook.plugin.message(topic.random_topic(msg.channel, msg.nick, msg.time, False)) + +@pinhook.plugin.register('!thistory') +def topic_history_plugin(msg): + return pinhook.plugin.message(topic.topic_history(msg.channel, msg.nick, msg.arg)) + diff --git a/Code/irc/util/__init__.py b/Code/irc/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Code/irc/util/acronymFinder.py b/Code/irc/util/acronymFinder.py new file mode 100644 index 0000000..0fcb7f4 --- /dev/null +++ b/Code/irc/util/acronymFinder.py @@ -0,0 +1,99 @@ +#!/usr/bin/python3 +import requests +from bs4 import BeautifulSoup +import random +import string + +dict = "/usr/share/dict/american-english" +(userId, token) = open("/home/krowbar/.secret/s4token").readline().rstrip().split(",") + + +def get_acros(word, silly, short): + acros = [] + url = "http://www.stands4.com/services/v2/abbr.php?uid={}&tokenid={}&term={}".format( + userId, token, word + ) + soup = BeautifulSoup(requests.get(url).content, "html5lib") + results = soup.find_all("result") + # there are lots of cases where the same definition is repeated multiple times under different categories. this is dumb so we should do a little more work + defs = [] + for r in results: + rdef = r.find("definition").text + match = next((x for x in defs if x["definition"].lower() == rdef.lower()), None) + if match is not None: + # if we find a match, add the category to the existing categories and increase the score + match["categories"].append( + ( + (r.find("parentcategoryname").text + "\\") + if r.find("parentcategoryname") + else "" + ) + + r.find("categoryname").text + ) + match["score"] = str(float(match["score"]) + float(r.find("score").text)) + else: # add a new item + defs.append( + { + "term": r.find("term").text, + "definition": r.find("definition").text, + "categories": [ + ( + (r.find("parentcategoryname").text + "\\") + if r.find("parentcategoryname") + else "" + ) + + r.find("categoryname").text + ], + "score": r.find("score").text, + } + ) + + for d in sorted(defs, key=lambda x: float(x["score"]), reverse=True): + # print d; + if short is True: + acros.append('"%s"' % d["definition"]) + else: + acros.append( + ( + '{}: "{}" ({}, score: {})'.format( + d["term"], + d["definition"], + ", ".join(d["categories"]), + d["score"], + ) + ) + ) + if silly is True: + newDef = [] + words = open(dict, "r").readlines() + try: + for idx, letter in enumerate(word): + newWord = random.choice( + list(filter( + lambda w: (idx is 0 or not w.strip().lower().endswith("'s")) + and w.lower().startswith(letter.lower()), + words, + )) + ).strip() + print(str(idx) + " -> " + newWord) + newDef.append(newWord) + newWord = string.capwords(" ".join(newDef)) + if short is True: + acros.append('"%s"' % newWord) + else: + acros.append( + ( + '{}: "{}" ({}, score: {})'.format( + word.upper(), newWord, "Tilde.town Original", "0" + ) + ) + ) + except IndexError: + acros.append("Future hazy, try again later: Tilde.town Error") + if short is True: + shortList = acros[0:5] + if len(acros) > 5: + shortList.append(acros[-1]) + return [word.upper() + ": " + ", ".join(shortList)] + else: + return acros diff --git a/Code/irc/util/banter.py b/Code/irc/util/banter.py new file mode 100644 index 0000000..7b8ab53 --- /dev/null +++ b/Code/irc/util/banter.py @@ -0,0 +1,60 @@ +#!/usr/bin/python3 + +import random +import re + +banter_file = "/home/krowbar/Code/irc/data/banterscores.txt" + +def score_banter(nick, messageText): + print(re) + score = 5 + with open(banter_file, "r") as banterfile: + bantz = banterfile.readlines() + words = messageText.strip("\n").split(" ") + for word in words: + for bant in bantz: + bword = bant.strip("\n").split("|") + if re.sub("[^a-z0-9]+", "", word.lower()) == bword[0]: + score += int(bword[1]) + + score += messageText.count("!") * 2 # hype is banter + score -= messageText.count("!!!") * 6 # too much hype is not banter + score += messageText.count("#") * 3 # hashs are mad bantz + score -= messageText.count("##") * 6 # but too many is garbage + + names = ["mate", "lad", "my best boy"] + compliment = [ + "top-drawer", + "top-shelf", + "bangin'", + "legendary", + "smashing", + "incredible", + "impeccable", + "stunning", + ] + + msg = "" + if score > 100: + msg = "Truely {}, {}! That was some #banter! You earned a {} for that!".format( + random.choice(compliment).capitalize(), random.choice(names), score + ) + elif score > 50: + msg = "{} #banter! You get a {} from me!".format( + random.choice(compliment).capitalize(), score + ) + elif score > 10: + msg = "{} #banter. You get a {}".format( + random.choice(["acceptible", "reasonable", "passable"]).capitalize(), score + ) + else: + msg = "That {} #banter, {}. I'll give you a {}. Maybe try again?".format( + random.choice( + ["was hardly", "was barely", "wasn't", "won't pass for", "was awful"] + ), + random.choice(["lad", "lah", "boy", "son"]), + score, + ) + + return msg + diff --git a/Code/irc/util/defineWord.py b/Code/irc/util/defineWord.py new file mode 100644 index 0000000..e9839ad --- /dev/null +++ b/Code/irc/util/defineWord.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +import requests +from bs4 import BeautifulSoup +import random + + +def define(word): + defs = [] + url = "http://www.merriam-webster.com/dictionary/{}".format(word) + soup = BeautifulSoup(requests.get(url).content, "html.parser") + head = soup.find("div", id="headword") + if head: + for p in head.find_all("p"): + defs.append(p.text) + return defs + + +key = open("/home/krowbar/.secret/key").readline().rstrip() + + +def defWord(word, short=True): + defs = [] + url = "http://www.dictionaryapi.com/api/v1/references/collegiate/xml/{}?key={}".format( + word, key + ) + soup = BeautifulSoup(requests.get(url).content, "html5lib") + entry = soup.find("entry") + if entry: + for d in entry.find_all("dt"): + defs.append(d.text) + if short: + return " ".join(defs) + else: + return defs diff --git a/Code/irc/util/dict_puzzle.py b/Code/irc/util/dict_puzzle.py new file mode 100644 index 0000000..30dbf1a --- /dev/null +++ b/Code/irc/util/dict_puzzle.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +import random +import inflect + +p = inflect.engine() +dictionary = "/usr/share/dict/american-english-small" +BAD_WORDS_FILE = "/home/krowbar/Code/irc/data/badwords.txt" + + +def gen_wordlist(): + # I feel weird calling this "get_wordlist" when it's a generator without calling out that I do in fact realise it's weird - ~deltawitch + # how about gen_wordlist + with open(BAD_WORDS_FILE, "r") as fp: + bad_words = set(fp) + + for word in open(dictionary).readlines(): + if "'" not in word and word not in bad_words: + yield word.rstrip() + + +def get_puzzle(): + dict_words = list(gen_wordlist()) + words = random.sample(dict_words, 3) + key = random.randrange(0, 3) # get values 1-3 + puzzle = "When alphebetized, what is the {} in {}?".format( + p.ordinal(p.number_to_words(key + 1)), ", ".join(words) + ) + words.sort() + answer = words[key] + return [answer, puzzle] + + +def get_anagram(maxlen=6): + dict_words = [ + word for word in gen_wordlist() if len(word) > 2 and len(word) < maxlen + 1 + ] + word = random.choice(dict_words) + anagram = list(word) + random.shuffle(anagram) + anagram = "".join(anagram) + + # Anagrams can have multiple answers, so we provide a check function that accepts all possibilities + def answer_checker(guess): + # Check for exact match + if guess == word: + return True + # Bail out early if they didn't even use all the same letters + if sorted(guess) != sorted(word): + return False + # Ok, gotta actually check if it's a word now + return any(guess == item for item in gen_wordlist()) + + return [answer_checker, "Unscramble the following word: '{}'".format(anagram)] diff --git a/Code/irc/util/duckduckgo.py b/Code/irc/util/duckduckgo.py new file mode 100755 index 0000000..767254e --- /dev/null +++ b/Code/irc/util/duckduckgo.py @@ -0,0 +1,198 @@ +import requests +import urllib +import json as j +import sys + +__version__ = 0.242 + + +def query( + query, + useragent="python-duckduckgo " + str(__version__), + safesearch=True, + html=False, + meanings=True, + **kwargs +): + """ + Query DuckDuckGo, returning a Results object. + + Here's a query that's unlikely to change: + + >>> result = query('1 + 1') + >>> result.type + 'nothing' + >>> result.answer.text + '1 + 1 = 2' + >>> result.answer.type + 'calc' + + Keword arguments: + useragent: UserAgent to use while querying. Default: "python-duckduckgo %d" (str) + safesearch: True for on, False for off. Default: True (bool) + html: True to allow HTML in output. Default: False (bool) + meanings: True to include disambiguations in results (bool) + Any other keyword arguments are passed directly to DuckDuckGo as URL params. + """ % __version__ + + safesearch = "1" if safesearch else "-1" + html = "0" if html else "1" + meanings = "0" if meanings else "1" + params = { + "q": query, + "o": "json", + "kp": safesearch, + "no_redirect": "1", + "no_html": html, + "d": meanings, + } + params.update(kwargs) + encparams = urllib.parse.urlencode(params) + url = "http://api.duckduckgo.com/?" + encparams + + request = requests.get(url, headers={"User-Agent": useragent}) + + return Results(request.json()) + + +class Results(object): + def __init__(self, json): + self.type = { + "A": "answer", + "D": "disambiguation", + "C": "category", + "N": "name", + "E": "exclusive", + "": "nothing", + }.get(json.get("Type", ""), "") + + self.json = json + self.api_version = None # compat + + self.heading = json.get("Heading", "") + + self.results = [Result(elem) for elem in json.get("Results", [])] + self.related = [Result(elem) for elem in json.get("RelatedTopics", [])] + + self.abstract = Abstract(json) + self.redirect = Redirect(json) + self.definition = Definition(json) + self.answer = Answer(json) + + self.image = Image({"Result": json.get("Image", "")}) + + +class Abstract(object): + def __init__(self, json): + self.html = json.get("Abstract", "") + self.text = json.get("AbstractText", "") + self.url = json.get("AbstractURL", "") + self.source = json.get("AbstractSource") + + +class Redirect(object): + def __init__(self, json): + self.url = json.get("Redirect", "") + + +class Result(object): + def __init__(self, json): + self.topics = json.get("Topics", []) + if self.topics: + self.topics = [Result(t) for t in self.topics] + return + self.html = json.get("Result") + self.text = json.get("Text") + self.url = json.get("FirstURL") + + icon_json = json.get("Icon") + if icon_json is not None: + self.icon = Image(icon_json) + else: + self.icon = None + + +class Image(object): + def __init__(self, json): + self.url = json.get("Result") + self.height = json.get("Height", None) + self.width = json.get("Width", None) + + +class Answer(object): + def __init__(self, json): + self.text = json.get("Answer") + self.type = json.get("AnswerType", "") + + +class Definition(object): + def __init__(self, json): + self.text = json.get("Definition", "") + self.url = json.get("DefinitionURL") + self.source = json.get("DefinitionSource") + + +def get_zci( + q, + web_fallback=True, + priority=["answer", "abstract", "related.0", "definition"], + urls=True, + **kwargs +): + """A helper method to get a single (and hopefully the best) ZCI result. + priority=list can be used to set the order in which fields will be checked for answers. + Use web_fallback=True to fall back to grabbing the first web result. + passed to query. This method will fall back to 'Sorry, no results.' + if it cannot find anything.""" + + ddg = query("\\" + q, **kwargs) + response = "" + + for p in priority: + ps = p.split(".") + type = ps[0] + index = int(ps[1]) if len(ps) > 1 else None + + result = getattr(ddg, type) + if index is not None: + if not hasattr(result, "__getitem__"): + raise TypeError("%s field is not indexable" % type) + result = result[index] if len(result) > index else None + if not result: + continue + + if result.text: + response = result.text + if result.text and hasattr(result, "url") and urls: + if result.url: + response += " (%s)" % result.url + if response: + break + + # if there still isn't anything, try to get the first web result + if not response and web_fallback: + if ddg.redirect.url: + response = ddg.redirect.url + + # final fallback + if not response: + response = "Sorry, no results." + + return response + + +def main(): + if len(sys.argv) > 1: + q = query(" ".join(sys.argv[1:])) + keys = q.json.keys() + keys.sort() + for key in keys: + sys.stdout.write(key) + if type(q.json[key]) in [str, unicode]: + print(":", q.json[key]) + else: + sys.stdout.write("\n") + for i in q.json[key]: + print("\t", i) + else: + print("Usage: %s [query]" % sys.argv[0]) diff --git a/Code/irc/util/evil.py b/Code/irc/util/evil.py new file mode 100644 index 0000000..dc1ee61 --- /dev/null +++ b/Code/irc/util/evil.py @@ -0,0 +1,6 @@ +import random + +evil_file = "/home/krowbar/logs/evildata.txt" + +def get_thing(): + return "If I Ever Become an Evil Overlord: {}".format(random.choice(list(open(evil_file))).rstrip()) diff --git a/Code/irc/util/nameFix.py b/Code/irc/util/nameFix.py new file mode 100644 index 0000000..6587f3a --- /dev/null +++ b/Code/irc/util/nameFix.py @@ -0,0 +1,27 @@ +#!/usr/bin/python3 + +import json + +names = { + "jumblesal": "jumblesale", + "hardmath1": "kc", + "hardmath123": "kc", + "bendorphan": "endorphant", + "endorphan": "endorphant", + "synergian": "synergiance", +} + +def fixName(name): + return names[name] if name in names else name + +def get_name(name): + names_file = "/home/jumblesale/Code/canonical_names/canonical_names.json" + try: + with open(names_file) as names_data: + names = json.load(names_data) + try: + return names[name]["userName"] + except KeyError: + return name + except IOError: + return name # if we didn't already diff --git a/Code/irc/util/newBanter.py b/Code/irc/util/newBanter.py new file mode 100644 index 0000000..d8b91c7 --- /dev/null +++ b/Code/irc/util/newBanter.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 +import random +import re + +dictDir = "/usr/share/dict/" + +def getBanter(morphWord="bant", dictName="words"): + with open(dictDir + dictName, "r") as dict: + # get rid of all the words with apostrophes + words = list(filter(lambda word: re.search(r"^[^']*$", word), dict.readlines())) + + head = getBanterHead(words, morphWord) + tail = getBanterTail(words, morphWord) + + if head == "" and tail == "": + return "" # dang, we just failed + else: + # pick randomly between non-empty strings + return random.choice([w for w in [head, tail] if w != ""]) + +def getBanterHead(words, morphWord): + morphHead = morphWord[0:-1] + morphLast = morphWord[-1] + + filtered = list(filter(lambda word: re.search(morphHead, word), words)) + if len(filtered) == 0: + return "" # nothing applicable found + + word = random.choice(filtered).strip("\n") + end = word.find(morphHead) + len(morphHead) + if end == len(word): + return word + morphLast + else: + if "aeiou".find(word[end]) > -1: # just append 't' + return word[:end] + morphLast + word[end:] + else: # replace the letter with 'b' + return word[:end] + morphLast + word[end + 1 :] + +def getBanterTail(words, morphWord): + morphTail = morphWord[1:] + morphFirst = morphWord[0] + + filtered = list(filter(lambda word: re.search(morphTail, word), words)) + if len(filtered) == 0: + return "" # nothing applicable found + + word = random.choice(filtered).strip("\n") + start = word.find(morphTail) + if start == 0: + return morphFirst + word + else: + if "aeiou".find(word[start]) > -1: # just append a 'b' + return word[:start] + morphFirst + word[start:] + else: # replace the letter with 'b' + return word[: start - 1] + morphFirst + word[start:] diff --git a/Code/irc/util/numberwang.py b/Code/irc/util/numberwang.py new file mode 100644 index 0000000..c004938 --- /dev/null +++ b/Code/irc/util/numberwang.py @@ -0,0 +1,191 @@ +#!/usr/bin/python3 + +import inflect +import random + +p = inflect.engine() +LIMIT_GUESSING = True +MIN_ROUNDS = 5 +MAX_ROUNDS = 12 +SCORE_FILE = "/home/krowbar/Code/irc/data/numberwangscores.txt" +SHOW_TOP_NUM = 5 +GOOD_CHAN = "#bots" + +roundsLeft = 0 +bonusRound = 0 +guesses = 0 +lastGuesser = "" +currentScores = {} + + +def resetGlobals(): + global roundsLeft + global bonusRound + global guesses + global lastGuesser + global currentScores + roundsLeft = 0 + bonusRound = 0 + guesses = 0 + lastGuesser = "" + currentScores.clear() + + +# returns a number of lines to be printed out. Numbers represent time to sleep +# between prints +def start_numberwang(channel, user): + if channel != GOOD_CHAN: + return [ "Numberwang has been disabled in {} due to spamminess. Please join {} to start a game.".format(channel, GOOD_CHAN) ] + + message = [ "{} started a game".format(user) ] + resetGlobals() + message.append("It's time for Numberwang!") + message.append(1) + message.append("Here's how to play:") + message.append("1. There are 10 rounds") + message.append("2. Each round lasts 10 seconds. You're up against the clock!") + message.append("3. Play your numbers, as long as they're between 0 and 99.") + message.append("4. That's Numberwang!") + message.append(2) + message.append("Let's get started!") + global roundsLeft + global bonusRound + roundsLeft = random.randint(MIN_ROUNDS, MAX_ROUNDS) + bonusRound = random.randint(2, roundsLeft - 1) + # debug print + print( + "There will be {} rounds with the bonus on round {}".format( + str(roundsLeft), str(roundsLeft - bonusRound + 1) + ) + ) + + +def print_scores(): + scoreStrs = [] + first = True + for name in currentScores: + scoreStrs.append( + "{} is {} on {}".format( + name, + ("also " if not first and random.randint(1, 3) == 3 else ""), + currentScores[name], + ) + ) + first = False + return p.join(scoreStrs) + + +def guess_numberwang(user, messageText): + global guesses + global lastGuesser + global currentScores + global roundsLeft + # print(user + " guessed '" + messageText + "'") + guess = re.sub( + "[^0-9]", "", messageText.split()[0] + ) # must have a number in the first 'word' + messages = [] + if guess: + if LIMIT_GUESSING and user == lastGuesser: + messages.appen("{}, you just guessed! Give another player a try!".format(user)) + else: + guesses += 1 + lastGuesser = user + ###CORRECT GUESS### + if ( + random.randint(0, 10) > 10 - guesses + ): # the more guesses, the higher the probability + guesses = 0 + lastGuesser = "" + message = "{}: THAT'S NUMBERWANG!".format(user) + points = random.randint(2, 10) * ( + random.randint(2, 4) if roundsLeft == bonusRound else 1 + ) + if user in currentScores.keys(): + currentScores[user] += points + else: + currentScores[user] = points + roundsLeft -= 1 + message.append(2) + if roundsLeft == 0: + messages.append("Numberwang is now over. Thank you for playing!") + messages.append("Final scores:") + messages.extend(print_scores()) + save_scores() + else: + messages.extend(print_scores()) + newRoundStr = "" + if roundsLeft == 1: + newRoundStr += "The last round is Wangernumb!" + elif roundsLeft == bonusRound: + newRoundStr += "**Bonus Round!**" + else: + newRoundStr += "New Round!" + if random.randint(1, 10) > 8: + newRoundStr += " Let's rotate the board!" + messages.append("{} Start guessing!".format(newRoundStr)) + + ###INCORRECT GUESS### + else: + messages.append("{}, {}, {} Numberwang!".format( + random.choice(["Sorry", "I'm sorry", "No", "Nope"]), + user, + random.choice( + [ + "that's not", + "that is not", + "that isn't", + "that is not", + "that won't make", + "that will not make", + ] + ), + )) + + return messages + + +def stop_numberwang(user): + # print(user + " stopped a game") + resetGlobals() + return ["Numberwang has been stopped. No points have been awarded. {} is such a party pooper!".format(user)] + + +def save_scores(): + with open(SCORE_FILE, "r+") as scorefile: + scores = scorefile.readlines() + scorefile.seek(0) + scorefile.truncate() + for line in scores: + for name in currentScores: + score = line.strip("\n").split("&^%") + if score[0] == name: + line = "{}&^%{}\n".format( + score[0], int(score[1]) + currentScores[name] + ) + del currentScores[name] + break + scorefile.write(line) + + for name in currentScores: # new wangers + scorefile.write("{}&^%{}\n".format(name, currentScores[name])) + + +def show_highscores(): + with open(SCORE_FILE, "r") as scorefile: + scores = [] + for line in scorefile.readlines(): + sline = line.strip("\n").split("&^%") + scores.append((int(sline[1]), sline[0])) + scores = sorted(scores, reverse=True)[:SHOW_TOP_NUM] + + return ["====TOP WANGERS===="] + [" :== ~{} ({} points!) ==".format(score[1], score[0]) for score in scores] + +def show_user_score(user): + with open(SCORE_FILE, "r") as scorefile: + for line in scorefile.readlines(): + score = line.strip("\n").split("&^%") + if user == score[0]: + return ["{}: Your global numberwang score is {}!".format(user, score[1])] + # if we don't find a score line + return ["{}: You haven't scored any points yet!".format(user)] diff --git a/Code/irc/util/puzzle.py b/Code/irc/util/puzzle.py new file mode 100644 index 0000000..6ab6973 --- /dev/null +++ b/Code/irc/util/puzzle.py @@ -0,0 +1,187 @@ +#!/usr/bin/python3 + +import random +import hashlib +import inflect + +import util.quote_puzzle +import util.dict_puzzle +import util.textcaptcha + +p = inflect.engine() +primes = [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71] +fuzz_amount = 3 + + +def make_puzzle(obfuscate=True, roll=None): + answer = 0 + bonus = 0 + puzzle = random.choice( + [ + "Prove you're not a robot: ", + "Are you a robot?: ", + "Anti-bot check: ", + "Counter-cnd0rphant measures: ", + "Cosn0k countermeasures: ", + "Anti-tilde7hief precautions: ", + "Pro-l0gin challenge: ", + "Riddle me this: ", + "Would you like to play a game? ", + "How about this? " + ] + ) + puzzle += random.choice( + [ + "What is", + "What do you get from", + "What do you get with", + "What is the value of", + "Can you answer", + "Can you tell me", + "Ask wiz3bot what is", + "Does anybody know", + "Who knows", + "Guess what", + "Calculate", + "Find out" + ] + ) + puzzle += " " + roll = roll or random.randrange(0, 21) + var1 = random.randrange(1, 10) + var2 = random.randrange(1, 10) + var3 = random.randrange(1, 20) + var4 = random.randrange(1, 20) + let1_ord = random.randrange(ord("a"), ord("z") + 1) + + if roll == 0: + answer = var1 + var2 + puzzle += "{} {} {}".format( + p.number_to_words(var1), + random.choice(["and", "plus", "sum", "add"]), + p.number_to_words(var2), + ) + + elif roll == 1: + answer = var1 * var2 + puzzle += "{} {} {}".format( + p.number_to_words(var1), + random.choice(["times", "multiply", "multiplied by", "product"]), + p.number_to_words(var2), + ) + + elif roll == 2: + if var2 > var1: + var1, var2 = var2, var1 + answer = var1 - var2 + puzzle += "{} {} {}".format( + p.number_to_words(var1), + random.choice(["minus", "subtract", "take away", "less"]), + p.number_to_words(var2), + ) + + elif roll == 3: + if var2 > var1: + var1, var2 = var2, var1 + answer = var1 * 2 / var2 + puzzle += "{} {} {} (no remainder)".format( + p.number_to_words(var1 * 2), + random.choice(["divided by", "over"]), + p.number_to_words(var2), + ) + + elif roll == 4: + answer = var1 ** var2 + puzzle += "{} to the {} power".format( + p.number_to_words(var1), p.ordinal(p.number_to_words(var2)) + ) + + elif roll == 5: + p1 = random.choice(primes) + p2 = random.choice(primes) + + def answer(guess): + # Check the the numbers entered are correct, regardless of order + # or surrounding whitespace. + attempt = sorted(word.strip() for word in guess.split(",")) + correct = sorted([str(p1), str(p2)]) + return attempt == correct + + bonus = 1 + puzzle += "{} when factored into its two primes (answer in the form of the two primes with a comma between)".format( + p.number_to_words(p1 * p2) + ) + + elif roll == 6: + prime = random.choice(primes) + answer = prime % var1 + puzzle += p.number_to_words(prime) + " modulus " + p.number_to_words(var1) + elif roll == 7: + if let1_ord + var1 > ord("z"): + let1_ord -= var1 + answer = chr(let1_ord + var1) + puzzle += "letter comes {} letters after '{}'".format( + p.number_to_words(var1), chr(let1_ord) + ) + + obfuscate = False + elif roll == 8: + if let1_ord - var1 < ord("a"): + let1_ord += var1 + answer = chr(let1_ord - var1) + puzzle += "letter comes {} letters before '{}'".format( + p.number_to_words(var1), chr(let1_ord) + ) + + obfuscate = False + elif roll == 9: + answer, puzzle = util.quote_puzzle.get_quote() + obfuscate = False + elif roll == 10: + answer = str(min(var1, var2, var3, var4)) + puzzle += "the {} of {}, {}, {}, and {}".format( + random.choice(["smallest", "lowest"]), + p.number_to_words(var1), + p.number_to_words(var2), + p.number_to_words(var3), + p.number_to_words(var4), + ) + + elif roll == 11: + answer = str(max(var1, var2, var3, var4)) + puzzle += "the {} of {}, {}, {}, and {}".format( + random.choice(["biggest", "largest"]), + p.number_to_words(var1), + p.number_to_words(var2), + p.number_to_words(var3), + p.number_to_words(var4), + ) + + elif roll <= 14: # 12-14 + answer, puzzle = util.dict_puzzle.get_puzzle() + obfuscate = False + elif roll <= 17: # 15-17 + answer, puzzle = util.dict_puzzle.get_anagram() + obfuscate = False + elif roll == 18: + answer, puzzle = util.quote_puzzle.get_chuck() + obfuscate = False + elif roll <= 20: #19-20 + captcha = util.textcaptcha.get_captcha() + puzzle = captcha['q'] # the question part of the captcha + # print(captcha) + answer = lambda a : hashlib.md5(a.encode()).hexdigest() in captcha['a'] # check if the answer is correct + obfuscate = False + bonus = 1 + + # Add a question mark on the end of the question + if puzzle[-1] != "?": + puzzle += "?" + + if obfuscate == True: + for _ in range(fuzz_amount): + idx = random.randrange(len(puzzle) - 2) # get between 0 and string length (except the ? mark) + puzzle = "".join( + [puzzle[0:idx], chr(random.randint(33, 126)), puzzle[idx + 1 :]] + ) + return [puzzle, answer, bonus] diff --git a/Code/irc/util/quote_puzzle.py b/Code/irc/util/quote_puzzle.py new file mode 100644 index 0000000..67388a6 --- /dev/null +++ b/Code/irc/util/quote_puzzle.py @@ -0,0 +1,27 @@ +#!/usr/bin/python3 + +import json +import requests +import random +import re + +quotefile = "/home/karlen/irc/quotes.txt" +chuckfile = "/home/krowbar/Code/irc/data/chuck.txt" +chuckApi = "https://api.icndb.com/jokes/random" + +def get_quote(): + quotes = open(quotefile, "r").read().split("---") + quote, attr = random.choice(quotes).strip().splitlines() + quote = quote[:200] # get only the first 200 chars + word = random.choice([q for q in quote.split(" ") if len(q) > 1]) + quote = quote.replace(word, re.sub(r"[a-zA-Z]", "_", word)) + return [word, 'Fill in the blank: "' + quote + '" ' + attr] + +def get_chuck(): + #chucks = open(chuckfile, "r").readlines() + #chuck = random.choice(chucks).rstrip()[:200] # get only the first 200 chars + # ha ha! let's see if we can confus login + chuck = json.loads(requests.get(chuckApi).content.decode())['value']['joke'][:200] + word = random.choice([w for w in chuck.split(" ") if len(w) > 1 and w.lower() != "chuck" and w.lower() != "norris"]) + chuck = chuck.replace(word, re.sub(r"[a-zA-Z]", "_", word)).replace(""", "\"") + return [word, 'Fill in the blank: "{}"'.format(chuck)] diff --git a/Code/irc/util/rhymesWith.py b/Code/irc/util/rhymesWith.py new file mode 100644 index 0000000..37a5f98 --- /dev/null +++ b/Code/irc/util/rhymesWith.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +import requests + +# from lxml.html import fromstring +from bs4 import BeautifulSoup +import random + + +def getRhymes(word): + words = [] + url = "http://www.rhymer.com/RhymingDictionaryLast/%s.html" % word + soup = BeautifulSoup(requests.get(url).content, "html.parser") + + for t in soup.find_all("table", "table"): + words.append( + random.choice( + [ + w + for w in t.text.split("\n") + if w not in [u"", u"\xa0"] and "-" not in w + ] + ) + ) + return words + + +def rhymeZone(word): + words = [] + url = ( + "http://rhymezone.com/r/rhyme.cgi?Word=%s&typeofrhyme=perfect&org1=syl&org2=l&org3=y" + % word + ) + soup = BeautifulSoup(requests.get(url).content, "html.parser") + + for t in soup.find_all("a", "d"): + w = t.text.rstrip() + if w not in [u"", u"\xa0"] and "?" not in t: + words.append(w) + random.shuffle(words) + return words[0:5] diff --git a/Code/irc/util/textcaptcha.py b/Code/irc/util/textcaptcha.py new file mode 100644 index 0000000..e91d64f --- /dev/null +++ b/Code/irc/util/textcaptcha.py @@ -0,0 +1,11 @@ +#!/usr/bin/python3 + +import hashlib +import json +import requests + +url = "http://api.textcaptcha.com/krowbar@tilde.town.json" + +def get_captcha(): + return json.loads(requests.get(url).content.decode()) + diff --git a/Code/irc/util/tilde.py b/Code/irc/util/tilde.py new file mode 100755 index 0000000..81af28b --- /dev/null +++ b/Code/irc/util/tilde.py @@ -0,0 +1,282 @@ +#!/usr/bin/python3 + +# Import some necessary libraries. +import random +import inflect +import util.puzzle + +p = inflect.engine() +challenges = {} +SCORE_FILE = "/home/krowbar/Code/irc/data/tildescores.txt" +JACKPOT_FILE = "/home/krowbar/Code/irc/data/tildejackpot.txt" +JACKPOT_MIN = 3 +DEBUG = False +GOOD_CHAN = "#bots" # The name of the "good" channel that the bot is allowed to run in + +def too_recent(time1, time2): + return int(float(time1)) - int(float(time2)) < 60 * 60 + + +def get_positive(): + return random.choice( + [ + "Yes", + "Yep", + "Yeppers", + "Correct", + "You got it", + "Yeah", + "Right on", + "Uh-huh", + "Positive", + "Totally right", + "Close enough", + "That's it", + "Winner, winner", + "Bingo", + "Affirmative", + ] + ) + + +def get_negative(): + return random.choice( + [ + "No", + "Nope", + "Sorry", + "Wrong", + "Nuh-uh", + "Negatory", + "Incorrect", + "Not today", + "Try again", + "Maybe later", + "Maybe next time", + "Probably not", + "Answer hazy", + "Not quite", + "Not even close", + "Not for you", + "I think not" + ] + ) + + +def get_superlative(score): + if score > 4: + return random.choice( + [ + "super cool", + "totally rad", + "extraordinary", + "dynomite", + "#topdrawer", + "a #TopLad", + "the cat's meow", + "a tilde town hero", + "my favorite person", + "incredibly lucky", + "unbelievable", + "a tilde town hunk", + "could bring all the boys to the yard", + "worth twice their weight in gold", + "the hero we need", + "no ordinary townie", + ] + ) + elif score > 2: + return random.choice( + [ + "really cool", + "pretty neat", + "rather nice", + "a dynamic doggo", + "radical", + "intense", + "pretty lucky", + "knows the territory", + "has what it takes", + "has mad skillz", + "going the distance", + "a hard worker", + "my sunshine", + "ready to rumble", + ] + ) + else: + return random.choice( + [ + "cool", + "nice", + "acceptible", + "good enough", + "a promising pupper", + "better than a horse", + "swell", + "a little lucky", + "just credible", + "my friend", + "probably not a robot", + "valuable to the team", + "now trending", + ] + ) + + +def get_bad_thing(): + return random.choice( + [ + "is a meanie", + "mugs me right off", + "miffed me off", + "is worse than a horse", + "smells like a ghost", + "probably didn't bathe today", + "probably shakes babies", + "didn't guess hard enough", + "isn't lucky", + "smells of elderberries", + "should reconsider their life choices", + "did't believe in the heart of the tilde", + "came to the wrong side of town", + "should have stopped while they were ahead", + "requires annotations from an authoratative source", + "could have been a contender", + "spreads vicious rumors", + "drank my milkshake", + "is probably cheating", + "is trying too hard", + "didn't really try", + "should try harder", + "caught me in a bad mood", + "should have gone with their first choice", + "did not receive IFR clearance from tower", + "was tardy for class", + "is on double secret probation", + "forgot their keys", + "forgot to bribe me", + "forgot to close the door", + "waited too long", + "doesn't call me on my cellphone", + "isn't wearing a seatbelt", + "didn't courtesy flush", + "asked a bot for answers", + "was right but I didn't feel like it", + "is right on opposite day", + "actually answered the last question", + "has their pants on backwards", + "forgot their own name", + ] + ) + + +def get_prize(user, isHuman, bonus=0): + prizes = [1] * 8 + [2] * 4 + [3] * 2 + [5] * isHuman # no 5pt prize for non-humans + prize = random.choice(prizes) + bonus + if ( + random.randint(1, 10) > 6 - 4 * isHuman + ): # 80% of the time it's a normal prize (40% for not humans) + return [ + prize, + "{}: {}! You are {} and get {} tildes!".format( + user, + (get_positive() if isHuman else get_negative()), + get_superlative(prize), + p.number_to_words(prize), + ), + ] + else: # 20% of the time its a jackpot situation + with open(JACKPOT_FILE, "r+") as jackpotfile: + jackpot = int(jackpotfile.readline().strip("\n")) + jackpotfile.seek(0) + jackpotfile.truncate() + if ( + random.randint(1, 10) > 1 or not isHuman + ): # 90% of the time it's a non-prize. non-humans never win jackpot + new_jackpot = jackpot + max(1, prize) + jackpotfile.write( + str(new_jackpot) + ) # increase the jackpot by the prize size + return [ + 0, + "{} {} and gets no tildes! (Jackpot is now {} tildes)".format( + user, get_bad_thing(), new_jackpot + ), + ] + else: # hit jackpot! + jackpotfile.write(str(JACKPOT_MIN)) + return [ + jackpot, + "{} hit the jackpot and won **{}** tildes!".format( + user, p.number_to_words(jackpot) + ), + ] + + +def show_jackpot(): + with open(JACKPOT_FILE, "r") as jackpotfile: + jackpot = int(jackpotfile.readline().strip("\n")) + return "The jackpot is currently {} tildes!".format(p.number_to_words(jackpot)) + + +def give_tilde(user, time, human, bonus=0): + found = False + with open(SCORE_FILE, "r+") as scorefile: + scores = scorefile.readlines() + scorefile.seek(0) + scorefile.truncate() + message = "" + for score in scores: + name, score_on_file, timestamp = score.strip("\n").split("&^%") + if name == user: + found = True + if too_recent(time, timestamp) and not DEBUG: + message = "{} asked for a tilde too recently and {}. Try again later.".format(user, get_bad_thing()) + else: + prizevalue, prizetext = get_prize(user, human, bonus) + score = "{}&^%{}&^%{}\n".format( + user, str(int(score_on_file) + prizevalue), time + ) + message = prizetext + scorefile.write(score) + if not found: + prizevalue, prizetext = get_prize(user, True, bonus) + scorefile.write("{}&^%{}&^%{}\n".format(user, str(prizevalue + 1), time)) + message = "Welcome to the tilde game, {}! Here's {} free tildes to start you off.".format(user, p.number_to_words(prizevalue + 1)) + + return message + + +def show_tildescore(user): + with open(SCORE_FILE, "r") as scorefile: + for _idx, score in enumerate(scorefile): + record = score.strip("\n").split("&^%") + if record[0] == user: + return "{} has {} tildes!".format(user, p.number_to_words(record[1])) + # person has not played yet + return "{} has no tildes yet!".format(user) + + +def challenge(channel, user, time): + global challenges + challenge = util.puzzle.make_puzzle() + challenges[user] = challenge[1:] + return "{}: {}".format(user, challenge[0]) + + +def challenge_response(user, time, msg): + global challenges + # print(msg) + response = "" + if user in challenges: + answer, bonus = challenges[user] + if (callable(answer) and answer(msg.lower())) or ( + msg.lower() == str(answer).lower() or msg == p.number_to_words(answer) + ): + response = give_tilde(user, time, True, bonus) + else: + response = give_tilde(user, time, False, 0) + del challenges[user] + # delete the user from challenges either way + return response diff --git a/Code/irc/util/topic.py b/Code/irc/util/topic.py new file mode 100644 index 0000000..398d646 --- /dev/null +++ b/Code/irc/util/topic.py @@ -0,0 +1,95 @@ +#!/usr/bin/python3 + +import inflect +import random + +scores_file = "/home/krowbar/Code/irc/data/topicscores.txt" +channel_topics = "/home/krowbar/Code/irc/data/topics_{}.txt" +random_topics = "/home/krowbar/Code/irc/data/randomtopics.txt" + +p = inflect.engine() + +def get_topic(channel, user, time): + # topic scores are saved as &^%&^% + with open(scores_file, "r") as scorefile: + scores = scorefile.readlines() + userscore = 1 + found = False + with open(scores_file, "w") as scorefile: + for idx, score in enumerate(scores): + data = score.strip("\n").split("&^%") + if data[0] == user: + found = True + userscore = int(data[1]) + 1 + scores[idx] = data[0] + "&^%" + str(userscore) + "&^%" + data[2] + "\n" + scorefile.writelines(scores) + if not found: + scorefile.write(user + "&^%1&^%0\n") + + with open(channel_topics.format(channel), "r") as topics: + topic = topics.readlines()[-1].strip("\n").split("&^%", 3) + byuser = util.get_name(topic[1]) + return "I've told you {} times! It's \"{}\" (set by {} {})".format( + p.number_to_words(userscore), + topic[2], + byuser, + util.pretty_date(int(topic[0])), + ) + + +def count_topic(channel, user, time, msg): + with open(channel_topic.format(channel), "a") as topics: + topics.write(time + "&^%" + user + "&^%" + msg + "\n") + with open(scores_file, "r") as scorefile: + scores = scorefile.readlines() + userscore = 1 + found = False + with open(scores_file, "w") as scorefile: + for idx, score in enumerate(scores): + data = score.strip("\n").split("&^%") + if data[0] == user: + found = True + userscore = int(data[2]) + 1 + scores[idx] = data[0] + "&^%" + data[1] + "&^%" + str(userscore) + "\n" + scorefile.writelines(scores) + if not found: + scorefile.write(user + "&^%0&^%1") + return "{} has changed the topic {} times!".format(user, p.number_to_words(userscore)) + + +def set_topic(channel, user, time, msg): + ircsock.send("TOPIC " + channel + " :" + msg + "\n") + return count_topic(channel, user, time, msg) + + +def random_topic(channel, user, time, setTopic=False): + with open(random_topics) as rtopics: + msg = random.choice(rtopics.readlines()).strip("\n") + if setTopic: + set_topic(channel, user, time, msg) + else: + return "Suggested Topic: {}".format(msg) + + +def rollcall(channel): + return "topicbot reporting! I respond to !topic !settopic !suggesttopic !thistory" + + +def topic_history(channel, user, count): + try: + iCount = int(count.split()[1]) + except (ValueError, IndexError): + iCount = 3 + if iCount > 10: + iCount = 10 + if iCount < 1: + iCount = 3 + + with open(channel_topics.format(channel), "r") as topicsfile: + message = "Ok, here are the last {} topics".format(p.number_to_words(iCount)) + for idx, topic in enumerate(reversed(topicsfile.readlines()[-iCount:])): + topic = topic.strip("\n").split("&^%", 3) + byuser = util.get_name(topic[1]) + message += "\n{}: {} (set by {} {})".format( str(idx + 1), topic[2], byuser, util.pretty_date(int(topic[0])) ) + + return message diff --git a/Code/irc/util/tumblr.py b/Code/irc/util/tumblr.py new file mode 100644 index 0000000..8d4c64d --- /dev/null +++ b/Code/irc/util/tumblr.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 +from bs4 import BeautifulSoup +import random +import re +import requests + +def tumble(url): + # Find the max pages + soup = BeautifulSoup(requests.get(url).content, "html.parser") + pages = soup.findAll("span", "page-numbers")[0].text.split("/")[ + 1 + ] # this could totally fail several ways + tries = 3 + while True: + try: + page = random.randrange(1, int(pages) + 1) + + # Parse a page + soup = BeautifulSoup( + requests.get("{}/page/{}".format(url, page)).content, "html.parser" + ) + article = random.choice(soup.findAll("article")) + quote = article.find("blockquote").text.replace("\n", "") + if len(article.find("footer").findAll("ul")) > 1: + quote += re.sub( + "\n+", " ", article.find("footer").findAll("ul")[0].text + ) + # the hash tags + quote += ( + "(" + + re.sub("\n+", " ", article.find("footer").findAll("ul")[1].text) + + ")" + ) + # and the date and notes + else: + quote += ( + "(" + + re.sub("\n+", " ", article.find("footer").findAll("ul")[0].text) + + ")" + ) + # just the date and notes + + return quote + except: # sometimes we fail. let's retry a few times + if tries == 0: + return "" + else: + tries -= 1 + break diff --git a/Code/irc/util/welch.py b/Code/irc/util/welch.py new file mode 100644 index 0000000..563d96b --- /dev/null +++ b/Code/irc/util/welch.py @@ -0,0 +1,10 @@ +import random + + +def get_thing(): + file = "/home/krowbar/logs/welchdata.txt" + thing = "" + return ( + "Thing Mr. Welch can no longer do in a RPG #" + + random.choice(list(open(file))).rstrip() + ) diff --git a/Code/irc/util/whoSaid.py b/Code/irc/util/whoSaid.py new file mode 100644 index 0000000..a318a73 --- /dev/null +++ b/Code/irc/util/whoSaid.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 + +import datetime +import fileinput +import time +import calendar +import re +import operator +from util import nameFix + +MAX_NODES = 5 + +logfile = "/home/archangelic/irc/log" +timeCutoff = calendar.timegm(time.gmtime()) - (3 * 7 * 24 * 60 * 60) # 3 weeks + +def whoSaid(text): + if not text: + return "No text given :(" + else: + result = whoSaid_data(text) + date = datetime.date.fromtimestamp(result["timecutoff"]) + dateStr = date.strftime("%B %d") + if not result["data"]: + msg = "Nobody said '%s' since %s" % (text, dateStr) + else: + msg = "Since %s, %s said '%s' %d times" % ( + dateStr, + result["data"][0][0], + text, + result["data"][0][1], + ) + if len(result["data"]) > 1: + msg += " and %s said it %d times" % ( + result["data"][1][0], + result["data"][1][1], + ) + return msg + +def whoSaid_data(word): + word = word.lower() + userData = ( + {} + ) # hash keyed by "user" that contains a hash of mentioned other users with count + # Get a list of all user names by checking the logs for people who have said things + with open(logfile, "r") as log: + for line in log: + try: + time, user, message = line.split("\t", 3) + time = int(time) + user = nameFix.fixName(user).lower() + except ValueError: + continue # There are some bad lines in the log file that we'll ignore if we can't parse + + if time > timeCutoff and message[0] is not "!" and word in message.lower(): + if user in userData: + userData[user] += 1 + else: + userData[user] = 1 + userData = sorted(userData.items(), key=operator.itemgetter(1), reverse=True) + return {"timecutoff": timeCutoff, "data": userData} + diff --git a/Code/irc/util/wikiphilosophy.py b/Code/irc/util/wikiphilosophy.py new file mode 100644 index 0000000..be1eab1 --- /dev/null +++ b/Code/irc/util/wikiphilosophy.py @@ -0,0 +1,87 @@ +#!/usr/bin/python3 +from bs4 import BeautifulSoup +import random +import requests + + +def get_philosophy(word, max_steps=20): + step_words = [word] + steps = 0 + + url = "https://en.wikipedia.org/wiki/%s" % word + while steps < max_steps: + print("url: {}".format(url)) + soup = BeautifulSoup(requests.get(url).content, "html.parser") + title = soup.find("h1", id="firstHeading") + content = soup.find("div", id="mw-content-text") + if not content: + break + item = [ + item + for item in content.find_all("a") + if not item.get("class") + and not item.get("target") + and item.get("title") + and not "Wikipedia:" in item.get("title") + and not "Category:" in item.get("title") + and not "Help:" in item.get("title") + and not "Portal:" in item.get("title") + and not "Special:" in item.get("title") + and not "Talk:" in item.get("title") + and not "Template:" in item.get("title") + and not "File:" in item.get("title") + and "Edit section:" not in item.get("title") + and "Commons:" not in item.get("title") + and not item.get("title") in step_words + ][0] + step_words.append(item.get("title")) + # print item.get('title') + "\n" + url = "https://en.wikipedia.org{}".format(item.get("href")) + steps += 1 + return step_words + + +def containsAny(str, set): + return 1 in [c in str for c in set] + + +def get_philosophy_lower(word, max_steps=20): + step_words = [word] + steps = 0 + + url = "https://en.wikipedia.org/wiki/{}".format(word.replace(" ", "%20")) + while steps < max_steps: + print("url: {}".format(url)) + soup = BeautifulSoup(requests.get(url).content, "html.parser") + + if soup.find(id="noarticletext"): + step_words.append("(not found)") + break + + title = soup.find("h1", id="firstHeading") + content = soup.find("div", id="mw-content-text") + if not content: + break + links = [ + item + for item in content.find_all("a") + if not item.get("class") + and item.text + and item.text[0].islower() + and not containsAny(item.text, ":()") + and item.get("title") + and not containsAny(item.get("title"), ":()") + and not item.get("title") in step_words + ] + if not links: + step_words.append("(dead end)") + break + item = links[0] # grab the first good link item + # print "Checking %s %s" % (item.get('title'), item.text) + step_words.append(item.get("title")) + if item.get("title") == "Philosophy": + break + # print item.get('title') + "\n" + url = "https://en.wikipedia.org%s" % item.get("href") + steps += 1 + return step_words diff --git a/Code/irc/util/xkcdApropos.py b/Code/irc/util/xkcdApropos.py new file mode 100644 index 0000000..e729a8e --- /dev/null +++ b/Code/irc/util/xkcdApropos.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 +import requests +from bs4 import BeautifulSoup +import util.duckduckgo + +def xkcd(query): + res = util.duckduckgo.get_zci("site:xkcd.com " + query) + title = "" + try: # ddg returns a url for these searches. i want a title as well + title = BeautifulSoup(requests.get(res).content, "html.parser").title.text + except: + pass # just swallow the error. maybe the result wasn't a url or something else bad happened + return [(((title + " - ") if title else "") + res)] + + +# never mind, blocked by ddg +# def xkcd_links(query): +# url = "https://duckduckgo.com/html/?q=site%3Axkcd.com+" + query.replace(' ', '+') +# soup = BeautifulSoup(requests.get(url).content, 'html.parser') +# items = soup.find_all("a", class_="result__a") +# print items +# items = list(filter(lambda i: i[0:8] == 'xkcd.com', +# [i.find(class_="result__title").text.strip() for i in items])) +# print items +# def pretty_link(item): +# url = item.find(class_="result__url").text.strip() +# title = item.find(class_="result__title").text.strip() +# return (title + ' - ' + url) if title else url +# +# links = map(lambda url: pretty_link(url), items) +# return links diff --git a/Code/irc/wangbot_plugins/numberwang.py b/Code/irc/wangbot_plugins/numberwang.py new file mode 100644 index 0000000..bce6037 --- /dev/null +++ b/Code/irc/wangbot_plugins/numberwang.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 + +import pinhook.plugin +import time +from util import numberwang + +@pinhook.plugin.register('!rollcall') +def rollcall_plugin(msg): + return pinhook.plugin.message("Is it time for Numberwang? It might be! Start a new game with !numberwang or stop a current game with !wangernumb Get your score with !myscore and the list of top wangers with !topwangers") + +# the numberwang lib returns message lists that may contain numbers. +# if we get one of those, sleep for that long +def print_messages(msg, messages): + for line in messages: + if isinstance(line, int) or isinstance(line, float): + time.sleep(line) + else: + msg.privmsg(msg.channel, line) + +@pinhook.plugin.register('!numberwang') +def numberwang_plugin(msg): + if numberwang.roundsLeft == 0: + print_messages(msg, numberwang.start_numberwang(msg.channel, msg.nick)) + +@pinhook.plugin.register('!wangernumb') +def wangernumb_plugin(msg): + if numberwang.roundsLeft > 0: + print_messages(msg, numberwang.stop_numberwang(msg.nick)) + +@pinhook.plugin.register('!topwangers') +def top_plugin(msg): + print_messages(msg, numberwang.show_highscores()) + +@pinhook.plugin.register('!myscore') +def my_score_plugin(msg): + print_messages(msg, numberwang.show_user_score(msg.nick)) + +@pinhook.plugin.listener('numberwang') +def numberwang_listener(msg): + if msg.text.split()[0] in ['!numberwang', '!wangernumb', '!topwangers', '!myscore'] or numberwang.roundsLeft is 0 or msg.channel != numberwang.GOOD_CHAN: + return + + print_messages(msg, numberwang.guess_numberwang(msg.channel, msg.user, msg.arg)) diff --git a/madlibs/baseball.madlib b/madlibs/baseball.madlib new file mode 100644 index 0000000..fae8a0a --- /dev/null +++ b/madlibs/baseball.madlib @@ -0,0 +1,13 @@ +It was the bottom of the {{an ordinal number, ending in -th or -rd}} and the {{a city}} {{an animal, plural}} were down {{a number}} points against their rivals, the {{a city}} {{an occupation}}. +Young {{a male name}} Jackson was up to the plate. He'd have to {{a verb}} this pitch if his team any chance of winning the game. +The pitch came in {{an adjective}}, the batter swung but was too {{another adjective}}! Strike one! +The second pitch, but {{a noun}} got in the batter's {{a body part}}! Strike two! +"{{an encouraging aphorism}}" somebody shouted from the stands. +The pitcher wound up and threw a {{a shape}}. The batter hit the ball but it went wide! "{{an adjective}} ball!" The umpire yelled. +The batter was feeling very {{an emotion}}, {{a liquid}} was pouring down his face. +"You'll never be able to hit this one!" the pitcher {{a verb, past tense}} before letting loose! +The batter closed his eyes, wished on his lucky {{an astrological body, plural}}, and swung. +"{{a loud sound effect}}!" +The ball flew over the {{a direction}} field wall! Home run! +After he ran the bases, his teammates cheered and slapped him on the {{a body part}}! +The game was tied up, but it still wasn't over... (To be continued?) diff --git a/madlibs/feature_test.madlib b/madlibs/feature_test.madlib new file mode 100644 index 0000000..5dfa930 --- /dev/null +++ b/madlibs/feature_test.madlib @@ -0,0 +1,3 @@ +Here is a number {{a number#}} ordinal {{a number#|ordinal}}, numeric {{a number#|numeric}} +Here is a word {{a word#1}} upper {{a word#1|upper}} lower {{a word#1|lower}} +Here is another word {{a word#2}} title {{a word#2|title}}