コード例 #1
0
ファイル: youtube.py プロジェクト: Mego/Karkat
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
コード例 #2
0
ファイル: shorten.py プロジェクト: Mego/Karkat
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)
コード例 #3
0
ファイル: restart.py プロジェクト: Mego/Karkat
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)
コード例 #4
0
ファイル: threads.py プロジェクト: Mego/Karkat
 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
コード例 #5
0
 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__)
コード例 #6
0
ファイル: threads.py プロジェクト: Mego/Karkat
    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
コード例 #7
0
 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}
コード例 #8
0
ファイル: threads.py プロジェクト: svkampen/Karkat
 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}
コード例 #9
0
ファイル: threads.py プロジェクト: svkampen/Karkat
 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__)
コード例 #10
0
ファイル: __init__.py プロジェクト: Mego/Karkat
    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)
コード例 #11
0
ファイル: spellchecker.py プロジェクト: Mego/Karkat
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)