def __initialise__(name, bot, stream): cb = Callback() cb.initialise(name, bot, stream) @cb.background def refresh_tokens(line): if yt.tokensExpired(): yt.refresh_tokens() @cb.command(["youtube", "yt"], "(-\d\s+)?(.+)", public=".@", private="!", usage="You04Tube│ Usage: [.@]youtube [-NUM_RESULTS] <query>", error="You04Tube│ Failed to get search results.") def youtube(message, nresults, query): if nresults: nresults = min(-int(nresults.strip()), lines[message.prefix]) else: nresults = lines[message.prefix] results = yt.search(query, results=nresults) for i in results: data = {"title": i["snippet"]["title"], "channel": i["snippet"]["channelTitle"], "url": i["id"]["videoId"]} yield templates[message.prefix] % data bot.register("privmsg", youtube) bot.register("ALL", refresh_tokens) # keep tokens current
def __initialise__(name, server, printer): cb = Callback() cb.initialise(name, server, printer) class LinkGrabber(object): def __init__(self): self.links = {} @Callback.background def trigger_linkget(self, line): x = list(line.split(" ")) if x[2][0] == "#": x[3] = x[3][1:] self.links.setdefault(server.lower(x[2]), []).extend([i for i in x[3:] if re.match("^(http|https|ftp)\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\-\._\?\,\'/\\\+&%\$#\=~])*$", i)]) lg = LinkGrabber() server.register("privmsg", lg.trigger_linkget) @cb.threadsafe @cb.command("shorten shortgo bl bitly bit.ly".split(), "(.*)", private="!", public="@.", usage="12bit.ly│ Usage: !shorten <url>", error="05bit.ly│ Unable to generate shortlink.") def shortgo(message, url): if not url: url = lg.links[server.lower(message.context)][-1] return "12%s│ %s" % ("bit.ly" * message.text.startswith("@"), URL.format(URL.shorten(url))) server.register("privmsg", shortgo)
def __initialise__(name, bot, printer): cb = Callback() cb.initialise(name, bot, printer) @cb.command("restart", public=":", private="", admin=True) def set_restart(message): bot.restart = True printer.raw_message("QUIT :Restarting...") bot.register("privmsg", set_restart)
def __init__(self, trigger, function): self.trigger = trigger self.module = inspect.getmodule(function) self.name = self.module.__name__ + "." + function.__qualname__ self.funct = function if Callback.isInline(function): self.cbtype = self.INLINE elif Callback.isThreadsafe(function): self.cbtype = self.THREADSAFE elif Callback.isBackground(function): self.cbtype = self.BACKGROUND else: self.cbtype = self.GENERAL
def loadplugin(self, mod): """ The following can optionally be defined to hook into karkat: __callbacks__: A mapping of callbacks. __icallbacks__: A mapping of inline callbacks. __initialise__(name, botobj, printer) : A function to initialise the module. __destroy__(): A function triggered on bot death. """ if "__initialise__" in dir(mod): mod.__initialise__(self) print(" Initialised %s." % mod.__name__) if "__callbacks__" in dir(mod): for trigger in mod.__callbacks__: for callback in mod.__callbacks__[trigger]: self.register(trigger, callback) print(" Registered callback: %s" % callback.__name__) if "__icallbacks__" in dir(mod): for trigger in mod.__icallbacks__: for callback in mod.__icallbacks__[trigger]: self.register(Callback.inline(trigger), callback) print(" Registered inline callback: %s" % callback.__name__) if "__destroy__" in dir(mod): self.register("DIE", mod.__destroy__) print(" Registered destructor: %s" % mod.__destroy__.__name__)
def dump(self): """ Dumps the contents of the caller's queue, returns it, then terminates. """ newq = Work() requeue = [] with self.work._lock: # This blocks the queue. # The lock will be acquired after the queue feeds a task # to the caller, or the caller is still executing a task. lastarg = self.work.last while not self.work.empty(): funct, args = self.work.get() if Callback.isThreadsafe(funct) or funct != lastarg: newq.put((funct, args)) else: requeue.append((funct, args)) for funct, args in requeue: # These functions aren't threadsafe, so we can't safely fork # off a queue with these tasks because we know that the # function is probably already executing. self.work.put((funct, args)) self.terminate() return newq
def __init__(self, trigger, function): self.trigger = trigger self.module = inspect.getmodule(function) self.name = function.__qualname__ if self.module: self.name = self.module.__name__ + "." + self.name self.funct = function if Callback.isInline(function): self.cbtype = self.INLINE self.__mutex__ = {function} elif Callback.isThreadsafe(function): self.cbtype = self.THREADSAFE self.__mutex__ = set() elif Callback.isBackground(function): self.cbtype = self.BACKGROUND self.__mutex__ = {function} else: self.cbtype = self.GENERAL if hasattr(function, '__mutex__'): self.__mutex__ = function.__mutex__ else: self.__mutex__ = {function}
def __initialise__(name, bot, printer): cb = Callback() cb.initialise(name, bot, printer) class WolframAlpha(object): t_max = 62 h_max = 7 h_max_settings = {"#lgbteens": 3, "#teenagers":3} t_lines = 12 timeout = 45 results = ["Result", "Response", "Infinite sum", "Decimal approximation", "Decimal form", "Limit", "Definition", "Definitions", "Description", "Balanced equation", "Chemical names and formulas", "Conversions to other units", "Roots", "Root", "Definite integral", "Plot", "Plots"] input_categories = ["Input interpretation", "Input"] def breakdown(self, data, width): """ Uses heuristics to guess the data type of a piece of data, then generates an appropriate representation. Parses meta, lists, numbers, URLs, numbered lists and tables. Truncates long or multiple lines. """ data = parser.delete_blank(data) data = parser.transpose_prepare(data) data = parser.replace_encoded_chars(data) data = parser.replace_symbol_words(data) data = parser.parse_maths(data) data = parser.parse_supersubs(data) data = parser.parse_all_sqrts(data) data = parser.shorten_urls(data) data = parser.rechunk(data) data = parser.format(data) return data @lru_cache(maxsize=4096) def wolfram(self, query): response = urllib.request.urlopen("http://api.wolframalpha.com/v2/query?"+urllib.parse.urlencode({"appid": apikeys["key"], "input":query, "scantimeout":str(self.timeout)}), timeout=self.timeout) response = etree.parse(response) data = collections.OrderedDict() for pod in response.findall("pod"): title = pod.get("title") data[title] = "\n".join([i.findtext("plaintext") or i.find("img").get("src") for i in pod.findall("subpod")]) if not data[title].strip(): del data[title] return data def wolfram_format(self, query, category=None, h_max=None): try: answer = self.wolfram(query) except urllib.error.URLError: return "05Wolfram08Alpha failed to respond. Try again later or go to " + URL.format(URL.shorten("http://www.wolframalpha.com/input/?i=%s" % urllib.parse.quote_plus(query))) if not answer: return "05Wolfram08Alpha returned no results for '07%s'" % query for i in self.input_categories: if i in answer: header = answer[i] remove = i break else: header ="'%s'"%query remove = False header = str.join(" ", header.split()) h_max = h_max or self.h_max if not category: results = answer if remove: results = collections.OrderedDict([(k, v) for k, v in answer.items() if k != remove]) if category is not None: # Guess the category for i in self.results: if i in answer: results = {i: results[i]} break else: results = {list(results.keys())[0]: list(results.values())[0]} else: results = max(answer, key=lambda x:difflib.SequenceMatcher(None, category, x).ratio()) results = {results:answer[results]} output = [spacepad("05Wolfram08Alpha 04 ", " %s" % header, self.t_max)] t_max = striplen(output[0]) results = collections.OrderedDict([(k, self.breakdown(v.split("\n"), t_max - 3)) for k, v in results.items()]) total_lines = sum([min(len(results[x]), h_max) for x in results]) if total_lines > self.t_lines: # Too many lines, print the available categories instead. for i in justifiedtable(sorted(results.keys(), key=len) + ["05Categories"], t_max-3): output.append(" 04⎪ %s" % i) output[-1] = " 04⎩" + output[-1][5:] elif results: if len(results) == 1 and len(list(results.values())[0]) == 1: # Single line: Shorten output catname = list(results.keys())[0] if catname in self.results: output = ["08│ %s " % list(results.values())[0][0]] else: output = [spacepad("08│ %s " % list(results.values())[0][0], "07%s" % catname, t_max)] else: for category in results: lines = [x.rstrip() for x in results[category]] output.append(spacepad(" 08⎨ %s " % lines.pop(0), " 07%s" % category, t_max)) truncated = lines[:h_max] for line in truncated: output.append(" 08⎪ " + line) if len(truncated) < len(lines): omission = "%d more lines" % (len(lines) - h_max) length = t_max - len(omission) - 5 output[-1] = " 08⎬�" + ("-"*int(length)) + " 07%s" % omission else: output.append(" 08‣ 05No plaintext results. See " + URL.format(URL.shorten("http://www.wolframalpha.com/input/?i=%s" % urllib.parse.quote_plus(query)))) return "\n".join(i.rstrip() for i in output) def getoutputsettings(self, target): if bot.isIn(target, self.h_max_settings): return self.h_max_settings[bot.lower(target)] else: return self.h_max @cb.threadsafe @Callback.msghandler def shorthand_trigger(self, user, context, message): pattern = re.match(r"([~`])(.*?\1 ?|([\"']).*?\3 ?|[^ ]+ )(.+)", message.text) if pattern: prefix, category, quoted, query = pattern.groups() category = category.rstrip() if quoted: category = category[1:-1] elif category and category[-1] == prefix: category = category[:-1] target, msgtype = {"~": (context, "PRIVMSG"), "`": (user.nick, "NOTICE")}[prefix] printer.message(self.wolfram_format(query, category, h_max=self.getoutputsettings(target)), target, msgtype) @cb.threadsafe @cb.command(["wa", "wolfram"], "(.+)", usage="05Wolfram08Alpha04⎟ Usage: [.@](wa|wolfram) 03query") def trigger(self, message, query): return self.wolfram_format(query, h_max=self.getoutputsettings(message.context)) wa = WolframAlpha() bot.register("privmsg", wa.trigger) bot.register("privmsg", wa.shorthand_trigger)
def __initialise__(name, server, printer): cb = Callback() cb.initialise(name, server, printer) class SpellChecker(object): DBFILE = "spellchecker.db" LOCKFILE = "ircwords_locked" users = {} os.makedirs(server.get_config_dir(), exist_ok=True) dictionary = enchant.DictWithPWL("en_US", pwl=server.get_config_dir("ircwords")) alternate = enchant.Dict("en_GB") reset_at = 1500 reset_to = int(round(math.log(reset_at))) last = None last_correction = None threshhold = 2 wordsep = "/.:^&*|+=-?,_()" literalprefixes = ".!/@<`:~=+" dataprefixes = "#$<[/" contractions = ["s", "d", "ve", "nt", "m"] def __init__(self): try: self.locked = [open(server.get_config_dir(self.LOCKFILE)).read().split("\n")] except: self.locked = [] open(server.get_config_dir(self.LOCKFILE), "w") self.db = server.get_config_dir(self.DBFILE) if not os.path.exists(self.db): os.makedirs(server.get_config_dir(), exist_ok=True) # Initialise the db with sqlite3.connect(self.db) as db: db.execute("CREATE TABLE typos (timestamp int, nick text, channel text, server text, word text);") db.execute("CREATE TABLE settings (server text, context text, threshhold int);") def getSettings(self, context): with sqlite3.connect(self.db) as db: c = db.cursor() c.execute("SELECT threshhold FROM settings WHERE server=? AND context=?", (name, server.lower(context))) result = c.fetchone() return result if result is None else result[0] def setThreshhold(self, context, threshhold): with sqlite3.connect(self.db) as db: db.execute("DELETE FROM settings WHERE server=? AND context=?", (name, server.lower(context))) if threshhold is not None: db.execute("INSERT INTO settings VALUES (?, ?, ?)", (name, server.lower(context), threshhold)) @classmethod def stripContractions(cls, word): if word[0] == word[-1] and word[0] in "'\"": word = word[1:-1] last = word.rsplit("'", 1)[-1].lower() return word[:-len(last) - 1] if last in cls.contractions else word @classmethod def isWord(cls, word): # excessively non-alpha strings are not words. if len([i for i in word if not (i.isalpha() or i in "'")]) >= cls.threshhold: return False # words prefixed with the following are not real words if word[0] in cls.dataprefixes: return False # words with unicode in them are not words if any(ord(c) > 127 for c in word): return False return True @classmethod def isLiteral(cls, sentence): return not sentence or sentence[0] in cls.literalprefixes @classmethod def spellcheck(cls, sentence): sentence = ircstrip(sentence) if cls.isLiteral(sentence): return sentence = [cls.stripContractions(i) for i in sentence.split() if cls.isWord(i)] errors = [i for i in sentence if not (cls.dictionary.check(i) or cls.alternate.check(i))] suggestions = [set(cls.alternate.suggest(i)) | set(cls.dictionary.suggest(i)) for i in errors] # reduce the suggestions suggestions = [{"".join(z for z in i if z.isalpha() or z in "'").lower() for i in x} for x in suggestions] wrong = [] append = {} for i, word in enumerate(errors): if "".join(i for i in word if i.isalpha()).lower() not in suggestions[i]: token = set(word) & set(cls.wordsep) if token: token = token.pop() words = word.split(token) suggested = [cls.spellcheck(i) for i in words] suggested = [list(i.values())[0] if i else None for i in suggested] if all(suggested): wrong.append(word) elif any(suggested): if suggested[0]: suggested = suggested[0] suggested = [i + token + words[1] for i in suggested] else: suggested = suggested[1] suggested = [words[0] + token + i for i in suggested] append[word] = suggested else: # Repetition for emphasis is allowed over a threshhold string = re.escape(word) pattern = re.sub(r"(.+?)\1\1+", r"(\1)+", string, flags=re.IGNORECASE) truncated = re.sub(r"(.+?)\1\1+", r"\1\1", word, flags=re.IGNORECASE) truncated2 = re.sub(r"(.+?)\1\1+", r"\1", word, flags=re.IGNORECASE) suggestions[i] |= set(cls.alternate.suggest(truncated)) | set(cls.dictionary.suggest(truncated)) | set(cls.alternate.suggest(truncated2)) | set(cls.dictionary.suggest(truncated2)) if not any(re.match(pattern, x) for x in suggestions[i]): wrong.append(word) if wrong or append: wrong = {i: cls.alternate.suggest(i) for i in wrong} wrong.update(append) # wrong = {k: [i for i in v if difflib.SequenceMatcher(None, k, i).quick_ratio() > 0.6] for k, v in wrong.items()} return wrong # Give a dictionary of words : [suggestions] @Callback.background def passiveCorrector(self, line): msg = Message(line) nick = msg.address.nick if not self.dictionary.check(nick): self.dictionary.add(nick) nick = server.lower(nick) if msg.text and msg.text[0] in "@!.:`~/": return if msg.text.startswith("\x01ACTION") and msg.text.endswith("\x01"): data = self.spellcheck(msg.text[8:-1]) else: data = self.spellcheck(msg.text) user = self.users.setdefault(nick, [0, 0]) user[0] += len(data) if data else 0 user[1] += len(line.split(" ")) - 3 if user[1] > self.reset_at: user[0] /= self.reset_to user[1] /= self.reset_to if data: with sqlite3.connect(self.db) as typos: for i in data: typos.execute("INSERT INTO typos VALUES (?, ?, ?, ?, ?)", (time.time(), nick, msg.context, name, i)) threshhold_context = self.getSettings(msg.context) threshhold_user = self.getSettings(nick) if threshhold_user == threshhold_context == None: return threshhold = min(threshhold_context, threshhold_user, key=lambda x: float("inf") if x is None else x) if user[1] and 1000*user[0]/user[1] > threshhold: sentence_substitute = ircstrip(msg.text) if sentence_substitute.startswith("\x01ACTION") and sentence_substitute.endswith("\x01"): sentence_substitute = "%s %s" % (msg.address.nick, sentence_substitute[8:-1]) for word, sub in data.items(): sentence_substitute = sentence_substitute.replace(word, "\x02%s\x02" % sub[0] if sub else strikethrough(word)) printer.message(("%s: " % msg.address.nick) + sentence_substitute, msg.context) if len(data) == 1: self.last = list(data.keys())[0] else: self.last = None @Callback.threadsafe @cb.command("spell spellcheck".split(), "(.+)") def activeCorrector(self, msg, query): if (self.dictionary.check(query) or self.alternate.check(query)): return "%s, %s is spelt correctly." % (msg.address.nick, query) else: suggestions = self.alternate.suggest(query)[:6] return "Suggestions: %s" % ("/".join(suggestions)) def updateKnown(self, y): x = y.split(" ") newword = re.match(r":(%s[^\a]?\s*)?([^\s]+)( i|')s a( real)? word(!| FORCE| LOCK)?.*" % server.nick, " ".join(x[3:]), flags=re.IGNORECASE) notword = re.match(r":(%s[^\a]?\s*)?([^\s]+)( isn't| is not|'s not) a( real)? word(!| FORCE| LOCK)?.*" % server.nick, " ".join(x[3:]), flags=re.IGNORECASE) match = newword or notword if not server.is_admin(x[0]) and match.group(2).lower() in self.locked: printer.message("F**K OFF.", x[2] if x[2][0] == "#" else Address(x[0]).nick) return if newword: word = newword.group(2) if word.lower() == "that": word = self.last if server.is_admin(x[0]) and newword.group(5): self.locked.append(word.lower()) self.saveLocked() if not word: printer.message("What is?", x[2] if x[2][0] == "#" else Address(x[0]).nick) elif self.dictionary.check(word): printer.message("I KNOW.", x[2] if x[2][0] == "#" else Address(x[0]).nick) else: self.dictionary.add(word) printer.message("Oh, sorry, I'll remember that.", x[2] if x[2][0] == "#" else Address(x[0]).nick) self.last_correction = word if notword: word = notword.group(2) if word.lower() == "that": word = self.last_correction if server.is_admin(x[0]) and notword.group(5): self.locked.append(word.lower()) self.saveLocked() if self.dictionary.is_added(word): self.dictionary.remove(word) printer.message("Okay then.", x[2] if x[2][0] == "#" else Address(x[0]).nick) else: printer.message("I DON'T CARE.", x[2] if x[2][0] == "#" else Address(x[0]).nick) def saveLocked(self): with open(server.get_config_dir(self.LOCKFILE), "w") as f: f.write("\n".join(self.locked)) @cb.command("spellchecker", "(on|off|\d+)") def correctChannel(self, msg, threshhold): if threshhold == "off": if self.getSettings(msg.context) is not None: self.setThreshhold(msg.context, None) return "FINE." else: return "IT'S OFF DICKBUTT" else: query = int(threshhold) if threshhold.isdigit() else 0 self.setThreshhold(msg.context, query) return "DONE." spellchecker = SpellChecker() spellchecker.dictionary._add = spellchecker.dictionary.add spellchecker.dictionary.add = lambda x: spellchecker.dictionary._add(x) if "\n" not in x else sys.__stdout__.write("f**k you.") server.register("privmsg", spellchecker.correctChannel) server.register("privmsg", spellchecker.updateKnown) server.register("privmsg", spellchecker.activeCorrector) server.register("privmsg", spellchecker.passiveCorrector)