Пример #1
0
class UrbanDictionaryPlugin(plugin.PluginObject):

    commands = None
    config = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()

        ### Register commands
        self.commands.register_command("urbandictionary",
                                       self.urbandictionary_cmd,
                                       self,
                                       "urbandictionary.definition",
                                       aliases=["ud"], default=True)

    def urbandictionary_cmd(self, protocol, caller, source, command, raw_args,
                            parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        ### Get LastFM username to use
        username = None
        if len(args) == 0:
            caller.respond("Usage: {CHARS}urbandictionary <term>")
            return

        term = " ".join(args)

        try:
            definition, permalink = self.get_definition(term)
            if definition is None:
                source.respond('[UD] "%s" is not defined yet' % term)
            else:
                # TODO: Limit definition length
                source.respond('[UD] "%s" - %s - (%s)' %
                               (term,
                                to_bytes(definition)
                                .replace('\r', '')
                                .replace('\n', ' '),
                                to_bytes(permalink)))
        except:
            self.logger.exception("Cannot get definition for '%s'" % term)
            source.respond("There was an error while fetching the definition -"
                           " please try again later, or alert a bot admin.")

    def get_definition(self, term):
        request = urllib2.Request("http://api.urbandictionary.com/v0/define?" +
                                  urllib.urlencode({'term': term}))
        # F**k you PEP8. F**k you with the largest, spikiest dragon d***o, in
        # every orifice you have, and more.
        request.add_header('User-agent', 'Mozilla/5.0 '
                                         '(X11; U; Linux i686; '
                                         'en-US; rv:1.9.0.1) '
                                         'Gecko/2008071615 '
                                         'Fedora/3.0.1-1.fc9-1.fc9 '
                                         'Firefox/3.0.1')
        try:
            definition = json.load(urllib2.urlopen(request))["list"][0]
            return definition["definition"], definition["permalink"]
        except IndexError:
            return None, None
Пример #2
0
class RoulettePlugin(plugin.PluginObject):

    commands = None

    channels = {}
    users = {}

    def setup(self):
        self.commands = CommandManager()
        self.commands.register_command("rroulette", self.play, self,
                                       "russianroulette.rroulette",
                                       aliases=["roulette"], default=True)

    def addChannel(self, channel):
        if channel not in self.channels.keys():
            players = []
            curplayers = []
            shots = 0
            deaths = 0
            chambers = 6

            data = {"players": players, "shots": shots, "deaths": deaths,
                    "chambers": chambers, "curplayers": curplayers}

            self.channels[channel] = data

    def play(self, protocol, caller, source, command, raw_args,
             parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        self.logger.trace("Caller: %s" % repr(caller))
        self.logger.trace("Source: %s" % repr(source))
        self.logger.trace("Result: %s" % isinstance(source, Channel))
        if not isinstance(source, Channel):
            caller.respond("This command may only be used in a channel.")
            return

        self.addChannel(source.name)

        chambers_left = self.channels[source.name]["chambers"]

        random.seed()

        if random.randint(1, chambers_left) == 1:
            # Boom!
            source.respond("BANG")
            protocol.send_action(source, "*reloads the gun*")
            chambers_left = 6
            source.respond(
                'There are %s new chambers. You have a %s%% chance of dying.'
                % (chambers_left, int(100.0 / chambers_left)))

        else:
            # Click..
            chambers_left -= 1
            source.respond(
                '*click* You\'re safe for now. There are %s chambers left. '
                'You have a %s%% chance of dying.'
                % (chambers_left, int(100.0 / chambers_left)))
        self.channels[source.name]["chambers"] = chambers_left
Пример #3
0
class ItemsPlugin(plugin.PluginObject):

    commands = None

    config = None
    data = None
    storage = None

    handler = None

    @property
    def storage_type(self):
        if self.config["storage"].lower() == "json":
            return "json"
        return "sqlite"

    def setup(self):
        self.commands = CommandManager()
        self.storage = StorageManager()

        self.logger.trace("Entered setup method.")
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/items.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.warn("Defaulting to SQLite for storage.")
        else:
            if not self.config.exists:
                self.logger.warn("Unable to find config/plugins/items.yml")
                self.logger.warn("Defaulting to SQLite for storage.")
            else:
                self.logger.info("Using storage type: %s" % self.storage_type)

        self._load()
        self.config.add_callback(self._load)

        self.commands.register_command("give", self.give_command, self,
                                       "items.give", default=True)
        self.commands.register_command("get", self.get_command, self,
                                       "items.get", default=True)

    def _load(self):
        if self.storage_type == "json":
            self.handler = JSONType(self, self.storage, self.logger)
        else:
            self.handler = SQLiteType(self, self.storage, self.logger)

    @RateLimiter(5, 0, 10)
    def give_command(self, *args, **kwargs):
        return self.handler.give_command(*args, **kwargs)

    @RateLimiter(5, 0, 10)
    def get_command(self, *args, **kwargs):
        return self.handler.get_command(*args, **kwargs)
Пример #4
0
class MemosPlugin(plugin.PluginObject):

    commands = None
    events = None
    storage = None

    # data = None  # SQLite for a change

    def setup(self):
        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()
        # self.data = self.storage.get_file(self, "data", SQLITE,
        #                                   "plugins/memos/memos.sqlite")

        # with self.data as c:
        #     # Multiline strings because of an IDE bug
        #     c.execute("""CREATE TABLE IF NOT EXISTS memos
        #               (to TEXT, from TEXT, memo TEXT)""")

        self.events.add_callback("PreMessageReceived", self,
                                 self.message_received, 0)
        self.commands.register_command("memo", self.memo_command, self,
                                       "memo.send", default=True)

    def save_memo(self, sender, recipient, memo):
        recipient = recipient.lower()
        # with self.data as c:
        #     c.execute("""INSERT INTO memos VALUES (?, ?, ?)""",
        #               (recipient, sender, memo))

    def get_memos(self, recipient):
        recipient = recipient.lower()
        # with self.data as c:
        #     c.execute("""SELECT * FROM memos WHERE from=?""", (recipient,))
        #     d = c.fetchall()
        #     return d

    def message_received(self, event=PreMessageReceived):
        user = event.source
        target = event.target if isinstance(event.target, Channel) else user
        memos = self.get_memos(user.name)
        if memos:
            for memo in memos:
                sender = memo[1]
                text = memo[2]
                target.respond("Memo for %s (from %s): %s" % (user.name,
                                                              sender, text))

    def memo_command(self, protocol, caller, source, command, raw_args,
                     parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        raise NotImplementedError("This isn't done yet.")
Пример #5
0
class ExamplePlugin(plugin.PluginObject):  # Create the plugin class
    """
    Example plugin object
    """

    commands = None

    def setup(self):
        """
        Called when the plugin is loaded. Performs initial setup.
        """

        # Get an instance of the command manager
        self.commands = CommandManager()

        # Register the command in our system
        self.commands.register_command(
            "example",  # The name of the command
            self.example_command,  # The command's function
            self,  # This plugin
            # permission="example.example",  # Required permission for command
            default=True  # Whether this command should be available to all
        )

        self.commands.register_command(  # Another example command
            "example2",
            self.example_command,
            self,
            default=True)

    def example_command(self, protocol, caller, source, command, raw_args,
                        args):
        """
        Command handler for the example command
        """

        if args is None:
            # You'll probably always want this, so you always have
            # arguments if quote-parsing fails
            args = raw_args.split()

        # Send to the channel
        source.respond("Hello, world! You ran the %s command!" % command)

        # Send to the user that ran the command
        caller.respond("Raw arguments: %s" % raw_args)
        caller.respond("Parsed arguments: %s" % args)

        # Send directly through the protocol
        protocol.send_msg(source.name, "Message through the protocol!")
Пример #6
0
class ExamplePlugin(plugin.PluginObject):  # Create the plugin class
    """
    Example plugin object
    """

    commands = None

    def setup(self):
        """
        Called when the plugin is loaded. Performs initial setup.
        """

        # Get an instance of the command manager
        self.commands = CommandManager()

        # Register the command in our system
        self.commands.register_command(
            "example",  # The name of the command
            self.example_command,  # The command's function
            self,  # This plugin
            # permission="example.example",  # Required permission for command
            default=True  # Whether this command should be available to all
        )

        self.commands.register_command(  # Another example command
            "example2", self.example_command, self, default=True
        )

    def example_command(self, protocol, caller, source, command, raw_args,
                        args):
        """
        Command handler for the example command
        """

        if args is None:
            # You'll probably always want this, so you always have
            # arguments if quote-parsing fails
            args = raw_args.split()

        # Send to the channel
        source.respond("Hello, world! You ran the %s command!" % command)

        # Send to the user that ran the command
        caller.respond("Raw arguments: %s" % raw_args)
        caller.respond("Parsed arguments: %s" % args)

        # Send directly through the protocol
        protocol.send_msg(source.name, "Message through the protocol!")
Пример #7
0
class HeartbleedPlugin(plugin.PluginObject):
    commands = None

    def setup(self):
        self.commands = CommandManager()

        self.commands.register_command("hb",
                                       self.hb_command,
                                       self,
                                       "hb.hb",
                                       default=True)

    @run_async_threadpool
    def hb_command(self, protocol, caller, source, command, raw_args,
                   parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 1:
            caller.respond("Usage: {CHARS}hb <address> [port]")
            return
        else:
            host = args[0]
            port = 443

            if len(args) > 1:
                port = args[1]
                try:
                    port = int(port)
                except:
                    source.respond("Port '%s' is invalid, trying on port "
                                   "443." % port)
            try:
                source.respond("Checking %s:%s" % (host, port))
                result = hb.try_host(host, port)
                if result:
                    source.respond("Host %s is vulnerable!" % host)
                elif result is None:
                    source.respond("Host %s returned an error. It's probably "
                                   "not vulnerable." % host)
                else:
                    source.respond("Host %s is not vulnerable." % host)
            except:
                self.logger.exception("Error querying host %s" % host)
                source.respond("Unable to determine whether host %s is "
                               "vulnerable." % host)
Пример #8
0
class HeartbleedPlugin(plugin.PluginObject):
    commands = None

    def setup(self):
        self.commands = CommandManager()

        self.commands.register_command("hb",
                                       self.hb_command,
                                       self,
                                       "hb.hb", default=True)

    @run_async_threadpool
    def hb_command(self, protocol, caller, source, command, raw_args,
                   parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 1:
            caller.respond("Usage: {CHARS}hb <address> [port]")
            return
        else:
            host = args[0]
            port = 443

            if len(args) > 1:
                port = args[1]
                try:
                    port = int(port)
                except:
                    source.respond("Port '%s' is invalid, trying on port "
                                   "443." % port)
            try:
                source.respond("Checking %s:%s" % (host, port))
                result = hb.try_host(host, port)
                if result:
                    source.respond("Host %s is vulnerable!" % host)
                elif result is None:
                    source.respond("Host %s returned an error. It's probably "
                                   "not vulnerable." % host)
                else:
                    source.respond("Host %s is not vulnerable." % host)
            except:
                self.logger.exception("Error querying host %s" % host)
                source.respond("Unable to determine whether host %s is "
                               "vulnerable." % host)
Пример #9
0
class TranslatePlugin(PluginObject):
    commands = None
    goslate = None

    def setup(self):
        self.commands = CommandManager()
        self.goslate = Goslate()

        self.commands.register_command(
            "translate", self.translate_command, self, "translate.translate",
            ["tr", "t"], True
        )

    @run_async_threadpool
    def translate_command(self, protocol, caller, source, command, raw_args,
                          parsed_args):
        if len(parsed_args) < 2:
            caller.respond(
                "Usage: {CHARS}" + command + " <languages> <text>"
            )
            return

        langs = parsed_args[0]
        text = u" ".join([to_unicode(x) for x in parsed_args[1:]])

        if u":" in langs:
            split = langs.split(u":")
            from_lang, to_lang = split[0], split[1]
        else:
            from_lang, to_lang = u"", langs

        try:
            translation = self.goslate.translate(text, to_lang, from_lang)

            source.respond(u"[{}] {}".format(to_lang, translation))
        except Error as e:
            source.respond(u"Translation error: {}".format(e))
        except Exception as e:
            self.logger.exception("Translation error")
            source.respond(u"Translation error: {}".format(e))
Пример #10
0
class GeoIPPlugin(plugin.PluginObject):

    commands = None
    api_url = "http://freegeoip.net/json/%s"

    def setup(self):
        self.commands = CommandManager()
        self.commands.register_command("geoip",
                                       self.command,
                                       self,
                                       "geoip.command",
                                       default=True)

    def command(self, protocol, caller, source, command, raw_args,
                parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 1:
            caller.respond("Usage: {CHARS}geoip <address>")
        else:
            addr = urllib.quote_plus(args[0])
            resp = urllib.urlopen(self.api_url % addr)
            data = resp.read()

            self.logger.trace("Data: %s" % repr(data))

            if data == "Not Found\n":
                source.respond("%s | Not found" % args[0])
            else:
                parsed = json.loads(data)
                country = parsed["country_name"]
                region = parsed["region_name"]
                city = parsed["city"]

                if not country and not city and not region:
                    source.respond("%s | Not found" % args[0])

                source.respond("%s | %s, %s, %s" %
                               (args[0], city or "???", region
                                or "???", country or "???"))
Пример #11
0
class GeoIPPlugin(plugin.PluginObject):

    commands = None
    api_url = "http://freegeoip.net/json/%s"

    def setup(self):
        self.commands = CommandManager()
        self.commands.register_command("geoip", self.command, self,
                                       "geoip.command", default=True)

    def command(self, protocol, caller, source, command, raw_args,
                parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 1:
            caller.respond("Usage: {CHARS}geoip <address>")
        else:
            addr = urllib.quote_plus(args[0])
            resp = urllib.urlopen(self.api_url % addr)
            data = resp.read()

            self.logger.trace("Data: %s" % repr(data))

            if data == "Not Found\n":
                source.respond("%s | Not found" % args[0])
            else:
                parsed = json.loads(data)
                country = parsed["country_name"]
                region = parsed["region_name"]
                city = parsed["city"]

                if not country and not city and not region:
                    source.respond("%s | Not found" % args[0])

                source.respond("%s | %s, %s, %s" % (args[0],
                                                    city or "???",
                                                    region or "???",
                                                    country or "???"))
Пример #12
0
class TranslatePlugin(PluginObject):
    commands = None
    goslate = None

    def setup(self):
        self.commands = CommandManager()
        self.goslate = Goslate()

        self.commands.register_command("translate", self.translate_command,
                                       self, "translate.translate",
                                       ["tr", "t"], True)

    @run_async_threadpool
    def translate_command(self, protocol, caller, source, command, raw_args,
                          parsed_args):
        if len(parsed_args) < 2:
            caller.respond("Usage: {CHARS}" + command + " <languages> <text>")
            return

        langs = parsed_args[0]
        text = u" ".join([to_unicode(x) for x in parsed_args[1:]])

        if u":" in langs:
            split = langs.split(u":")
            from_lang, to_lang = split[0], split[1]
        else:
            from_lang, to_lang = u"", langs

        try:
            translation = self.goslate.translate(text, to_lang, from_lang)

            source.respond(u"[{}] {}".format(to_lang, translation))
        except Error as e:
            source.respond(u"Translation error: {}".format(e))
        except Exception as e:
            self.logger.exception("Translation error")
            source.respond(u"Translation error: {}".format(e))
Пример #13
0
class PagesPlugin(plugin.PluginObject):
    """
    Pages plugin object
    """

    lines_per_page = 5
    stored_pages = {}

    commands = None

    def setup(self):
        """
        Called when the plugin is loaded. Performs initial setup.
        """

        self.commands = CommandManager()
        self.commands.register_command("page", self.page_command, self,
                                       default=True)

    def send_page(self, pageset, pagenum, target):
        """
        Send a page from a pageset to a specified target.

        :param pageset: The pageset to send
        :param pagenum: The page number
        :param target: The target to send to

        :type pageset: str
        :type pagenum: int
        :type target: User, Channel
        """

        if pageset not in self.stored_pages:
            target.respond(__("No pages found."))
            return

        if pagenum < 1:
            target.respond(__("Page %s not found - the page number must be "
                              "positive.") % pagenum)
            return

        index = pagenum - 1
        numpages = len(self.stored_pages[pageset]) - 1
        if index > numpages:
            target.respond(__("Page %s not found - there are %s pages.") %
                           (pagenum, numpages + 1))
            return

        page = self.stored_pages[pageset][index]

        target.respond(__("== Page %s/%s ==") % (pagenum, numpages + 1))
        for line in page:
            target.respond(line)

        target.respond(__("== Use {CHARS}page <page number> to see more. =="))

    def page(self, pageset, lines):
        """
        Create and store a pageset from a list of lines.

        :param pageset: The pageset to create and store
        :param lines: A list of lines to paginate

        :type pageset: str
        :type lines: list
        """

        pages = []
        current = []

        for line in lines:
            current.append(line)
            if len(current) == self.lines_per_page:
                pages.append(current)
                current = []

        if current:
            pages.append(current)

        self.stored_pages[pageset] = pages

    def get_pageset(self, protocol, target):
        """
        Get the name of a pageset for a given protocol and target.

        :param protocol: The protocol to use
        :param target: The target to use

        :type protocol: Protocol
        :type target: User, Channel

        :return: The name of the pageset
        :rtype: str
        """
        return "%s:%s" % (protocol.name, target.name)

    def page_command(self, protocol, caller, source, command, raw_args,
                     args):
        """
        Command handler for the page command
        """

        if args is None:
            args = raw_args.split()

        if len(args) < 1:
            caller.respond(__("Usage: {CHARS}%s <page number>" % command))
            return

        pagenum = args[0]
        try:
            pagenum = int(pagenum)
        except Exception:
            source.respond("'%s' is not a number." % pagenum)
            return

        page = self.get_pageset(protocol, source)

        self.send_page(page, pagenum, source)
Пример #14
0
class AuthPlugin(plugin.PluginObject):
    """
    Auth plugin. In charge of logins and permissions.
    """

    config = None
    passwords = None
    permissions = None
    blacklist = None

    commands = None
    events = None
    storage = None

    auth_h = None
    perms_h = None

    def setup(self):
        """
        Called when the plugin is loaded. Performs initial setup.
        """

        reload(auth_handler)
        reload(permissions_handler)

        self.logger.trace(_("Entered setup method."))

        self.storage = StorageManager()
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/auth.yml")
        except Exception:
            self.logger.exception(_("Error loading configuration!"))
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return
        if not self.config.exists:
            self.logger.error(_("Unable to find config/plugins/auth.yml"))
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return

        self.commands = CommandManager()
        self.events = EventManager()

        if self.config["use-permissions"]:
            try:
                self.permissions = self.storage.get_file(
                    self,
                    "data",
                    YAML,
                    "plugins/auth/"  # PEP
                    "permissions.yml")
            except Exception:
                self.logger.exception(
                    _("Unable to load permissions. They "
                      "will be unavailable!"))
            else:
                self.perms_h = permissions_handler.permissionsHandler(
                    self, self.permissions)
                result = self.commands.set_permissions_handler(self.perms_h)
                if not result:
                    self.logger.warn(_("Unable to set permissions handler!"))

        if self.config["use-auth"]:
            try:
                self.passwords = self.storage.get_file(
                    self,
                    "data",
                    YAML,
                    "plugins/auth/"  # PEP!
                    "passwords.yml")
                self.blacklist = self.storage.get_file(
                    self,
                    "data",
                    YAML,
                    "plugins/auth/"  # PEP!
                    "blacklist.yml")
            except Exception:
                self.logger.exception(
                    _("Unable to load user accounts. They "
                      "will be unavailable!"))
            else:
                self.auth_h = auth_handler.authHandler(self, self.passwords,
                                                       self.blacklist)
                result = self.commands.set_auth_handler(self.auth_h)
                if not result:
                    self.logger.warn(_("Unable to set auth handler!"))

        self.logger.debug(_("Registering commands."))
        self.commands.register_command("login",
                                       self.login_command,
                                       self,
                                       "auth.login",
                                       default=True)
        self.commands.register_command("logout",
                                       self.logout_command,
                                       self,
                                       "auth.login",
                                       default=True)
        self.commands.register_command("register",
                                       self.register_command,
                                       self,
                                       "auth.register",
                                       default=True)
        self.commands.register_command("passwd",
                                       self.passwd_command,
                                       self,
                                       "auth.passwd",
                                       default=True)

        self.events.add_callback("PreCommand", self, self.pre_command, 10000)

    def pre_command(self, event=PreCommand):
        """
        Pre-command hook to remove passwords from the log output.
        """

        self.logger.trace(_("Command: %s") % event.command)
        if event.command.lower() in ["login", "register"]:
            if len(event.args) >= 2:
                split_ = event.printable.split("%s " % event.command)
                second_split = split_[1].split()
                second_split[1] = _("[REDACTED]")
                split_[1] = " ".join(second_split)
                donestr = "%s " % event.command
                done = donestr.join(split_)
                event.printable = done
        elif event.command.lower() == "passwd":
            split_ = event.printable.split("%s " % event.command)
            second_split = split_[1].split()

            dsplit = []
            for x in second_split:
                dsplit.append(_("[REDACTED]"))

            split_[1] = " ".join(dsplit)
            donestr = "%s " % event.command
            done = donestr.join(split_)
            event.printable = done

    def login_command(self, protocol, caller, source, command, raw_args,
                      parsed_args):
        """
        Command handler for the login command - for logging users in.
        """

        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 2:
            caller.respond(__("Usage: {CHARS}login <username> <password>"))
        else:
            if self.auth_h.authorized(caller, source, protocol):
                caller.respond(
                    __("You're already logged in. "
                       "Try logging out first!"))
                return
            username = args[0]
            password = args[1]

            result = self.auth_h.login(caller, protocol, username, password)
            if not result:
                self.logger.warn(
                    _("%s failed to login as %s") %
                    (caller.nickname, username))
                caller.respond(__("Invalid username or password!"))
            else:
                self.logger.info(
                    _("%s logged in as %s") % (caller.nickname, username))
                caller.respond(__("You are now logged in as %s.") % username)

    def logout_command(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        """
        Command handler for the logout command - for logging users out.
        """

        if self.auth_h.authorized(caller, source, protocol):
            self.auth_h.logout(caller, protocol)
            caller.respond(__("You have been logged out successfully."))
        else:
            caller.respond(__("You're not logged in."))

    def register_command(self, protocol, caller, source, command, raw_args,
                         parsed_args):
        """
        Command handler for the register command - for creating new user
        accounts.
        """

        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 2:
            caller.respond(__("Usage: {CHARS}register <username> <password>"))
            return
        username = args[0]
        password = args[1]

        if isinstance(source, Channel):
            source.respond(__("You can't create an account in a channel."))
            caller.respond(__("Don't use this command in a channel!"))
            caller.respond(__("You should only use it in a private message."))
            caller.respond(
                __("For your security, the password you used has "
                   "been blacklisted."))
            self.auth_h.blacklist_password(password, username)
            return

        if self.auth_h.user_exists(username):
            caller.respond(__("That username already exists!"))
            return

        if self.auth_h.password_backlisted(password, username):
            caller.respond(
                __("That password has been blacklisted. "
                   "Try another!"))
            return

        if self.auth_h.create_user(username, password):
            caller.respond(
                __("Your account has been created and you will now "
                   "be logged in. Thanks for registering!"))
            self.perms_h.create_user(username)
            self.login_command(caller, source, [username, password], protocol,
                               raw_args, parsed_args)
        else:
            caller.respond(
                __("Something went wrong when creating your "
                   "account! You should ask the bot operators "
                   "about this."))

    def passwd_command(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        """
        Command handler for the passwd command - for changing passwords.
        """

        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 2:
            caller.respond(
                __("Usage: {CHARS}passwd <old password> "
                   "<new password>"))
            return
        if not self.auth_h.authorized(caller, source, protocol):
            caller.respond(
                __("You must be logged in to change your "
                   "password."))
            return

        username = caller.auth_name

        old = args[0]
        new = args[1]

        if self.auth_h.password_backlisted(new, username):
            caller.respond(
                __("That password has been blacklisted. Try "
                   "another!"))
            return

        if self.auth_h.change_password(username, old, new):
            caller.respond(__("Your password has been changed successfully."))
        else:
            caller.respond(__("Old password incorrect - please try again!"))
            self.logger.warn(
                _("User %s failed to change the password for %s") %
                (caller, username))

    def get_auth_handler(self):
        """
        API function for getting the auth handler.

        This will return None if no handler is registered.
        """

        if self.config["use-auth"]:
            return self.auth_h
        return None

    def get_permissions_handler(self):
        """
        API function for getting the permissions handler.

        This will return None if no handler is registered.
        """
        if self.config["use-permissions"]:
            return self.perms_h
        return None

    def deactivate(self):
        """
        Called when the plugin is deactivated. Does nothing right now.
        """
        if self.config["use-auth"]:
            if isinstance(self.commands.auth_handler,
                          auth_handler.authHandler):
                self.commands.auth_handler = None

        if self.config["use-permissions"]:
            if isinstance(self.commands.perm_handler,
                          permissions_handler.permissionsHandler):
                self.commands.perm_handler = None
Пример #15
0
class DictPlugin(plugin.PluginObject):

    api_key = ""
    api_client = None
    word_api = None
    words_api = None

    commands = None
    config = None
    plugman = None
    storage = None

    @property
    def urls(self):
        return self.plugman.get_plugin("URLs")

    def setup(self):
        self.storage = StorageManager()
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/wordnik.yml")
        except Exception:
            self.logger.exception("Unable to load the configuration!")
            return self._disable_self()
        if not self.config.exists:
            self.logger.error("Unable to find the configuration at "
                              "config/plugins/wordnik.yml - Did you fill "
                              "it out?")
            return self._disable_self()
        if "apikey" not in self.config or not self.config["apikey"]:
            self.logger.error("Unable to find an API key; did you fill out the"
                              " config?")
            return self._disable_self()

        self._load()
        self.config.add_callback(self._load)

        self.plugman = PluginManager()

        self.commands = CommandManager()

        self.commands.register_command("dict",
                                       self.dict_command,
                                       self,
                                       "wordnik.dict",
                                       default=True)
        self.commands.register_command("wotd",
                                       self.wotd_command,
                                       self,
                                       "wordnik.wotd",
                                       default=True)

    def _load(self):
        self.api_key = self.config["apikey"]
        self.api_client = swagger.ApiClient(self.api_key,
                                            "http://api.wordnik.com/v4")
        self.word_api = WordApi(self.api_client)
        self.words_api = WordsApi(self.api_client)

    def dict_command(self, protocol, caller, source, command, raw_args,
                     parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 1:
            caller.respond("Usage: {CHAR}dict <word to look up>")
        else:
            try:
                definition = self.get_definition(args[0])
                if not definition:
                    if isinstance(source, User):
                        caller.respond("%s | No definition found." % args[0])
                    else:
                        source.respond("%s | No definition found." % args[0])
                    return
                word = definition.word
                text = definition.text

                wiktionary_url = "http://en.wiktionary.org/wiki/%s" \
                                 % urllib.quote_plus(word)

                short_url = self.urls.tinyurl(wiktionary_url)
            except Exception as e:
                self.logger.exception("Error looking up word: %s" % args[0])
                caller.respond("Error getting definition: %s" % e)
            else:

                # Necessary attribution as per the Wordnik TOS
                if isinstance(source, User):
                    caller.respond("%s | %s (%s) - Provided by Wiktionary via "
                                   "the Wordnik API" % (word, text, short_url))
                else:
                    source.respond("%s | %s (%s) - Provided by Wiktionary via "
                                   "the Wordnik API" % (word, text, short_url))

    def wotd_command(self, protocol, caller, source, command, raw_args,
                     parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        try:
            wotd = self.get_wotd()
            definition = self.get_definition(wotd)
            if not definition:
                if isinstance(source, User):
                    caller.respond("%s | No definition found." % wotd)
                else:
                    source.respond("%s | No definition found." % wotd)
                return
            word = definition.word
            text = definition.text

            wiktionary_url = "http://en.wiktionary.org/wiki/%s" \
                             % urllib.quote_plus(word)

            short_url = self.urls.tinyurl(wiktionary_url)
        except Exception as e:
            self.logger.exception("Error looking up word of the day.")
            caller.respond("Error getting definition: %s" % e)
        else:

            # Necessary attribution as per the Wordnik TOS
            if isinstance(source, User):
                caller.respond("%s | %s (%s) - Provided by Wiktionary via "
                               "the Wordnik API" % (word, text, short_url))
            else:
                source.respond("%s | %s (%s) - Provided by Wiktionary via "
                               "the Wordnik API" % (word, text, short_url))

    def get_definition(self, word):
        result = self.word_api.getDefinitions(word,
                                              limit=1,
                                              sourceDictionaries="wiktionary")
        self.logger.trace("Data: %s" % result)
        if result:
            return result[0]
        return None

    def get_wotd(self):
        result = self.words_api.getWordOfTheDay()
        self.logger.trace("Data: %s" % result)
        return result.word
Пример #16
0
class LastseenPlugin(plugin.PluginObject):

    commands = None
    events = None
    storage = None

    data = None  # SQLite for a change

    def setup(self):
        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()
        self.data = self.storage.get_file(
            self,
            "data",
            DBAPI,
            "sqlite3:data/plugins/lastseen/users.sqlite",
            "data/plugins/lastseen/users.sqlite",
            check_same_thread=False)

        self.data.runQuery("CREATE TABLE IF NOT EXISTS users ("
                           "user TEXT, "
                           "protocol TEXT, "
                           "at INTEGER)")

        self.commands.register_command("seen",
                                       self.seen_command,
                                       self,
                                       "seen.seen",
                                       default=True)

        # General events

        self.events.add_callback("PreMessageReceived",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_source_caller])
        self.events.add_callback("PreCommand",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_source_caller])
        self.events.add_callback("NameChanged",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("UserDisconnected",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_user_caller])

        # Mumble events

        self.events.add_callback("Mumble/UserRemove",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserJoined",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserMoved",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserSelfMuteToggle",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserSelfDeafToggle",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserRecordingToggle",
                                 self,
                                 self.event_handler,
                                 0,
                                 cancelled=True,
                                 extra_args=[self.event_user_caller])

    def _get_user_txn(self, txn, user, protocol):
        user = user.lower()
        user = to_unicode(user)
        txn.execute(u"SELECT * FROM users WHERE user=? AND protocol=?",
                    (user, protocol))
        r = txn.fetchone()
        return r

    def _get_user_callback(self, result, user, protocol, source):
        if result is None:
            source.respond("User '%s' not found." % user)
        else:
            then = math.floor(result[2])
            now = math.floor(time.time())
            seconds = now - then

            m, s = divmod(seconds, 60)
            h, m = divmod(m, 60)
            d, h = divmod(h, 24)

            s = int(s)
            m = int(m)
            h = int(h)
            d = int(d)

            if (s + m + h + d) == 0:
                source.respond("'%s' was seen just now!" % user)
            else:
                constructed = "'%s' was seen" % user
                to_append = []
                if d > 0:
                    to_append.append("%s days" % d)
                if h > 0:
                    to_append.append("%s hours" % h)
                if m > 0:
                    to_append.append("%s minutes" % m)
                if s > 0:
                    to_append.append("%s seconds" % s)

                length = len(to_append)
                i = 1

                for x in to_append:
                    if length - i == 0:
                        if i != 1:
                            constructed += " and %s" % x
                            i += 1
                            continue
                    if i != 1:
                        constructed += ", %s" % x
                    else:
                        constructed += " %s" % x
                    i += 1

                constructed += " ago."

                source.respond(constructed)

    def _get_user_callback_fail(self, failure, user, protocol, source):
        source.respond("Error while finding user %s: %s" % (user, failure))

    def get_user(self, user, protocol):
        return self.data.runInteraction(self._get_user_txn, user, protocol)

    def _insert_or_update_user(self, txn, user, protocol):
        user = user.lower()
        user = to_unicode(user)

        txn.execute("SELECT * FROM users WHERE user=? AND protocol=?",
                    (user, protocol))
        r = txn.fetchone()

        now = time.time()

        if r is None:
            txn.execute("INSERT INTO users VALUES (?, ?, ?)",
                        (user, protocol, now))
            return False
        else:
            txn.execute("UPDATE users SET at=? WHERE user=? AND protocol=?",
                        (now, user, protocol))
            return True

    def insert_or_update_user(self, user, protocol):
        self.data.runInteraction(self._insert_or_update_user, user, protocol)

    def seen_command(self, protocol, caller, source, command, raw_args,
                     parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if not args:
            caller.respond("Usage: {CHARS}seen <username>")
        else:
            user = "******".join(args)

            if user.lower() == protocol.ourselves.nickname.lower():
                source.respond("I'm right here, smartass.")
                return

            if user.lower() == caller.nickname.lower():
                source.respond("Having a bit of an out-of-body experience, "
                               "%s?" % caller.nickname)
                return

            d = self.get_user(user, protocol.name)

            d.addCallbacks(self._get_user_callback,
                           self._get_user_callback_fail,
                           callbackArgs=(user.lower(), protocol.name, source),
                           errbackArgs=(user.lower(), protocol.name, source))

    def event_handler(self, event, handler):
        """
        This is a generic function so that other plugins can catch events
        and cause a user's last seen value to update.

        The handler should return (username, protocol name) as a tuple,
        or a list of tuples if it needs to do more than one update.
        """
        data = handler(event)
        if not isinstance(data, list):
            data = [data]

        for element in data:
            user, proto = element
            self.insert_or_update_user(user, proto)

    def event_source_caller(self, event):
        user = event.source.nickname
        proto = event.caller.name

        return user, proto

    def event_user_caller(self, event):
        user = event.user.nickname
        proto = event.caller.name

        return user, proto
Пример #17
0
class LastFMPlugin(plugin.PluginObject):

    commands = None
    _config = None
    storage = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self._config = self.storage.get_file(self, "config", YAML, "plugins/lastfm.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/lastfm.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return
            ### Same for the data file (nickname=>lastfmusername map)
        try:
            self._nickmap = self.storage.get_file(self, "data", YAML, "plugins/lastfm-nickmap.yml")
        except Exception:
            self.logger.exception("Error loading nickmap!")
            self.logger.error("Disabling...")
            self._disable_self()

        ### Load options from config and nick map from data
        self._load()

        self._config.add_callback(self._load)

        ### Register commands
        self.commands.register_command(
            "nowplaying", self.nowplaying_cmd, self, "lastfm.nowplaying", aliases=["np"], default=True
        )
        self.commands.register_command("lastfmnick", self.lastfmnick_cmd, self, "lastfm.lastfmnick", default=True)
        self.commands.register_command(
            "lastfmcompare", self.compare_cmd, self, "lastfm.compare", aliases=["musiccompare", "compare"], default=True
        )

    def reload(self):
        try:
            self._config.reload()
            self._nickmap.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        self._load()
        return True

    def _load(self):
        self.api = LastFM(self._apikey)

    @property
    def _apikey(self):
        return self._config["apikey"]

    @property
    def _recent_play_limit(self):
        # Allow for old configs without this setting
        if "recent_play_limit" in self._config:
            return self._config["recent_play_limit"]
        else:
            return 300  # 5 minutes in seconds

    def _get_username(self, user, none_if_unset=False):
        user = user.lower()
        try:
            return self._nickmap[user]
        except KeyError:
            if none_if_unset:
                return None
            else:
                return user

    def _set_username(self, user, lastfm_user):
        with self._nickmap:
            self._nickmap[user.lower()] = lastfm_user

    def _respond(self, target, msg):
        """
        Convenience function for responding to something with a prefix. Not
        only does this avoid confusion, but it also stops people being able to
        execute other bot commands in the case that we need to put any
        user-supplied data at the start of a message.
        """
        target.respond("LastFM: " + msg)

    def lastfmnick_cmd(self, protocol, caller, source, command, raw_args, parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) == 0:
            username = self._get_username(caller.nickname, True)
            if username is None:
                caller.respond("You have no stored username")
            else:
                caller.respond("Your stored username is %s" % username)
        elif len(args) == 1:
            self._set_username(caller.nickname, args[0])
            caller.respond("Your stored username has been updated")
        else:
            caller.respond("Usage: {CHARS}lastfmnick [lastfm username]")

    def nowplaying_cmd(self, protocol, caller, source, command, raw_args, parsed_args):
        self.logger.trace("Entering nowplaying_cmd()")
        args = raw_args.split()  # Quick fix for new command handler signature
        ### Get LastFM username to use
        username = None
        if len(args) == 0:
            username = self._get_username(caller.nickname)
        elif len(args) == 1:
            username = self._get_username(args[0])
        else:
            caller.respond("Usage: {CHARS}nowplaying [lastfm username]")
            return

        ### Query LastFM for user's most recent track
        deferred = self.api.user_get_recent_tracks(username, limit=1)
        deferred.addCallbacks(
            lambda r: self._nowplaying_cmd_recent_tracks_result(caller, source, username, r),
            lambda f: self._nowplaying_cmd_error(caller, f),
        )

    def compare_cmd(self, protocol, caller, source, command, raw_args, parsed_args):
        self.logger.trace("Entering compare_cmd()")
        args = raw_args.split()  # Quick fix for new command handler signature
        ### Get LastFM username to use
        username_one = None
        username_two = None
        if len(args) == 1:
            username_one = self._get_username(caller.nickname)
            username_two = self._get_username(args[0])
        elif len(args) == 2:
            username_one = self._get_username(args[0])
            username_two = self._get_username(args[1])
        else:
            caller.respond("Usage: {CHARS}%s <lastfm username> [lastfm username]" % command)
            return

        ### Query LastFM for user taste comparison
        deferred = self.api.tasteometer_compare("user", username_one, "user", username_two)
        deferred.addCallbacks(
            lambda r: self._compare_cmd_tasteometer_result(caller, source, username_one, username_two, r),
            lambda f: self._compare_cmd_tasteometer_error(caller, f),
        )

    def _nowplaying_cmd_recent_tracks_result(self, caller, source, username, result):
        """
        Receives the API response for User.getRecentTracks.
        """
        self.logger.trace("Entering _nowplaying_cmd_recent_tracks_result()")
        # Extract track info
        try:
            tracks = result["recenttracks"]["track"]
            if len(tracks) == 0:
                # User has never listened to anything - an extreme edge-case,
                # I know, but we should really handle it - (untested)
                self._respond(source, "%s hasn't listened to anything" % username)
                return
            if isinstance(tracks, list):
                track = tracks[0]
            else:
                track = tracks
            # Check if track is currently playing, or was played recently
            now_playing = "@attr" in track and "nowplaying" in track["@attr"] and bool(track["@attr"]["nowplaying"])
            just_played = (
                "date" in track
                and (datetime.utcnow() - datetime.utcfromtimestamp(float(track["date"]["uts"]))).seconds
                <= self._recent_play_limit
            )
            if now_playing or just_played:
                track_artist = track["artist"]["#text"]
                track_title = track["name"]
                album = ""
                if "album" in track:
                    album = track["album"]["#text"]
                mbid = None
                if "mbid" in track and track["mbid"]:
                    mbid = track["mbid"]
                ### Query LastFM for track info, then finally send info to chan
                deferred = self.api.track_get_info(track_title, track_artist, mbid, username)
                deferred.addCallbacks(
                    lambda r: self._nowplaying_cmd_end_result(
                        caller, source, username, now_playing, track_artist, track_title, album, r
                    ),
                    # TODO: If error here, just send the basic info?
                    lambda f: self._nowplaying_cmd_error(caller, f),
                )
            else:
                self._respond(source, "%s is not currently listening to anything" % username)
        except:
            self.logger.exception("Please tell the developer about this error")

    def _nowplaying_cmd_end_result(
        self, caller, source, username, now_playing, track_artist, track_title, album, result
    ):
        self.logger.trace("Entering _nowplaying_cmd_end_result()")
        try:
            ### Extract track info
            user_loved = False
            user_play_count = 0
            total_play_count = 0
            listener_count = 0
            duration = 0
            url = ""
            tags = []
            track = result["track"]
            # I don't know if any of these may not exist
            if "userloved" in track:
                user_loved = track["userloved"] == "1"
            if "userplaycount" in track:
                user_play_count = int(track["userplaycount"])
            if "playcount" in track:
                total_play_count = int(track["playcount"])
            if "listeners" in track:
                listener_count = int(track["listeners"])
            if "duration" in track:
                duration = int(track["duration"])
            if "url" in track:
                url = track["url"]
            if "id" in track:
                try:
                    fragment = nb60.numtosxg(int(track["id"]))
                    url = "http://last.fm/+t{}".format(fragment)
                except:
                    self.logger.exception("Error getting short URL; using long one.")

            if "toptags" in track and isinstance(track["toptags"], dict):
                # type check due to an irregularity in the LastFM API: http://
                # www.last.fm/group/Last.fm+Web+Services/forum/21604/_/2231458
                if isinstance(track["toptags"]["tag"], dict):
                    # If the list only contains one item, it gets turned into
                    # a dict, so reverse that shit
                    track["toptags"]["tag"] = [track["toptags"]["tag"]]
                for tag in track["toptags"]["tag"]:
                    # TODO: Make these clickable links for protocols that can?
                    if not isinstance(tag, dict):
                        self.logger.error("Tag isn't a dict!? - %s" % tag)
                        continue
                    tags.append(tag["name"])

            ### Finally, we send the message
            # TODO: This could do with a cleanup
            status_text = u"just listened to"
            if now_playing:
                status_text = u"is now playing"
            output = [u'%s %s: "%s" by %s' % (username, status_text, track_title, track_artist)]
            if album:
                output.append(u" [%s]" % album)
            output.append(u" - ")
            if user_loved:
                output.append(u"\u2665 ")  # Heart
            output.append(
                u"%s listens by %s, %s listens by %s listeners"
                % (
                    # Localisation support? What's that?
                    "{:,}".format(user_play_count),
                    username,
                    "{:,}".format(total_play_count),
                    "{:,}".format(listener_count),
                )
            )
            if len(tags) > 0:
                output.append(u" - Tags: %s" % u", ".join(tags))
            if url:
                output.append(u" - %s" % url)
            self._respond(source, u"".join(output))
        except:
            self.logger.exception("Please tell the developer about this error")

    def _compare_cmd_tasteometer_result(self, caller, source, username_one, username_two, response):
        """
        Receives the API response for User.getRecentTracks.
        """
        self.logger.trace("Entering _compare_cmd_tasteometer_result()")
        try:
            ### Extract info
            result = response["comparison"]["result"]
            score = float(result["score"])
            score_percent = score * 100
            # More weird shit caused by using the JSON API... <_<
            artist_count = -1
            if "@attr" in result["artists"]:
                artist_count = int(result["artists"]["@attr"]["matches"])
            else:
                artist_count = int(result["artists"]["matches"])
            artists = []
            if artist_count > 0:
                _json_artists = result["artists"]["artist"]
                if isinstance(_json_artists, dict):
                    _json_artists = [_json_artists]
                for artist in _json_artists:
                    artists.append(artist["name"])

            ### Send the message
            output = [u"%s and %s are %.0f%% compatible." % (username_one, username_two, score_percent)]

            if len(artists) > 0:
                output.append(u" Some artists they share: ")
                output.append(u", ".join(artists))

            self._respond(source, u"".join(output))
        except:
            # TODO: Remove this debug dump line
            print __import__("json").dumps(response)
            self.logger.exception("Please tell the developer about this error")

    def _nowplaying_cmd_error(self, caller, failure):
        """
        :type failure: twisted.python.failure.Failure
        """
        # Some errors will be caused by user input
        if failure.check(LastFMError) and failure.value.err_code in (6,):
            self._respond(caller, failure.value.message)
        else:
            self.logger.debug("Error while fetching nowplaying", exc_info=(failure.type, failure.value, failure.tb))
            caller.respond(
                "There was an error while contacting LastFM - " "please alert a bot admin or try again later"
            )

    def _compare_cmd_tasteometer_error(self, caller, failure):
        """
        :type failure: twisted.python.failure.Failure
        """
        # Some errors will be caused by user input
        if failure.check(LastFMError) and failure.value.err_code in (6, 7):
            self._respond(caller, failure.value.message)
        else:
            self.logger.debug("Error while fetching comparison", exc_info=(failure.type, failure.value, failure.tb))
            caller.respond(
                "There was an error while contacting LastFM - " "please alert a bot admin or try again later"
            )
Пример #18
0
class ControlPlugin(plugin.PluginObject):
    """
    Control plugin object
    """

    commands = None

    def setup(self):
        """
        The list of bridging rules
        """

        self.commands = CommandManager()

        self.commands.register_command("join", self.join_command, self,
                                       "control.join")
        self.commands.register_command("leave", self.leave_command, self,
                                       "control.leave")
        self.commands.register_command("say", self.say_command, self,
                                       "control.say")
        self.commands.register_command("action", self.action_command, self,
                                       "control.action")
        self.commands.register_command("raw", self.raw_command, self,
                                       "control.raw")
        self.commands.register_command("func", self.func_command, self,
                                       "control.func")

    def join_command(self, protocol, caller, source, command, raw_args, args):
        """
        Command handler for the join command
        """

        if not len(args) > 0:
            caller.respond(__("Usage: {CHARS}join <channel>"))
            return

        if hasattr(protocol, "join_channel"):
            result = protocol.join_channel(args[0])

            if result:
                caller.respond(__("Done!"))
            else:
                caller.respond(
                    __("Unable to join channel. Does this "
                       "protocol support joining channels?"))
        else:
            caller.respond(__("This protocol doesn't support channels."))

    def leave_command(self, protocol, caller, source, command, raw_args, args):
        """
        Command handler for the leave command
        """

        if not len(args) > 0:
            caller.respond(__("Usage: {CHARS}leave <channel>"))
            return

        if hasattr(protocol, "leave_channel"):
            result = protocol.leave_channel(args[0])

            if result:
                caller.respond(__("Done!"))
            else:
                caller.respond(
                    __("Unable to leave channel. Does this "
                       "protocol support leaving channels?"))
        else:
            caller.respond(__("This protocol doesn't support channels."))

    def raw_command(self, protocol, caller, source, command, raw_args, args):
        """
        Command handler for the raw command
        """

        if not len(args) > 0:
            caller.respond(__("Usage: {CHARS}raw <data>"))
            return

        if hasattr(protocol, "send_raw"):
            protocol.send_raw(raw_args)

            caller.respond(__("Done!"))
        else:
            caller.respond(
                __("This protocol doesn't support sending raw "
                   "data."))

    def say_command(self, protocol, caller, source, command, raw_args, args):
        """
        Command handler for the say command
        """

        if not len(args) > 1:
            caller.respond(__("Usage: {CHARS}say <target> <message>"))
            return

        channel = args[0]
        message = raw_args[len(channel):].strip()

        if hasattr(protocol, "send_msg"):
            protocol.send_msg(channel, message)

            caller.respond(__("Done!"))
        else:
            caller.respond(
                __("This protocol doesn't support sending "
                   "messages."))

    def action_command(self, protocol, caller, source, command, raw_args,
                       args):
        """
        Command handler for the action command
        """

        if not len(args) > 1:
            caller.respond(__("Usage: {CHARS}action <target> <message>"))
            return

        channel = args[0]
        message = raw_args[len(channel):].strip()

        if hasattr(protocol, "send_action"):
            protocol.send_action(channel, message)

            caller.respond(__("Done!"))
        else:
            caller.respond(
                __("This protocol doesn't support sending "
                   "actions."))

    def func_command(self, protocol, caller, source, command, raw_args, args):
        """
        Command handler for the func command
        """

        if not len(args) > 1:
            caller.respond(__("Usage: {CHARS}func <function> <data>"))
            return

        func = args[0]
        arguments = args[1:]

        _args = []
        _kwargs = {}

        for arg in arguments:
            if "=" in arg:
                pos = arg.find("=")
                if arg[pos - 1] != "\\":
                    split = arg.split("=", 1)
                    _kwargs[split[0]] = split[1]
            else:
                _args.append(arg)

        try:
            x = getattr(protocol, func, None)

            if not x:
                return caller.respond(__("No such function: %s") % func)

            r = x(*_args, **_kwargs)
        except Exception as e:
            self.logger.exception(_("Error running 'func' command!"))
            caller.respond(__("Error: %s") % e)
        else:
            caller.respond(__("Done! Call returned: %s") % r)
Пример #19
0
class DialectizerPlugin(plugin.PluginObject):
    """Dialectizer plugin object"""

    commands = None
    data = None
    events = None
    storage = None

    dialectizers = {
        "chef": Chef(),
        "fudd": Fudd(),
        "lower": Lower(),
        "off": Dialectizer(),
        "olde": Olde(),
        "reverse": Reverse(),
        "upper": Upper()
    }

    def setup(self):
        """The list of bridging rules"""

        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()

        self.data = self.storage.get_file(self, "data", YAML,
                                          "plugins/dialectizer/settings.yml")

        self.events.add_callback("MessageSent", self, self.handle_msg_sent, 1)
        self.commands.register_command("dialectizer",
                                       self.dialectizer_command,
                                       self,
                                       "dialectizer.set",
                                       aliases=["dialectiser"])

    def handle_msg_sent(self, event=MessageSent):
        """Handler for general message sent event"""

        if isinstance(event.target, User):
            return

        name = event.caller.name
        target = event.target.name

        with self.data:
            if name not in self.data:
                self.data[name] = {}

            if target not in self.data[name]:
                self.data[name][target] = "off"

        subber = self.dialectizers[self.data[name][target]]

        message = event.message
        message = subber.sub(message)
        event.message = message

    def dialectizer_command(self, protocol, caller, source, command, raw_args,
                            parsed_args):
        """Handler for the dialectizer command"""

        args = raw_args.split()  # Quick fix for new command handler signature
        if isinstance(source, User):
            caller.respond(__("This command only applies to channels."))
            return
        if not len(args) > 0:
            caller.respond(__("Usage: {CHARS}dialectizer <dialectizer>"))
            caller.respond(
                __("Available dialectizers: %s") %
                ", ".join(self.dialectizers.keys()))
            return

        with self.data:
            if protocol.name not in self.data:
                self.data[protocol.name] = {}

            if source.name not in self.data[protocol.name]:
                self.data[protocol.name][source.name] = "off"

            setting = args[0].lower()
            if setting not in self.dialectizers:
                caller.respond(__("Unknown dialectizer: %s") % setting)
                return

            self.data[protocol.name][source.name] = setting
            caller.respond(__("Dialectizer set to '%s'") % setting)
Пример #20
0
class TwilioPlugin(plugin.PluginObject):

    config = None
    data = None

    commands = None
    events = None
    plugins = None
    storage = None

    twilio = None

    @property
    def web(self):
        """
        :rtype: WebPlugin
        """
        return self.plugins.get_plugin("Web")

    def setup(self):
        self.logger.trace("Entered setup method.")

        self.commands = CommandManager()
        self.events = EventManager()
        self.plugins = PluginManager()
        self.storage = StorageManager()

        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/twilio.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            return self._disable_self()
        else:
            if not self.config.exists:
                self.logger.error("Unable to find config/plugins/twilio.yml")
                return self._disable_self()

        try:
            self.data = self.storage.get_file(self, "data", JSON,
                                              "plugins/twilio/contacts.json")
        except Exception:
            self.logger.exception("Error loading data!")
            self.logger.error("This data file is required. Shutting down...")
            return self._disable_self()

        self._load()
        self.config.add_callback(self._load)

        self.events.add_callback("Web/ServerStartedEvent", self,
                                 self.add_routes,
                                 0)

        self.commands.register_command("sms", self.sms_command, self,
                                       "twilio.sms")
        self.commands.register_command("mms", self.mms_command, self,
                                       "twilio.mms")
        self.commands.register_command("tw", self.tw_command, self,
                                       "twilio.tw")

    def _load(self):
        self.twilio = TwilioRestClient(
            self.config["identification"]["sid"],
            self.config["identification"]["token"]
        )

        account = self.twilio.accounts.get(
            self.config["identification"]["sid"]
        )

        self.logger.info("Logged in as [%s] %s." % (account.type,
                                                    account.friendly_name))

    def add_routes(self, event):
        self.web.add_handler(
            r"/twilio/%s" % self.config["security"]["api_key"],
            "plugins.twilio.route.Route"
        )
        self.logger.info("Registered route: /twilio/<apikey>")

    def tw_command(self, protocol, caller, source, command, raw_args,
                   parsed_args):

        args = raw_args.split(" ")
        if len(args) < 1:
            caller.respond("Usage: {CHARS}tw <contact/reload> [<set/del/get> "
                           "<[name] [number]>]")
            return

        action = args[0].lower()

        if action == "reload":
            self.config.reload()
            self.data.reload()

            source.respond("Files reloaded.")
            return

        if action != "contact":
            caller.respond("Unknown action: %s" % action)
            return

        if len(args) < 3:
            caller.respond("Usage: {CHARS}tw contact <set/del/get> "
                           "<[name] [number]>")
            return

        operation = args[1].lower()
        target = args[2]

        for case, default in switch(operation):  # I was bored, okay?
            if case("set"):
                if len(args) < 4:
                    caller.respond("Usage: {CHARS}tw contact set <name>"
                                   " <number>")
                    break
                try:
                    self.save_contact(name=target, number=args[3])
                except Exception as e:
                    source.respond("Error saving contact: %s" % e)
                else:
                    source.respond("Contact '%s' saved." % target)

                break
            if case("del"):
                try:
                    if target.startswith("+"):
                        c = self.load_contact(number=target)
                    else:
                        c = self.load_contact(name=target)

                    if not c:
                        source.respond("No contact found for '%s'" % target)
                        return

                    r = self.del_contact(contac_=c)

                    if not r:
                        source.respond("No contact found for '%s'" % target)
                    else:
                        source.respond("Contact for '%s' deleted." % target)
                except Exception as e:
                    source.respond("Error deleting contact: %s" % e)

                break
            if case("get"):
                try:
                    if target.startswith("+"):
                        c = self.load_contact(number=target)
                    else:
                        c = self.load_contact(name=target)

                    if not c:
                        source.respond("No contact found for '%s'" % target)
                    else:
                        source.respond("CONTACT | %s -> %s" % (c.name,
                                                               c.number))
                except Exception as e:
                    source.respond("Error loading contact: %s" % e)

                break
            if default:
                caller.respond("Unknown operation: %s" % operation)

    def sms_command(self, protocol, caller, source, command, raw_args,
                    parsed_args):
        args = raw_args.split(" ")
        if len(args) < 2:
            caller.respond("Usage: {CHARS}sms <name/number> <message>")
            return

        sent = self.config["formatting"].get("sms-sent",
                                             "SMS | {TO} | Message sent.")
        error = self.config["formatting"].get("sms-error",
                                              "SMS | ERROR | {ERROR}")

        target = args[0]
        message = "<%s> %s" % (caller.nickname, " ".join(args[1:]))

        if target.startswith("+"):
            c = self.load_contact(number=target)
        else:
            c = self.load_contact(name=target)

        if c is None:
            name = target
            if not target.startswith("+"):
                source.respond(
                    error.replace(
                        "{ERROR}",
                        "Numbers must start with a '+'"
                    )
                )
                return

            sent = sent.replace("{TO}", name)

            try:
                self.send_sms(name, message)
            except TwilioRestException as e:
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e.msg)
                    )
                )
            except Exception as e:
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e)
                    )
                )
            else:
                source.respond(sent)
        else:
            name = c.name
            sent = sent.replace("{TO}", name)

            try:
                self.send_sms(c, message)
            except TwilioRestException as e:
                if "has not been enabled for MMS" in e.msg:
                    e.msg = "Twilio number has not been enabled for MMS."
                elif "Please use only valid http and https urls" in e.msg:
                    e.msg = "Media URL is invalid - please use a link to a " \
                            "media file."
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e.msg)
                    )
                )
            except Exception as e:
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e).replace(
                            "\r", ""
                        ).replace(
                            "\n", " "
                        ).replace(
                            "  ", " "
                        )
                    )
                )
                self.logger.exception("Error sending SMS")
            else:
                source.respond(sent)

    def mms_command(self, protocol, caller, source, command, raw_args,
                    parsed_args):
        args = raw_args.split(" ")
        if len(args) < 3:
            caller.respond("Usage: {CHARS}mms <name/number> <url> <message>")
            return

        sent = self.config["formatting"].get("mms-sent",
                                             "MMS | {TO} | Message sent.")
        error = self.config["formatting"].get("mms-error",
                                              "MMS | ERROR | {ERROR}")

        target = args[0]
        url = args[1]
        message = "<%s> %s" % (caller.nickname, " ".join(args[2:]))

        if target.startswith("+"):
            c = self.load_contact(number=target)
        else:
            c = self.load_contact(name=target)

        if c is None:
            name = target
            if not target.startswith("+"):
                source.respond(
                    error.replace(
                        "{ERROR}",
                        "Numbers must start with a '+'"
                    )
                )
                return

            sent = sent.replace("{TO}", name)

            try:
                self.send_sms(name, message, media_url=url)
            except TwilioRestException as e:
                if "has not been enabled for MMS" in e.msg:
                    e.msg = "Twilio number has not been enabled for MMS."
                elif "Please use only valid http and https urls" in e.msg:
                    e.msg = "Media URL is invalid - please use a link to a " \
                            "media file."
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e.msg)
                    )
                )
            except Exception as e:
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e).replace(
                            "\r", ""
                        ).replace(
                            "\n", " "
                        ).replace(
                            "  ", " "
                        )
                    )
                )
                self.logger.exception("Error sending MMS")
            else:
                source.respond(sent)
        else:
            name = c.name
            sent = sent.replace("{TO}", name)

            try:
                self.send_sms(c, message, media_url=url)
            except TwilioRestException as e:
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e.msg)
                    )
                )
            except Exception as e:
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e)
                    )
                )
            else:
                source.respond(sent)

    def do_targets(self, sender, message):
        sender = str(sender).strip()
        c = self.load_contact(number=sender)
        name = "default"

        if c is not None:
            name = c.name

        if name not in self.config["targetting"]:
            name = "default"

            if name not in self.config["targetting"]:
                self.logger.trace("No default target found.")
                return

        targets = self.config["targetting"][name]

        f_str = self.config["formatting"].get(
            "sms",
            "SMS | {FROM} | {MESSAGE}"
        )

        from_ = name

        if c is not None:
            from_ = c.name
        elif from_ == "default":
            from_ = sender

        message = str(message).replace("\r", "")
        message = message.replace("\n", " ")

        f_str = f_str.replace("{FROM}", from_)
        f_str = f_str.replace("{MESSAGE}", message)

        self.logger.info(f_str)

        for target in targets:
            try:
                p_str = target["protocol"]
                p = self.factory_manager.get_protocol(p_str)

                if p is None:
                    self.logger.warn("No such protocol: %s" % p_str)
                    continue

                p.send_msg(target["target"], f_str,
                           target_type=target["target-type"])
            except Exception:
                self.logger.exception("Error relaying SMS message")
                continue

    def send_sms(self, target, message, media_url=None):
        if isinstance(target, Contact):
            target = target.number

        msg = self.twilio.messages.create(
            body=message, to=target,
            from_=self.config["identification"]["number"],
            media_url=media_url)

        return msg

    def load_contact(self, contac_=None, name=None, number=None):
        if contac_ is not None:
            return contac_

        if name is not None:
            number = self.data.get(name, None)
            if number is not None:
                return Contact(number, name, self)

        if number is not None:
            for k in self.data.keys():
                if number == self.data[k]:
                    return Contact(number, k, self)

        return None

    def save_contact(self, contac_=None, name=None, number=None):
        if contac_ is not None:
            with self.data:
                self.data[contac_.name] = contac_.number
            return contac_
        elif name is not None and number is not None:
            contac_ = Contact(number, name, self)
            with self.data:
                self.data[contac_.name] = contac_.number
            return contac_

        raise ValueError("You need to give either a contact, or a name and "
                         "number to save.")

    def del_contact(self, contac_=None, name=None, number=None):
        if contac_ is not None:
            with self.data:
                if contac_.name in self.data:
                    del self.data[contac_.name]
                    return True
                return False

        elif name is not None:
            with self.data:
                if name in self.data:
                    del self.data[name]
                    return True
                return False

        elif number is not None:
            for k in self.data.keys():
                if number == self.data[k]:
                    del self.data[k]
                    return True
            return False

        raise ValueError("You need to give either a contact, name or "
                         "number to delete.")

    pass  # So the regions work in PyCharm
Пример #21
0
class JargonPlugin(plugin.PluginObject):

    commands = None
    storage = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self._config = self.storage.get_file(self,
                                                 "config",
                                                 YAML,
                                                 "plugins/jargon.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/jargon.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Register commands
        self.commands.register_command("jargon",
                                       self.jargon_cmd,
                                       self,
                                       "jargon.jargon", default=True)

    def reload(self):
        try:
            self._config.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        return True

    def jargon_cmd(self, protocol, caller, source, command, raw_args,
                   parsed_args):
        source.respond(self.generate_sentence())

    def get_word(self, word_type):
        word_type = word_type.lower()
        if word_type == "verb":
            return random.choice(self._config["verbs"])["plain"]
        elif word_type == "verbing":
            verb = random.choice(self._config["verbs"])
            if "ing" in verb:
                return verb["ing"]
            else:
                return verb["plain"] + "ing"
        elif word_type == "noun":
            return random.choice(self._config["nouns"])
        elif word_type == "adjective":
            return random.choice(self._config["adjectives"])
        elif word_type == "abbreviation":
            return random.choice(self._config["abbreviations"])

    def generate_sentence(self):
        sentenceFormat = random.choice(self._config["formats"])
        words = []
        for word in sentenceFormat["types"]:
            words.append(self.get_word(word))
        return sentenceFormat["format"] % tuple(words)
Пример #22
0
class URLsPlugin(plugin.PluginObject):
    """
    URLs plugin object
    """

    catcher = None
    channels = None
    commands = None
    config = None
    events = None
    shortened = None
    storage = None

    blacklist = []
    handlers = {}
    shorteners = {}
    spoofing = {}

    content_types = [
        "text/html", "text/webviewhtml", "message/rfc822",
        "text/x-server-parsed-html", "application/xhtml+xml"
    ]

    def setup(self):
        """
        Called when the plugin is loaded. Performs initial setup.
        """

        self.logger.trace(_("Entered setup method."))
        self.storage = StorageManager()
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/urls.yml")
        except Exception:
            self.logger.exception(_("Error loading configuration!"))
        else:
            if not self.config.exists:
                self.logger.warn(_("Unable to find config/plugins/urls.yml"))
            else:
                self.content_types = self.config["content_types"]
                self.spoofing = self.config["spoofing"]

        self.logger.debug(_("Spoofing: %s") % self.spoofing)

        self.channels = self.storage.get_file(self, "data", YAML,
                                              "plugins/urls/channels.yml")
        self.shortened = self.storage.get_file(
            self,
            "data",
            DBAPI,
            "sqlite3:data/plugins/urls/shortened.sqlite",
            "data/plugins/urls/shortened.sqlite",
            check_same_thread=False)

        self.commands = CommandManager()
        self.events = EventManager()

        self.reload()

        def message_event_filter(event=MessageReceived):
            target = event.target
            type_ = event.type

            return type_ == "message" \
                or isinstance(target, Channel) \
                or isinstance(target, User)

        self.add_shortener("tinyurl", self.tinyurl)

        self.events.add_callback("MessageReceived", self, self.message_handler,
                                 1, message_event_filter)
        self.commands.register_command("urls", self.urls_command, self,
                                       "urls.manage")
        self.commands.register_command("shorten",
                                       self.shorten_command,
                                       self,
                                       "urls.shorten",
                                       default=True)

    def reload(self):
        """
        Reload files and create tables as necessary
        """

        self.shortened.runQuery("CREATE TABLE IF NOT EXISTS urls ("
                                "url TEXT, "
                                "shortener TEXT, "
                                "result TEXT)")

        self.catcher = Catcher(self, self.config, self.storage, self.logger)

        self.blacklist = []
        blacklist = self.config.get("blacklist", [])

        for element in blacklist:
            try:
                self.blacklist.append(re.compile(element))
            except Exception:
                self.logger.exception("Unable to compile regex '%s'" % element)

    def check_blacklist(self, url):
        """
        Check whether a URL is in the user-defined blacklist

        :param url: The URL to check
        :type url: str

        :return: Whether the URL is in the blacklist
        :rtype: bool
        """

        for pattern in self.blacklist:
            try:
                self.logger.debug(
                    _("Checking pattern '%s' against URL '%s'") %
                    (pattern, url))
                if re.match(pattern, url):
                    return True
            except Exception as e:
                self.logger.debug(_("Error in pattern matching: %s") % e)
                return False
        return False

    @run_async_threadpool
    def message_handler(self, event=MessageReceived):
        """
        Event handler for general messages
        """

        protocol = event.caller
        source = event.source
        target = event.target
        message = event.message

        allowed = self.commands.perm_handler.check("urls.title", source,
                                                   target, protocol)

        if not allowed:
            return

        # PEP = wat sometimes
        if self.channels.get(protocol.name, {}).get(target.name,
                                                    {}).get("status",
                                                            "on") == "off":
            return

        # Strip formatting characters if possible
        message_stripped = message
        try:
            message_stripped = event.caller.utils.strip_formatting(message)
        except AttributeError:
            pass

        for word in message_stripped.split(" "):
            pos = word.lower().find("http://")
            if pos == -1:
                pos = word.lower().find("https://")
            if pos > -1:
                end = word.lower().find(" ", pos)
                if end > -1:
                    url = word[pos:end]
                else:
                    url = word[pos:]

                if url in ["http://", "https://"]:
                    self.logger.trace(
                        _("URL is not actually a URL, just %s" % url))
                    return

                if self.check_blacklist(url):
                    self.logger.debug(_("Not parsing, URL is blacklisted."))
                    return

                if isinstance(target, Channel):
                    try:
                        self.catcher.insert_url(url, source.nickname,
                                                target.name, protocol.name)
                    except Exception:
                        self.logger.exception(_("Error catching URL"))

                title, domain = self.parse_title(url)

                self.logger.trace(_("Title: %s") % title)

                if isinstance(target, Channel):
                    if protocol.name not in self.channels:
                        with self.channels:
                            self.channels[protocol.name] = {
                                target.name: {
                                    "last": url,
                                    "status": "on",
                                    "shortener": "tinyurl"
                                }
                            }
                    if target.name not in self.channels[protocol.name]:
                        with self.channels:
                            self.channels[protocol.name][target.name] = {
                                "last": url,
                                "status": "on",
                                "shortener": "tinyurl"
                            }
                    else:
                        with self.channels:
                            self.channels[protocol.name][target.name]["last"] \
                                = url
                    if title is None:
                        return

                    if domain is not None and "/" in domain:
                        domain = domain.split("/")[0]
                    if domain is None:
                        target.respond(title)
                    else:
                        target.respond("\"%s\" at %s" % (title, domain))
                elif isinstance(target, User):
                    if title is None:
                        return

                    if domain is not None and "/" in domain:
                        domain = domain.split("/")[0]
                    if domain is None:
                        source.respond(title)
                    else:
                        source.respond("\"%s\" at %s" % (title, domain))
                else:
                    self.logger.warn(
                        _("Unknown target type: %s [%s]") %
                        (target, target.__class__))

    def urls_command(self, protocol, caller, source, command, raw_args,
                     parsed_args):
        """
        Command handler for the urls command
        """

        args = raw_args.split()  # Quick fix for new command handler signature
        if not isinstance(source, Channel):
            caller.respond(__("This command can only be used in a channel."))
            return
        if len(args) < 2:
            caller.respond(__("Usage: {CHARS}urls <setting> <value>"))
            caller.respond(
                __("Operations: set <on/off> - Enable or disable "
                   "title parsing for the current channel"))
            caller.respond("            %s" % __("shortener <name> - Set "
                                                 "which URL shortener to use "
                                                 "for the current channel"))
            caller.respond("            %s" % __("Shorteners: %s") %
                           ", ".join(self.shorteners.keys()))
            return

        operation = args[0].lower()
        value = args[1].lower()

        if protocol.name not in self.channels:
            with self.channels:
                self.channels[protocol.name] = {
                    source.name: {
                        "status": "on",
                        "last": "",
                        "shortener": "tinyurl"
                    }
                }
        if source.name not in self.channels[protocol.name]:
            with self.channels:
                self.channels[protocol.name][source.name] = {
                    "status": "on",
                    "last": "",
                    "shortener": "tinyurl"
                }

        if operation == "set":
            if value not in [__("on"), __("off")]:
                caller.respond(__("Usage: {CHARS}urls set <on|off>"))
            else:
                with self.channels:
                    if value == __("on"):
                        value = "on"
                    elif value == __("off"):
                        value = "off"
                    self.channels[protocol.name][source.name]["status"] = value
                caller.respond(
                    __("Title passing for %s turned %s.") %
                    (source.name, __(value)))
        elif operation == "shortener":
            if value.lower() in self.shorteners:
                with self.channels:
                    self.channels[protocol.name][source.name]["shortener"] \
                        = value.lower()
                caller.respond(
                    __("URL shortener for %s set to %s.") %
                    (source.name, value))
            else:
                caller.respond(__("Unknown shortener: %s") % value)
        else:
            caller.respond(__("Unknown operation: '%s'.") % operation)

    def _respond_shorten(self, result, source, handler):
        """
        Respond to a shorten command, after a successful Deferred
        """

        if result is not None:
            return source.respond(result)
        return source.respond(
            __("Unable to shorten using handler %s. Poke the"
               "bot owner!") % handler)

    def _respond_shorten_fail(self, failure, source, handler):
        """
        Respond to a shorten command, after a failed Deferred
        """

        return source.respond(
            __("Error shortening url with handler %s: %s") %
            (handler, failure))

    def shorten_command(self, protocol, caller, source, command, raw_args,
                        parsed_args):
        """
        Command handler for the shorten command
        """

        args = parsed_args  # Quick fix for new command handler signature
        if not isinstance(source, Channel):
            if len(args) == 0:
                caller.respond(__("Usage: {CHARS}shorten [url]"))
                return
            else:
                handler = "tinyurl"
                url = args[0]
                try:
                    d = self.shorten_url(url, handler)
                    d.addCallbacks(self._respond_shorten,
                                   self._respond_shorten_fail,
                                   callbackArgs=(source, handler),
                                   errbackArgs=(source, handler))
                except Exception as e:
                    self.logger.exception(_("Error fetching short URL."))
                    caller.respond(__("Error: %s") % e)
                    return
        else:
            if protocol.name not in self.channels \
               or source.name not in self.channels[protocol.name] \
               or not len(self.channels[protocol.name][source.name]["last"]):
                caller.respond(__("Nobody's pasted a URL here yet!"))
                return
            handler = self.channels[protocol.name][source.name]["shortener"]
            if len(handler) == 0:
                with self.channels:
                    self.channels[protocol.name][source.name]["shortener"]\
                        = "tinyurl"
                handler = "tinyurl"
            if handler not in self.shorteners:
                caller.respond(
                    __("Shortener '%s' not found - please set a "
                       "new one!") % handler)
                return

            url = self.channels[protocol.name][source.name]["last"]

            if len(args) > 0:
                url = args[0]

            try:
                d = self.shorten_url(url, handler)
                d.addCallbacks(self._respond_shorten,
                               self._respond_shorten_fail,
                               callbackArgs=(source, handler),
                               errbackArgs=(source, handler))
            except Exception as e:
                self.logger.exception(_("Error fetching short URL."))
                caller.respond(__("Error: %s") % e)
                return

    def tinyurl(self, url):
        """
        Shorten a URL with TinyURL. Don't use this directly.
        """

        return urllib2.urlopen("http://tinyurl.com/api-create.php?url=" +
                               urllib.quote_plus(url)).read()

    def parse_title(self, url, use_handler=True):
        """
        Get and return the page title for a URL, or the title from a
        specialized handler, if one is registered.

        This function returns a tuple which may be one of these forms..

        * (title, None) if the title was fetched by a specialized handler
        * (title, domain) if the title was parsed from the HTML
        * (None, None) if fetching the title was entirely unsuccessful.
            This occurs in each of the following cases..

            * When a portscan is detected and stopped
            * When the page simply has no title
            * When there is an exception in the chain somewhere

        :param url: The URL to check
        :param use_handler: Whether to use specialized handlers

        :type url: str
        :type use_handler: bool

        :returns: A tuple containing the result
        :rtype: tuple(None, None), tuple(str, str), tuple(str, None)
        """

        domain = ""
        self.logger.trace(_("Url: %s") % url)
        try:
            parsed = urlparse.urlparse(url)
            domain = parsed.hostname

            ip = socket.gethostbyname(domain)

            matches = all_matching_cidrs(ip, [
                "10.0.0.0/8", "0.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",
                "127.0.0.0/8"
            ])

            if matches:
                self.logger.warn(_("Prevented a portscan: %s") % url)
                return None, None

            if domain.startswith("www."):
                domain = domain[4:]

            if use_handler:
                for pattern in self.handlers:
                    if fnmatch.fnmatch(domain, pattern):
                        try:
                            result = self.handlers[domain](url)
                            if result:
                                return to_unicode(result), None
                        except Exception:
                            self.logger.exception(
                                _("Error running handler, "
                                  "parsing title normally."))

            self.logger.trace(_("Parsed domain: %s") % domain)

            request = urllib2.Request(url)
            if domain in self.spoofing:
                self.logger.debug(_("Custom spoofing for this domain found."))
                user_agent = self.spoofing[domain]
                if user_agent:
                    self.logger.debug(
                        _("Spoofing user-agent: %s") % user_agent)
                    request.add_header("User-agent", user_agent)
                else:
                    self.logger.debug(_("Not spoofing user-agent."))
            else:
                self.logger.debug(_("Spoofing Firefox as usual."))
                request.add_header(
                    'User-agent', 'Mozilla/5.0 (X11; U; Linux '
                    'i686; en-US; rv:1.9.0.1) '
                    'Gecko/2008071615 Fedora/3.0.'
                    '1-1.fc9-1.fc9 Firefox/3.0.1')

            # Deal with Accept-Language
            language_value = None
            language = self.config.get("accept_language", {})
            language_domains = language.get("domains", {})
            if domain in language_domains:
                language_value = language_domains[domain]
            elif domain.lower() in language_domains:
                language_value = language_domains[domain.lower()]
            elif "default" in language:
                language_value = language["default"]

            if language_value is not None:
                request.add_header("Accept-Language", language_value)

            response = urllib2.urlopen(request)

            self.logger.trace(_("Info: %s") % response.info())

            headers = response.info().headers
            new_url = response.geturl()

            _domain = domain

            parsed = urlparse.urlparse(new_url)
            domain = parsed.hostname

            if _domain != domain:
                self.logger.info(_("URL: %s") % new_url)
                self.logger.info(_("Domain: %s") % domain)

                if self.check_blacklist(new_url):
                    self.logger.debug(_("Not parsing, URL is blacklisted."))
                    return

                ip = socket.gethostbyname(domain)

                matches = all_matching_cidrs(ip, [
                    "10.0.0.0/8", "0.0.0.0/8", "172.16.0.0/12",
                    "192.168.0.0/16", "127.0.0.0/8"
                ])

                if matches:
                    self.logger.warn(_("Prevented a portscan: %s") % new_url)
                    return None, None

                if domain.startswith("www."):
                    domain = domain[4:]

                if domain in self.handlers and use_handler:
                    try:
                        result = self.handlers[domain](new_url)
                        if result:
                            return to_unicode(result), None
                    except Exception:
                        self.logger.exception(
                            _("Error running handler,"
                              " parsing title normally."))

            headers_dict = {}

            for x in headers:
                k, v = x.split(": ", 1)

                headers_dict[k.lower()] = v.strip("\r\n")

            status_code = response.getcode()

            if status_code in [301, 302, 303, 307, 308]:
                return self.parse_title(headers["location"])

            ct = headers_dict["content-type"]
            if ";" in ct:
                ct = ct.split(";")[0]

            self.logger.trace(_("Content-type: %s") % repr(ct))

            if ct not in self.content_types:
                self.logger.debug(_("Content-type is not allowed."))
                return None, None

            page = response.read()
            soup = BeautifulSoup(page)
            if soup.title and soup.title.string:
                title = soup.title.string.strip()
                title = re.sub("\s+", " ", title)
                title = to_unicode(title)
                domain = to_unicode(domain)
                return title, domain
            else:
                return None, None
        except Exception as e:
            if not str(e).lower() == "not viewing html":
                self.logger.exception(_("Error parsing title."))
                return str(e), domain
            return None, None

    def _shorten(self, txn, url, handler):
        """
        Shorten a URL, checking the database in case it's already been
        done. This is a database interaction and uses Deferreds.
        """

        txn.execute("SELECT * FROM urls WHERE url=? AND shortener=?",
                    (url, handler.lower()))
        r = txn.fetchone()

        self.logger.trace(_("Result (SQL): %s") % repr(r))

        if r is not None:
            return r[2]

        if handler in self.shorteners:
            result = self.shorteners[handler](url)

            txn.execute("INSERT INTO urls VALUES (?, ?, ?)",
                        (url, handler.lower(), result))
            return result
        return None

    def shorten_url(self, url, handler):
        """
        Shorten a URL using the specified handler. This returns a Deferred.

        :param url: The URL to shorten
        :param handler: The name of the handler to shorten with

        :type url: str
        :type handler: str

        :returns: Deferred which will fire with the result or None
        :rtype: Deferred
        """

        self.logger.trace(_("URL: %s") % url)
        self.logger.trace(_("Handler: %s") % handler)

        return self.shortened.runInteraction(self._shorten, url, handler)

    def add_handler(self, domain, handler):
        """
        API method to add a specialized URL handler.

        This will fail if there's already a handler there for that domain.

        :param domain: The domain to handle, without the 'www.'.
        :param handler: The callable handler

        :type domain: str
        :type handler: callable

        :returns: Whether the handler was registered
        :rtype: bool
        """

        if domain.startswith("www."):
            raise ValueError(_("Domain should not start with 'www.'"))
        if domain not in self.handlers:
            self.logger.trace(
                _("Handler registered for '%s': %s") % (domain, handler))
            self.handlers[domain] = handler
            return True
        return False

    def add_shortener(self, name, handler):
        """
        API method to add a URL shortener. This is the same as
        `add_handler`, but for URL shortening.
        """

        if name not in self.shorteners:
            self.logger.trace(
                _("Shortener '%s' registered: %s") % (name, handler))
            self.shorteners[name] = handler
            return True
        return False

    def remove_handler(self, domain):
        if domain.startswith("www."):
            raise ValueError(_("Domain should not start with 'www.'"))
        if domain in self.handlers:
            del self.handlers[domain]
            return True
        return False

    def remove_shortener(self, name):
        if name in self.shorteners:
            del self.shorteners[name]
            return True
        return False
Пример #23
0
class RoulettePlugin(plugin.PluginObject):

    commands = None

    channels = {}
    users = {}

    def setup(self):
        self.commands = CommandManager()
        self.commands.register_command("rroulette", self.play, self,
                                       "russianroulette.rroulette",
                                       aliases=["roulette"], default=True)

    def getChannel(self, channel):
        if channel not in self.channels.keys():
            self.channels[channel] = {"players": [], "shots": 0, "deaths": 0,
                                      "chambers": 6, "curplayers": []}
        return self.channels[channel]

    def setChambers(self, channel, chambers):
        self.channels[channel]["chambers"] = chambers

    def play(self, protocol, caller, source, command, raw_args,
             parsed_args):
        self.logger.trace("Caller: %s" % repr(caller))
        self.logger.trace("Source: %s" % repr(source))
        self.logger.trace("Result: %s" % isinstance(source, Channel))

        if not isinstance(source, Channel):
            caller.respond("This command may only be used in a channel.")
            return

        chan = self.getChannel("{}/{}".format(protocol.name, source.name))

        chambers_left = chan["chambers"]

        random.seed()

        if random.randint(1, chambers_left) == 1:
            # Boom!
            if isinstance(protocol, ChannelsProtocol):
                attempt = protocol.channel_kick(caller, source, "BANG")

                if not attempt:
                    source.respond("BANG")
            else:
                attempt = protocol.global_kick(caller, "BANG")

                if not attempt:
                    source.respond("BANG")

            protocol.send_action(source, "*reloads the gun*")
            chambers_left = 6
            source.respond(
                'There are %s new chambers. You have a %s%% chance of dying.'
                % (chambers_left, int(100.0 / chambers_left)))

        else:
            # Click..
            chambers_left -= 1
            source.respond(
                '*click* You\'re safe for now. There are %s chambers left. '
                'You have a %s%% chance of dying.'
                % (chambers_left, int(100.0 / chambers_left)))

        self.setChambers(
            "{}/{}".format(protocol.name, source.name), chambers_left
        )
Пример #24
0
class DictPlugin(plugin.PluginObject):

    api_key = ""
    api_client = None
    word_api = None
    words_api = None

    commands = None
    config = None
    plugman = None
    storage = None

    @property
    def urls(self):
        return self.plugman.get_plugin("URLs")

    def setup(self):
        self.storage = StorageManager()
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/wordnik.yml")
        except Exception:
            self.logger.exception("Unable to load the configuration!")
            return self._disable_self()
        if not self.config.exists:
            self.logger.error("Unable to find the configuration at "
                              "config/plugins/wordnik.yml - Did you fill "
                              "it out?")
            return self._disable_self()
        if "apikey" not in self.config or not self.config["apikey"]:
            self.logger.error("Unable to find an API key; did you fill out the"
                              " config?")
            return self._disable_self()

        self._load()
        self.config.add_callback(self._load)

        self.plugman = PluginManager()

        self.commands = CommandManager()

        self.commands.register_command("dict", self.dict_command,
                                       self, "wordnik.dict", default=True)
        self.commands.register_command("wotd", self.wotd_command,
                                       self, "wordnik.wotd", default=True)

    def _load(self):
        self.api_key = self.config["apikey"]
        self.api_client = swagger.ApiClient(self.api_key,
                                            "http://api.wordnik.com/v4")
        self.word_api = WordApi(self.api_client)
        self.words_api = WordsApi(self.api_client)

    def dict_command(self, protocol, caller, source, command, raw_args,
                     parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 1:
            caller.respond("Usage: {CHAR}dict <word to look up>")
        else:
            try:
                definition = self.get_definition(args[0])
                if not definition:
                    if isinstance(source, User):
                        caller.respond("%s | No definition found." % args[0])
                    else:
                        source.respond("%s | No definition found." % args[0])
                    return
                word = definition.word
                text = definition.text

                wiktionary_url = "http://en.wiktionary.org/wiki/%s" \
                                 % urllib.quote_plus(word)

                short_url = self.urls.tinyurl(wiktionary_url)
            except Exception as e:
                self.logger.exception("Error looking up word: %s" % args[0])
                caller.respond("Error getting definition: %s" % e)
            else:

                # Necessary attribution as per the Wordnik TOS
                if isinstance(source, User):
                    caller.respond("%s | %s (%s) - Provided by Wiktionary via "
                                   "the Wordnik API" % (word, text, short_url))
                else:
                    source.respond("%s | %s (%s) - Provided by Wiktionary via "
                                   "the Wordnik API" % (word, text, short_url))

    def wotd_command(self, protocol, caller, source, command, raw_args,
                     parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        try:
            wotd = self.get_wotd()
            definition = self.get_definition(wotd)
            if not definition:
                if isinstance(source, User):
                    caller.respond("%s | No definition found." % wotd)
                else:
                    source.respond("%s | No definition found." % wotd)
                return
            word = definition.word
            text = definition.text

            wiktionary_url = "http://en.wiktionary.org/wiki/%s" \
                             % urllib.quote_plus(word)

            short_url = self.urls.tinyurl(wiktionary_url)
        except Exception as e:
            self.logger.exception("Error looking up word of the day.")
            caller.respond("Error getting definition: %s" % e)
        else:

            # Necessary attribution as per the Wordnik TOS
            if isinstance(source, User):
                caller.respond("%s | %s (%s) - Provided by Wiktionary via "
                               "the Wordnik API" % (word, text, short_url))
            else:
                source.respond("%s | %s (%s) - Provided by Wiktionary via "
                               "the Wordnik API" % (word, text, short_url))

    def get_definition(self, word):
        result = self.word_api.getDefinitions(word, limit=1,
                                              sourceDictionaries="wiktionary")
        self.logger.trace("Data: %s" % result)
        if result:
            return result[0]
        return None

    def get_wotd(self):
        result = self.words_api.getWordOfTheDay()
        self.logger.trace("Data: %s" % result)
        return result.word
Пример #25
0
class DialectizerPlugin(plugin.PluginObject):
    """Dialectizer plugin object"""

    commands = None
    data = None
    events = None
    storage = None

    dialectizers = {"chef": Chef(),
                    "fudd": Fudd(),
                    "lower": Lower(),
                    "off": Dialectizer(),
                    "olde": Olde(),
                    "reverse": Reverse(),
                    "upper": Upper()}

    def setup(self):
        """The list of bridging rules"""

        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()

        self.data = self.storage.get_file(self, "data", YAML,
                                          "plugins/dialectizer/settings.yml")

        self.events.add_callback("MessageSent", self, self.handle_msg_sent,
                                 1)
        self.commands.register_command("dialectizer", self.dialectizer_command,
                                       self, "dialectizer.set",
                                       aliases=["dialectiser"])

    def handle_msg_sent(self, event=MessageSent):
        """Handler for general message sent event"""

        if isinstance(event.target, User):
            return

        name = event.caller.name
        target = event.target.name

        with self.data:
            if name not in self.data:
                self.data[name] = {}

            if target not in self.data[name]:
                self.data[name][target] = "off"

        subber = self.dialectizers[self.data[name][target]]

        message = event.message
        message = subber.sub(message)
        event.message = message

    def dialectizer_command(self, protocol, caller, source, command, raw_args,
                            parsed_args):
        """Handler for the dialectizer command"""

        args = raw_args.split()  # Quick fix for new command handler signature
        if isinstance(source, User):
            caller.respond(__("This command only applies to channels."))
            return
        if not len(args) > 0:
            caller.respond(__("Usage: {CHARS}dialectizer <dialectizer>"))
            caller.respond(__("Available dialectizers: %s")
                           % ", ".join(self.dialectizers.keys()))
            return

        with self.data:
            if protocol.name not in self.data:
                self.data[protocol.name] = {}

            if source.name not in self.data[protocol.name]:
                self.data[protocol.name][source.name] = "off"

            setting = args[0].lower()
            if setting not in self.dialectizers:
                caller.respond(__("Unknown dialectizer: %s") % setting)
                return

            self.data[protocol.name][source.name] = setting
            caller.respond(__("Dialectizer set to '%s'") % setting)
Пример #26
0
class xkcdPlugin(plugin.PluginObject):

    _commands = None
    _config = None
    _storage = None

    _comic_cache = None
    _archive = None

    def setup(self):
        ### Grab important shit
        self._commands = CommandManager()
        self._storage = StorageManager()

        ### Initial config load
        try:
            self._config = self._storage.get_file(self, "config", YAML,
                                                  "plugins/xkcd.yml")
        except:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/xkcd.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        ### Same for the data files
        try:
            self._comic_cache = self._storage.get_file(
                self, "data", YAML,
                "plugins/xkcd/comic-cache.yml")
        except:
            self.logger.exception("Error loading comic-cache!")
            self.logger.error("Disabling...")
            self._disable_self()
        try:
            self._archive = self._storage.get_file(self, "data", YAML,
                                                   "plugins/xkcd/archive.yml")
        except:
            self.logger.exception("Error loading archive!")
            self.logger.error("Disabling...")
            self._disable_self()

        ### Initial data file setup and stuff
        self._load()

        self._config.add_callback(self._load)
        self._comic_cache.add_callback(self._load)
        self._archive.add_callback(self._load)

        ### Register commands
        self._commands.register_command("xkcd",
                                        self.xkcd_cmd,
                                        self,
                                        "xkcd.xkcd", default=True)

    def reload(self):
        # Reload config
        try:
            self._config.reload()
        except:
            self.logger.exception("Error reloading config file!")
            return False
        # Reload data
        try:
            self._comic_cache.reload()
            self._archive.reload()
        except:
            self.logger.exception("Error reloading data files!")
            return False
        # Everything went fine
        return True

    def _load(self):
        altered = False
        if "last-update" not in self._archive:
            self._archive["last-update"] = 0
            altered = True
        if "latest" not in self._archive:
            self._archive["latest"] = 0
            altered = True
        if "by-id" not in self._archive:
            self._archive["by-id"] = {}
            altered = True
        if altered:
            self._archive.save()

    def _archive_time(self):
        return self._config["archive-time"]

    def _respond(self, target, msg):
        """
        Convenience function for responding to something with a prefix. Not
        only does this avoid confusion, but it also stops people being able to
        execute other bot commands in the case that we need to put any
        user-supplied data at the start of a message.
        """
        target.respond("[xkcd] " + msg)

    def _log_failure(self, failure, msg="Exception occurred"):
        self.logger.error(msg,
                          exc_info=(failure.type, failure.value, failure.tb))

    def xkcd_cmd(self, protocol, caller, source, command, raw_args,
                 parsed_args):
        self.logger.trace("xkcd_cmd()")
        args = raw_args.split()  # Quick fix for new command handler signature
        ### Decide what they want to do
        if len(args) == 0:
            ### Get random
            self.logger.trace("xkcd_cmd - get random")
            d = self.get_random_comic()
            d.addCallbacks(self._xkcd_command_get_comic_callback,
                           self._log_failure,
                           [source])
        else:
            ### Get specific
            ## Attempt to use single arg as ID if applicable
            cid = None
            if len(args) == 1:
                try:
                    cid = int(args[0])
                except ValueError:
                    pass
            ## Actually get the comic
            if cid is None:
                ## Get comic by title
                self.logger.trace("xkcd_cmd - get by term")
                d = self.get_comic_by_term(raw_args)
                d.addCallbacks(self._xkcd_command_get_comic_callback,
                               self._xkcd_command_get_comic_errback,
                               callbackArgs=[source],
                               errbackArgs=[caller, cid, False])
            else:
                ## Get comic by id
                self.logger.trace("xkcd_cmd - get by ID")
                d = self.get_comic_by_id(cid)
                d.addCallbacks(self._xkcd_command_get_comic_callback,
                               self._xkcd_command_get_comic_errback,
                               callbackArgs=[source],
                               errbackArgs=[caller, cid, True])

    def _xkcd_command_get_comic_callback(self, comic, target):
        self.logger.trace("_xkcd_command_get_comic_callback()")
        self._respond(target,
                      '"%s" - %s' % (
                          comic["title"],
                          ("http://xkcd.com/%s/" % comic["num"])
                      ))

    def _xkcd_command_get_comic_errback(self, failure, target, cid, is_id):
        if failure.check(NoSuchComicError):
            if is_id:
                self._respond(target, "No comic with that ID")
            else:
                self._respond(target,
                              "Could not find a comic matching that term - "
                              "if you know one, tell a bot admin")
        elif failure.check(ConnectionError):
            self._log_failure(failure, "Error while getting comic '%s'" % cid)
            self._respond(target,
                          "Error while fetching comic info - try again later")
        else:
            self._log_failure(failure, "Unexpected exception occurred")

    def get_comic(self, url):
        """
        Returns the info for the given comic
        :param url: Comic URL in format http://xkcd.com/1316/
        :return: Deferred that fires with a dict of info
        """
        self.logger.debug("Getting comic (from url)")
        term = "xkcd.com/"
        pos = url.find(term)
        if pos < 0:
            return defer.succeed(None)
        pos += len(term)
        end = url.find("/")
        if end < 0:
            end = len(url) - 1
        comic_id = url[pos:end]
        return self.get_comic_by_id(comic_id)

    def get_random_comic(self):
        """
        Returns the info for a random comic
        :return: Deferred that fires with a dict of info
        """
        self.logger.debug("Getting random comic")
        d = self._ensure_archive_freshness()
        d.addBoth(
            lambda r: self._get_random_comic()
        )
        return d

    def _get_random_comic(self):
        self.logger.trace("_get_random_comic()")
        latest = self._archive["latest"]
        cid = random.randint(1, latest)
        while cid not in self._archive["by-id"]:
            # In case a comic is ever removed/skipped (paranoid programming)
            cid = random.randint(1, latest)
        return self.get_comic_by_id(cid)

    def get_comic_by_term(self, term):
        """
        Returns the info for a comic that matches the given term (title)
        :param url: Comic ID number
        :return: Deferred that fires with a dict of info
        """
        self.logger.debug("Getting comic by term")
        ### Update the archive, if necessary
        d = self._ensure_archive_freshness()
        d.addBoth(
            lambda r: self._get_comic_by_term(term)
        )
        return d

    def _get_comic_by_term(self, term):
        self.logger.trace("_get_comic_by_term()")
        ### Search the archive for the given term
        term = term.lower()
        half_match = None
        with self._archive.mutex:
            for cid, item in self._archive["by-id"].iteritems():
                if term == item:
                    half_match = cid
                    break
                elif term in item:
                    half_match = cid
        if half_match is None:
            return defer.succeed(None)
        return self.get_comic_by_id(half_match)

    def get_comic_by_id(self, comic_id):
        """
        Returns the info for the given comic
        :param url: Comic ID number
        :return: Deferred that fires with a dict of info
        """
        self.logger.debug("Getting comic by ID")
        if comic_id in self._comic_cache:
            return defer.succeed(dict(self._comic_cache[comic_id]))
        else:
            d = treq.get("http://xkcd.com/%s/info.0.json" % comic_id)
            d.addCallbacks(self._get_comic_result,
                           self._get_comic_error,
                           [comic_id])
            return d

    def _get_comic_result(self, result, comic_id):
        self.logger.trace("_get_comic_result()")
        if result.code == 200:
            d = result.json()
            d.addCallbacks(self._get_comic_result_json,
                           self._get_comic_error,
                           [comic_id])
            return d
        elif result.code == 404:
            return defer.fail(NoSuchComicError(comic_id))
        else:
            return defer.fail(
                ConnectionError("Unexpected response code: %s" % result.code)
            )

    def _get_comic_result_json(self, result, comic_id):
        self.logger.trace("_get_comic_result_json()")
        with self._comic_cache:
            self._comic_cache[comic_id] = result
        return result

    def _get_comic_error(self, failure):
        self._log_failure(failure, "Error while fetching comic")

    def _ensure_archive_freshness(self):
        self.logger.trace("Ensuring archive freshness")
        if time.time() - self._archive["last-update"] > self._archive_time:
            return self._update_archive()
        else:
            return defer.succeed(True)

    def _update_archive(self):
        self.logger.debug("Updating archive...")
        d = treq.get("http://xkcd.com/archive/")
        d.addCallbacks(
            self._update_archive_callback,
            self._log_failure,
            errbackArgs=["Error while updating archive (fetching)"]
        )
        return d

    def _update_archive_callback(self, response):
        self.logger.trace("_update_archive_callback()")
        d = response.content()
        d.addCallbacks(
            self._update_archive_content_callback,
            self._log_failure,
            errbackArgs=["Error while updating archive (reading)"]
        )
        return d

    def _update_archive_content_callback(self, content):
        self.logger.trace("_update_archive_content_callback()")
        with self._archive.mutex:
            try:
                soup = BeautifulSoup(content)
                links = soup.select("#middleContainer a")
                latest = 1
                for link in links:
                    href = None
                    try:
                        href = link["href"]
                        cid = int(href.strip("/"))
                        self._archive["by-id"][cid] = link.text.lower()
                        if cid > latest:
                            latest = cid
                    except Exception as e:
                        self.logger.exception("Error while updating archive "
                                              "cache - unexpected href '%s'"
                                              % href)
                self._archive["latest"] = latest
                self._archive["last-update"] = time.time()
                return defer.succeed(True)
            except Exception as e:
                self.logger.exception("Error while updating archive cache "
                                      "- using old version")
                return defer.fail()
Пример #27
0
class JargonPlugin(plugin.PluginObject):

    commands = None
    storage = None

    _file_config = None
    _config = None
    _word_cache = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self._file_config = self.storage.get_file(
                self,
                "config",
                YAML,
                "plugins/jargon.yml"
            )
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._file_config.exists:
            self.logger.error("Unable to find config/plugins/jargon.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        self._file_config.add_callback(self.load)
        self.load()

        ### Register commands
        self.commands.register_command(
            "jargon",
            self.jargon_cmd,
            self,
            JARGON_PERM,
            aliases=["generatesentence", "generate"],
            default=True
        )
        self.commands.register_command(
            "jargonlist",
            self.jargonlist_cmd,
            self,
            JARGON_LIST_PERM,
            aliases=["generatesentencelist", "generatelist"],
            default=True
        )
        # TODO: jargonlist command for themes

    def reload(self):
        try:
            self._file_config.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        self.load()
        return True

    def load(self):
        self._config = convert_config(self._file_config)
        self._word_cache = LRU(self.cache_size)

    def _respond(self, target, message):
        if self.prefix_response:
            message = "[Jargon] " + message
        target.respond(message)

    @property
    def per_category_permissions(self):
        return self._config.get("per_category_permissions", False)

    @property
    def prefix_response(self):
        return self._config.get("prefix_response", False)

    @property
    def cache_size(self):
        # This cache should pretty much never miss unless someone has a heck of
        # a lot of categories, but hey, it's just as easy to implement as a
        # limitless dict thanks to the boltons package, and some people are
        # running Ultros in very low memory environments where every little
        # helps.
        return self._config.get("cache_size", 64)

    @property
    def _categories(self):
        return self._config.get("categories", {})

    def _get_category(self, category):
        try:
            return self._categories[category]
        except LookupError:
            self.logger.trace(
                'Invalid category "{}" - trying aliases',
                category
            )
            for k, v in self._categories.iteritems():
                if category in v.get("names", []):
                    return v

        self.logger.warning('Invalid category "{}" given', category)
        raise InvalidCategory()

    def _get_format_args(self, category, category_name):
        # This is what happens when you adds things as an after thought rather
        # than planning shit out...
        # Make sure we always use the same name to cache the category
        category_name = category.get("names", [category_name])[0]
        try:
            return self._word_cache[category_name]
        except KeyError:
            self.logger.debug("Word cache miss")
        words = {}
        for k, v in category["words"].iteritems():
            if k == "noun":
                words[k] = Nouns(v)
            elif k == "verb":
                words[k] = Verbs(v)
            else:
                words[k] = WordClass(v)
        self._word_cache[category_name] = words
        return words

    def _check_category_perm(self, category, caller, source, protocol,
                             cat=None):
        if cat is None:
            cat = self._get_category(category)
        category = cat.get("names", [category])[0]
        self.logger.debug('Checking permission for category "{}"', category)
        return self.commands.perm_handler.check(
            JARGON_CATEGORY_PERM % category,
            caller,
            source,
            protocol
        )

    def jargon_cmd(self, protocol, caller, source, command, raw_args,
                   parsed_args):
        if len(raw_args) > 0:
            cat = raw_args.lower()
        else:
            cat = None
        if self.per_category_permissions:
            if cat is None:
                try:
                    cat = self._config["default"]
                except KeyError:
                    self.logger.error(
                        "Cannot have per_category_permissions without default"
                    )
                    return
            if not self._check_category_perm(cat, caller, source, protocol):
                caller.respond("You don't have permission to do that")
                return
        gen_kwargs = {}
        if cat:
            gen_kwargs["category"] = cat
        try:
            self._respond(source, self.generate_sentence(**gen_kwargs))
        except InvalidCategory:
            caller.respond("I don't know any jargon for that")

    def jargonlist_cmd(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        categories = self._config.get("categories", {}).iteritems()
        if self.per_category_permissions:
            # Perm check spam time!
            def _cat_filter(cat):
                return self._check_category_perm(cat[0], caller, source,
                                                 protocol, cat[1])
            categories = filter(_cat_filter, categories)
        categories = map(lambda c: c[1].get("names", [c[0]])[0], categories)
        self._respond(source, "Categories: " + ", ".join(categories))

    def generate_sentence(self, category=None):
        if category is None:
            category = self._config.get("default", None)
            if category is None:
                category = random.choice(self._config["categories"].keys())
        cat = self._get_category(category)
        fmt = random.choice(cat["formats"])
        fmt_args = self._get_format_args(cat, category)
        sentence = fmt.format(**fmt_args)
        if cat.get("options", {}).get("capitalise_start", True):
            sentence = sentence[0].upper() + sentence[1:]
        return sentence
Пример #28
0
class LastFMPlugin(plugin.PluginObject):

    commands = None
    _config = None
    storage = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self._config = self.storage.get_file(self, "config", YAML,
                                                 "plugins/lastfm.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/lastfm.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return
            ### Same for the data file (nickname=>lastfmusername map)
        try:
            self._nickmap = self.storage.get_file(
                self, "data", YAML, "plugins/lastfm-nickmap.yml")
        except Exception:
            self.logger.exception("Error loading nickmap!")
            self.logger.error("Disabling...")
            self._disable_self()

        ### Load options from config and nick map from data
        self._load()

        self._config.add_callback(self._load)

        ### Register commands
        self.commands.register_command("nowplaying",
                                       self.nowplaying_cmd,
                                       self,
                                       "lastfm.nowplaying",
                                       aliases=["np"],
                                       default=True)
        self.commands.register_command("lastfmnick",
                                       self.lastfmnick_cmd,
                                       self,
                                       "lastfm.lastfmnick",
                                       default=True)
        self.commands.register_command("lastfmcompare",
                                       self.compare_cmd,
                                       self,
                                       "lastfm.compare",
                                       aliases=["musiccompare", "compare"],
                                       default=True)

    def reload(self):
        try:
            self._config.reload()
            self._nickmap.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        self._load()
        return True

    def _load(self):
        self.api = LastFM(self._apikey)

    @property
    def _apikey(self):
        return self._config["apikey"]

    @property
    def _recent_play_limit(self):
        # Allow for old configs without this setting
        if "recent_play_limit" in self._config:
            return self._config["recent_play_limit"]
        else:
            return 300  # 5 minutes in seconds

    def _get_username(self, user, none_if_unset=False):
        user = user.lower()
        try:
            return self._nickmap[user]
        except KeyError:
            if none_if_unset:
                return None
            else:
                return user

    def _set_username(self, user, lastfm_user):
        with self._nickmap:
            self._nickmap[user.lower()] = lastfm_user

    def _respond(self, target, msg):
        """
        Convenience function for responding to something with a prefix. Not
        only does this avoid confusion, but it also stops people being able to
        execute other bot commands in the case that we need to put any
        user-supplied data at the start of a message.
        """
        target.respond("LastFM: " + msg)

    def lastfmnick_cmd(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) == 0:
            username = self._get_username(caller.nickname, True)
            if username is None:
                caller.respond("You have no stored username")
            else:
                caller.respond("Your stored username is %s" % username)
        elif len(args) == 1:
            self._set_username(caller.nickname, args[0])
            caller.respond("Your stored username has been updated")
        else:
            caller.respond("Usage: {CHARS}lastfmnick [lastfm username]")

    def nowplaying_cmd(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        self.logger.trace("Entering nowplaying_cmd()")
        args = raw_args.split()  # Quick fix for new command handler signature
        ### Get LastFM username to use
        username = None
        if len(args) == 0:
            username = self._get_username(caller.nickname)
        elif len(args) == 1:
            username = self._get_username(args[0])
        else:
            caller.respond("Usage: {CHARS}nowplaying [lastfm username]")
            return

        ### Query LastFM for user's most recent track
        deferred = self.api.user_get_recent_tracks(username, limit=1)
        deferred.addCallbacks(
            lambda r: self._nowplaying_cmd_recent_tracks_result(
                caller, source, username, r),
            lambda f: self._nowplaying_cmd_error(caller, f))

    def compare_cmd(self, protocol, caller, source, command, raw_args,
                    parsed_args):
        self.logger.trace("Entering compare_cmd()")
        args = raw_args.split()  # Quick fix for new command handler signature
        ### Get LastFM username to use
        username_one = None
        username_two = None
        if len(args) == 1:
            username_one = self._get_username(caller.nickname)
            username_two = self._get_username(args[0])
        elif len(args) == 2:
            username_one = self._get_username(args[0])
            username_two = self._get_username(args[1])
        else:
            caller.respond(
                "Usage: {CHARS}%s <lastfm username> [lastfm username]" %
                command)
            return

        ### Query LastFM for user taste comparison
        deferred = self.api.tasteometer_compare("user", username_one, "user",
                                                username_two)
        deferred.addCallbacks(
            lambda r: self._compare_cmd_tasteometer_result(
                caller, source, username_one, username_two, r),
            lambda f: self._compare_cmd_tasteometer_error(caller, f))

    def _nowplaying_cmd_recent_tracks_result(self, caller, source, username,
                                             result):
        """
        Receives the API response for User.getRecentTracks.
        """
        self.logger.trace("Entering _nowplaying_cmd_recent_tracks_result()")
        # Extract track info
        try:
            tracks = result["recenttracks"]["track"]
            if len(tracks) == 0:
                # User has never listened to anything - an extreme edge-case,
                # I know, but we should really handle it - (untested)
                self._respond(source,
                              "%s hasn't listened to anything" % username)
                return
            if isinstance(tracks, list):
                track = tracks[0]
            else:
                track = tracks
            # Check if track is currently playing, or was played recently
            now_playing = ("@attr" in track and "nowplaying" in track["@attr"]
                           and bool(track["@attr"]["nowplaying"]))
            just_played = ("date" in track
                           and (datetime.utcnow() - datetime.utcfromtimestamp(
                               float(track["date"]["uts"]))).seconds <=
                           self._recent_play_limit)
            if now_playing or just_played:
                track_artist = track["artist"]["#text"]
                track_title = track["name"]
                album = ""
                if "album" in track:
                    album = track["album"]["#text"]
                mbid = None
                if "mbid" in track and track["mbid"]:
                    mbid = track["mbid"]
                ### Query LastFM for track info, then finally send info to chan
                deferred = self.api.track_get_info(track_title, track_artist,
                                                   mbid, username)
                deferred.addCallbacks(
                    lambda r: self._nowplaying_cmd_end_result(
                        caller, source, username, now_playing, track_artist,
                        track_title, album, r),
                    # TODO: If error here, just send the basic info?
                    lambda f: self._nowplaying_cmd_error(caller, f))
            else:
                self._respond(
                    source,
                    "%s is not currently listening to anything" % username)
        except:
            self.logger.exception("Please tell the developer about this error")

    def _nowplaying_cmd_end_result(self, caller, source, username, now_playing,
                                   track_artist, track_title, album, result):
        self.logger.trace("Entering _nowplaying_cmd_end_result()")
        try:
            ### Extract track info
            user_loved = False
            user_play_count = 0
            total_play_count = 0
            listener_count = 0
            duration = 0
            url = ""
            tags = []
            track = result["track"]
            # I don't know if any of these may not exist
            if "userloved" in track:
                user_loved = track["userloved"] == "1"
            if "userplaycount" in track:
                user_play_count = int(track["userplaycount"])
            if "playcount" in track:
                total_play_count = int(track["playcount"])
            if "listeners" in track:
                listener_count = int(track["listeners"])
            if "duration" in track:
                duration = int(track["duration"])
            if "url" in track:
                url = track["url"]
            if "id" in track:
                try:
                    fragment = nb60.numtosxg(int(track["id"]))
                    url = "http://last.fm/+t{}".format(fragment)
                except:
                    self.logger.exception(
                        "Error getting short URL; using long one.")

            if "toptags" in track and isinstance(track["toptags"], dict):
                # type check due to an irregularity in the LastFM API: http://
                # www.last.fm/group/Last.fm+Web+Services/forum/21604/_/2231458
                if isinstance(track["toptags"]["tag"], dict):
                    # If the list only contains one item, it gets turned into
                    # a dict, so reverse that shit
                    track["toptags"]["tag"] = [track["toptags"]["tag"]]
                for tag in track["toptags"]["tag"]:
                    # TODO: Make these clickable links for protocols that can?
                    if not isinstance(tag, dict):
                        self.logger.error("Tag isn't a dict!? - %s" % tag)
                        continue
                    tags.append(tag["name"])

            ### Finally, we send the message
            # TODO: This could do with a cleanup
            status_text = u"just listened to"
            if now_playing:
                status_text = u"is now playing"
            output = [
                u'%s %s: "%s" by %s' %
                (username, status_text, track_title, track_artist)
            ]
            if album:
                output.append(u" [%s]" % album)
            output.append(u" - ")
            if user_loved:
                output.append(u"\u2665 ")  # Heart
            output.append(u"%s listens by %s, %s listens by %s listeners" % (
                # Localisation support? What's that?
                "{:,}".format(user_play_count),
                username,
                "{:,}".format(total_play_count),
                "{:,}".format(listener_count)))
            if len(tags) > 0:
                output.append(u" - Tags: %s" % u", ".join(tags))
            if url:
                output.append(u" - %s" % url)
            self._respond(source, u"".join(output))
        except:
            self.logger.exception("Please tell the developer about this error")

    def _compare_cmd_tasteometer_result(self, caller, source, username_one,
                                        username_two, response):
        """
        Receives the API response for User.getRecentTracks.
        """
        self.logger.trace("Entering _compare_cmd_tasteometer_result()")
        try:
            ### Extract info
            result = response["comparison"]["result"]
            score = float(result["score"])
            score_percent = score * 100
            # More weird shit caused by using the JSON API... <_<
            artist_count = -1
            if "@attr" in result["artists"]:
                artist_count = int(result["artists"]["@attr"]["matches"])
            else:
                artist_count = int(result["artists"]["matches"])
            artists = []
            if artist_count > 0:
                _json_artists = result["artists"]["artist"]
                if isinstance(_json_artists, dict):
                    _json_artists = [_json_artists]
                for artist in _json_artists:
                    artists.append(artist["name"])

            ### Send the message
            output = [
                u'%s and %s are %.0f%% compatible.' %
                (username_one, username_two, score_percent)
            ]

            if len(artists) > 0:
                output.append(u" Some artists they share: ")
                output.append(u", ".join(artists))

            self._respond(source, u"".join(output))
        except:
            # TODO: Remove this debug dump line
            print __import__("json").dumps(response)
            self.logger.exception("Please tell the developer about this error")

    def _nowplaying_cmd_error(self, caller, failure):
        """
        :type failure: twisted.python.failure.Failure
        """
        # Some errors will be caused by user input
        if failure.check(LastFMError) and failure.value.err_code in (6, ):
            self._respond(caller, failure.value.message)
        else:
            self.logger.debug("Error while fetching nowplaying",
                              exc_info=(failure.type, failure.value,
                                        failure.tb))
            caller.respond("There was an error while contacting LastFM - "
                           "please alert a bot admin or try again later")

    def _compare_cmd_tasteometer_error(self, caller, failure):
        """
        :type failure: twisted.python.failure.Failure
        """
        # Some errors will be caused by user input
        if failure.check(LastFMError) and failure.value.err_code in (6, 7):
            self._respond(caller, failure.value.message)
        else:
            self.logger.debug("Error while fetching comparison",
                              exc_info=(failure.type, failure.value,
                                        failure.tb))
            caller.respond("There was an error while contacting LastFM - "
                           "please alert a bot admin or try again later")
Пример #29
0
class TwilioPlugin(plugin.PluginObject):

    config = None
    data = None

    commands = None
    events = None
    plugins = None
    storage = None

    twilio = None

    @property
    def web(self):
        """
        :rtype: WebPlugin
        """
        return self.plugins.get_plugin("Web")

    def setup(self):
        self.logger.trace("Entered setup method.")

        self.commands = CommandManager()
        self.events = EventManager()
        self.plugins = PluginManager()
        self.storage = StorageManager()

        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/twilio.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            return self._disable_self()
        else:
            if not self.config.exists:
                self.logger.error("Unable to find config/plugins/twilio.yml")
                return self._disable_self()

        try:
            self.data = self.storage.get_file(self, "data", JSON,
                                              "plugins/twilio/contacts.json")
        except Exception:
            self.logger.exception("Error loading data!")
            self.logger.error("This data file is required. Shutting down...")
            return self._disable_self()

        self._load()
        self.config.add_callback(self._load)

        self.events.add_callback("Web/ServerStartedEvent", self,
                                 self.add_routes, 0)

        self.commands.register_command("sms", self.sms_command, self,
                                       "twilio.sms")
        self.commands.register_command("mms", self.mms_command, self,
                                       "twilio.mms")
        self.commands.register_command("tw", self.tw_command, self,
                                       "twilio.tw")

    def _load(self):
        self.twilio = TwilioRestClient(self.config["identification"]["sid"],
                                       self.config["identification"]["token"])

        account = self.twilio.accounts.get(
            self.config["identification"]["sid"])

        self.logger.info("Logged in as [%s] %s." %
                         (account.type, account.friendly_name))

    def add_routes(self, event):
        self.web.add_handler(
            r"/twilio/%s" % self.config["security"]["api_key"],
            "plugins.twilio.route.Route")
        self.logger.info("Registered route: /twilio/<apikey>")

    def tw_command(self, protocol, caller, source, command, raw_args,
                   parsed_args):

        args = raw_args.split(" ")
        if len(args) < 1:
            caller.respond("Usage: {CHARS}tw <contact/reload> [<set/del/get> "
                           "<[name] [number]>]")
            return

        action = args[0].lower()

        if action == "reload":
            self.config.reload()
            self.data.reload()

            source.respond("Files reloaded.")
            return

        if action != "contact":
            caller.respond("Unknown action: %s" % action)
            return

        if len(args) < 3:
            caller.respond("Usage: {CHARS}tw contact <set/del/get> "
                           "<[name] [number]>")
            return

        operation = args[1].lower()
        target = args[2]

        for case, default in switch(operation):  # I was bored, okay?
            if case("set"):
                if len(args) < 4:
                    caller.respond("Usage: {CHARS}tw contact set <name>"
                                   " <number>")
                    break
                try:
                    self.save_contact(name=target, number=args[3])
                except Exception as e:
                    source.respond("Error saving contact: %s" % e)
                else:
                    source.respond("Contact '%s' saved." % target)

                break
            if case("del"):
                try:
                    if target.startswith("+"):
                        c = self.load_contact(number=target)
                    else:
                        c = self.load_contact(name=target)

                    if not c:
                        source.respond("No contact found for '%s'" % target)
                        return

                    r = self.del_contact(contac_=c)

                    if not r:
                        source.respond("No contact found for '%s'" % target)
                    else:
                        source.respond("Contact for '%s' deleted." % target)
                except Exception as e:
                    source.respond("Error deleting contact: %s" % e)

                break
            if case("get"):
                try:
                    if target.startswith("+"):
                        c = self.load_contact(number=target)
                    else:
                        c = self.load_contact(name=target)

                    if not c:
                        source.respond("No contact found for '%s'" % target)
                    else:
                        source.respond("CONTACT | %s -> %s" %
                                       (c.name, c.number))
                except Exception as e:
                    source.respond("Error loading contact: %s" % e)

                break
            if default:
                caller.respond("Unknown operation: %s" % operation)

    def sms_command(self, protocol, caller, source, command, raw_args,
                    parsed_args):
        args = raw_args.split(" ")
        if len(args) < 2:
            caller.respond("Usage: {CHARS}sms <name/number> <message>")
            return

        sent = self.config["formatting"].get("sms-sent",
                                             "SMS | {TO} | Message sent.")
        error = self.config["formatting"].get("sms-error",
                                              "SMS | ERROR | {ERROR}")

        target = args[0]
        message = "<%s> %s" % (caller.nickname, " ".join(args[1:]))

        if target.startswith("+"):
            c = self.load_contact(number=target)
        else:
            c = self.load_contact(name=target)

        if c is None:
            name = target
            if not target.startswith("+"):
                source.respond(
                    error.replace("{ERROR}", "Numbers must start with a '+'"))
                return

            sent = sent.replace("{TO}", name)

            try:
                self.send_sms(name, message)
            except TwilioRestException as e:
                source.respond(error.replace("{ERROR}", str(e.msg)))
            except Exception as e:
                source.respond(error.replace("{ERROR}", str(e)))
            else:
                source.respond(sent)
        else:
            name = c.name
            sent = sent.replace("{TO}", name)

            try:
                self.send_sms(c, message)
            except TwilioRestException as e:
                if "has not been enabled for MMS" in e.msg:
                    e.msg = "Twilio number has not been enabled for MMS."
                elif "Please use only valid http and https urls" in e.msg:
                    e.msg = "Media URL is invalid - please use a link to a " \
                            "media file."
                source.respond(error.replace("{ERROR}", str(e.msg)))
            except Exception as e:
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e).replace("\r",
                                       "").replace("\n",
                                                   " ").replace("  ", " ")))
                self.logger.exception("Error sending SMS")
            else:
                source.respond(sent)

    def mms_command(self, protocol, caller, source, command, raw_args,
                    parsed_args):
        args = raw_args.split(" ")
        if len(args) < 3:
            caller.respond("Usage: {CHARS}mms <name/number> <url> <message>")
            return

        sent = self.config["formatting"].get("mms-sent",
                                             "MMS | {TO} | Message sent.")
        error = self.config["formatting"].get("mms-error",
                                              "MMS | ERROR | {ERROR}")

        target = args[0]
        url = args[1]
        message = "<%s> %s" % (caller.nickname, " ".join(args[2:]))

        if target.startswith("+"):
            c = self.load_contact(number=target)
        else:
            c = self.load_contact(name=target)

        if c is None:
            name = target
            if not target.startswith("+"):
                source.respond(
                    error.replace("{ERROR}", "Numbers must start with a '+'"))
                return

            sent = sent.replace("{TO}", name)

            try:
                self.send_sms(name, message, media_url=url)
            except TwilioRestException as e:
                if "has not been enabled for MMS" in e.msg:
                    e.msg = "Twilio number has not been enabled for MMS."
                elif "Please use only valid http and https urls" in e.msg:
                    e.msg = "Media URL is invalid - please use a link to a " \
                            "media file."
                source.respond(error.replace("{ERROR}", str(e.msg)))
            except Exception as e:
                source.respond(
                    error.replace(
                        "{ERROR}",
                        str(e).replace("\r",
                                       "").replace("\n",
                                                   " ").replace("  ", " ")))
                self.logger.exception("Error sending MMS")
            else:
                source.respond(sent)
        else:
            name = c.name
            sent = sent.replace("{TO}", name)

            try:
                self.send_sms(c, message, media_url=url)
            except TwilioRestException as e:
                source.respond(error.replace("{ERROR}", str(e.msg)))
            except Exception as e:
                source.respond(error.replace("{ERROR}", str(e)))
            else:
                source.respond(sent)

    def do_targets(self, sender, message):
        sender = str(sender).strip()
        c = self.load_contact(number=sender)
        name = "default"

        if c is not None:
            name = c.name

        if name not in self.config["targetting"]:
            name = "default"

            if name not in self.config["targetting"]:
                self.logger.trace("No default target found.")
                return

        targets = self.config["targetting"][name]

        f_str = self.config["formatting"].get("sms",
                                              "SMS | {FROM} | {MESSAGE}")

        from_ = name

        if c is not None:
            from_ = c.name
        elif from_ == "default":
            from_ = sender

        message = str(message).replace("\r", "")
        message = message.replace("\n", " ")

        f_str = f_str.replace("{FROM}", from_)
        f_str = f_str.replace("{MESSAGE}", message)

        self.logger.info(f_str)

        for target in targets:
            try:
                p_str = target["protocol"]
                p = self.factory_manager.get_protocol(p_str)

                if p is None:
                    self.logger.warn("No such protocol: %s" % p_str)
                    continue

                p.send_msg(target["target"],
                           f_str,
                           target_type=target["target-type"])
            except Exception:
                self.logger.exception("Error relaying SMS message")
                continue

    def send_sms(self, target, message, media_url=None):
        if isinstance(target, Contact):
            target = target.number

        msg = self.twilio.messages.create(
            body=message,
            to=target,
            from_=self.config["identification"]["number"],
            media_url=media_url)

        return msg

    def load_contact(self, contac_=None, name=None, number=None):
        if contac_ is not None:
            return contac_

        if name is not None:
            number = self.data.get(name, None)
            if number is not None:
                return Contact(number, name, self)

        if number is not None:
            for k in self.data.keys():
                if number == self.data[k]:
                    return Contact(number, k, self)

        return None

    def save_contact(self, contac_=None, name=None, number=None):
        if contac_ is not None:
            with self.data:
                self.data[contac_.name] = contac_.number
            return contac_
        elif name is not None and number is not None:
            contac_ = Contact(number, name, self)
            with self.data:
                self.data[contac_.name] = contac_.number
            return contac_

        raise ValueError("You need to give either a contact, or a name and "
                         "number to save.")

    def del_contact(self, contac_=None, name=None, number=None):
        if contac_ is not None:
            with self.data:
                if contac_.name in self.data:
                    del self.data[contac_.name]
                    return True
                return False

        elif name is not None:
            with self.data:
                if name in self.data:
                    del self.data[name]
                    return True
                return False

        elif number is not None:
            for k in self.data.keys():
                if number == self.data[k]:
                    del self.data[k]
                    return True
            return False

        raise ValueError("You need to give either a contact, name or "
                         "number to delete.")

    pass  # So the regions work in PyCharm
Пример #30
0
class FactoidsPlugin(plugin.PluginObject):

    CHANNEL = "channel"
    PROTOCOL = "protocol"
    GLOBAL = "global"

    PERM_ADD = "factoids.add.%s"
    PERM_SET = "factoids.set.%s"
    PERM_DEL = "factoids.delete.%s"
    PERM_GET = "factoids.get.%s"

    (RES_INVALID_LOCATION,
     RES_INVALID_METHOD,  # _FOR_LOCATION - i.e. CHANNEL in PM
     RES_NO_PERMS,
     RES_MISSING_FACTOID) = xrange(4)

    def setup(self):
        # ## Grab important shit
        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()
        self.plugman = PluginManager()

        # ## Set up database
        self.database = self.storage.get_file(
            self,
            "data",
            DBAPI,
            "sqlite3:data/plugins/factoids.sqlite",
            "data/plugins/factoids.sqlite",
            check_same_thread=False
        )

        self.database.add_callback(self.reload)
        self.reload()

        # ## Register commands
        # We have multiple possible permissions per command, so we have to do
        # permission handling ourselves
        self.commands.register_command("addfactoid",
                                       self.factoid_add_command,
                                       self,
                                       None)
        self.commands.register_command("setfactoid",
                                       self.factoid_set_command,
                                       self,
                                       None)
        self.commands.register_command("deletefactoid",
                                       self.factoid_delete_command,
                                       self,
                                       None,
                                       ["delfactoid"])
        self.commands.register_command("getfactoid",
                                       self.factoid_get_command,
                                       self,
                                       None, default=True)

        # ## Register events
        self.events.add_callback("MessageReceived",
                                 self,
                                 self.message_handler,
                                 1)

        self.events.add_callback("Web/ServerStartedEvent",
                                 self,
                                 self.web_routes,
                                 1)

    def reload(self):
        with self.database as db:
            db.runQuery("CREATE TABLE IF NOT EXISTS factoids ("
                        "factoid_key TEXT, "
                        "location TEXT, "
                        "protocol TEXT, "
                        "channel TEXT, "
                        "factoid_name TEXT, "
                        "info TEXT, "
                        "UNIQUE(factoid_key, location, protocol, channel) "
                        "ON CONFLICT REPLACE)")

    # region Util functions

    def __check_perm(self, perm, caller, source, protocol):
        self.logger.trace(_("Checking for permission: '%s'"), perm)
        allowed = self.commands.perm_handler.check(perm,
                                                   caller,
                                                   source,
                                                   protocol)
        return allowed

    def _parse_args(self, raw_args):
        """
        Grabs the location, factoid name, and info from a raw_args string
        """
        pos = raw_args.find(" ")
        if pos < 0:
            raise ValueError(_("Invalid args"))
        location = raw_args[:pos]
        pos2 = raw_args.find(" ", pos + 1)
        if pos2 < 0:
            raise ValueError(_("Invalid args"))
        factoid = raw_args[pos + 1:pos2]
        # pos3 = raw_args.find(" ", pos2 + 1)
        info = raw_args[pos2 + 1:]
        if info == "":
            raise ValueError(_("Invalid args"))
        return location, factoid, info

    def valid_location(self, location, source=None):
        """
        Checks if a given location is one of channel, protocol or global, and
        if it's a channel request, that it's in a channel.
        """
        location = location.lower()
        result = location in (self.CHANNEL, self.PROTOCOL, self.GLOBAL)
        if not result:
            raise InvalidLocationError(_("'%s' is not a valid location") %
                                       location)
        if source is not None:
            if location == self.CHANNEL and not isinstance(source, Channel):
                raise InvalidMethodError(_("'channel' location can only be "
                                           "used inside a channel"))
        return True

    # endregion

    # region API functions to access factoids

    def _add_factoid_interaction(self, txn, factoid_key, location, protocol,
                                 channel, factoid, info):
        """
        Appends a factoid to an existing one if there, otherwise creates it.
        :return: True if already exists, otherwise False
        """
        txn.execute("SELECT * FROM factoids WHERE "
                    "factoid_key = ? AND location = ? AND "
                    "protocol = ? AND channel = ?",
                    (
                        to_unicode(factoid_key),
                        to_unicode(location),
                        to_unicode(protocol),
                        to_unicode(channel)
                    ))
        results = txn.fetchall()
        if len(results) == 0:
            # Factoid doesn't exist yet, create it
            txn.execute("INSERT INTO factoids VALUES(?, ?, ?, ?, ?, ?)",
                        (
                            to_unicode(factoid_key),
                            to_unicode(location),
                            to_unicode(protocol),
                            to_unicode(channel),
                            to_unicode(factoid),
                            to_unicode(info)
                        ))

            e = FactoidAddedEvent(self, factoid_key, factoid)
            self.events.run_callback("Factoids/Added", e, from_thread=True)
            return False
        else:
            # Factoid already exists, append
            txn.execute("INSERT INTO factoids VALUES(?, ?, ?, ?, ?, ?)",
                        (
                            to_unicode(results[0][0]),
                            to_unicode(results[0][1]),
                            to_unicode(results[0][2]),
                            to_unicode(results[0][3]),
                            to_unicode(results[0][4]),
                            results[0][5] + u"\n" + to_unicode(info)
                        ))
            e = FactoidUpdatedEvent(self, factoid_key, factoid)
            self.events.run_callback("Factoids/Updated", e, from_thread=True)
            return True

    def _delete_factoid_interaction(self, txn, factoid_key, location, protocol,
                                    channel):
        """
        Deletes a factoid if it exists, otherwise raises MissingFactoidError
        """

        self.logger.trace("DELETE | Key: %s | Loc: %s | Pro: %s | Cha: %s"
                          % (factoid_key, location, protocol, channel))

        if location == self.CHANNEL:
            txn.execute("DELETE FROM factoids WHERE factoid_key = ? AND "
                        "location = ? AND protocol = ? AND channel = ?",
                        (
                            to_unicode(factoid_key),
                            to_unicode(location),
                            to_unicode(protocol),
                            to_unicode(channel)
                        ))
        else:
            txn.execute("DELETE FROM factoids WHERE factoid_key = ? AND "
                        "location = ? AND protocol = ?",
                        (
                            to_unicode(factoid_key),
                            to_unicode(location),
                            to_unicode(protocol)
                        ))
        if txn.rowcount == 0:
            raise MissingFactoidError(_("Factoid '%s' does not exist") %
                                      factoid_key)

        e = FactoidDeletedEvent(self, factoid_key)
        self.events.run_callback("Factoids/Deleted", e, from_thread=True)

    def _get_factoid_interaction(self, txn, factoid_key, location, protocol,
                                 channel):
        """
        Gets a factoid if it exists, otherwise raises MissingFactoidError
        :return: (factoid_name, [entry, entry, ...])
        """
        self.logger.trace(_("Getting factoid params: factoid_key = '%s', "
                            "location = '%s', protocol = '%s', "
                            "channel = '%s'"),
                          factoid_key,
                          location,
                          protocol,
                          channel)
        if location is None:
            self.logger.trace(_("Location is None - getting all factoids with "
                                "key '%s'"), factoid_key)
            txn.execute("SELECT location, protocol, channel, factoid_name, "
                        "info FROM factoids WHERE factoid_key = ?",
                        (
                            to_unicode(factoid_key),
                        ))
            results = txn.fetchall()
            if len(results) > 0:
                # Check for channel match
                for row in results:
                    if ((row[0] == self.CHANNEL and row[1] == protocol and
                         row[2] == channel)):
                        self.logger.trace(_("Match found (channel)!"))
                        return (row[3], row[4].split("\n"))
                # Check for protocol match
                for row in results:
                    if row[0] == self.PROTOCOL and row[1] == protocol:
                        self.logger.trace(_("Match found (protocol)!"))
                        return (row[3], row[4].split("\n"))
                # Check for global match
                for row in results:
                    if row[0] == self.GLOBAL:
                        self.logger.trace(_("Match found (global)!"))
                        return (row[3], row[4].split("\n"))
        else:
            txn.execute("SELECT location, protocol, channel, factoid_name, "
                        "info FROM factoids WHERE factoid_key = ? AND "
                        "location = ? AND protocol = ? AND channel = ?",
                        (
                            to_unicode(factoid_key),
                            to_unicode(location),
                            to_unicode(protocol),
                            to_unicode(channel)
                        ))
            results = txn.fetchall()
            if len(results) > 0:
                return (results[0][3], results[0][4].split("\n"))
        raise MissingFactoidError(_("Factoid '%s' does not exist")
                                  % factoid_key)

    def _get_all_factoids_interaction(self, txn):
        """
        Gets all factoids
        :return: (factoid_name, [entry, entry, ...])
        """
        self.logger.trace("Getting all factoids.")
        txn.execute("SELECT location, protocol, channel, factoid_name, "
                    "info FROM factoids")
        results = txn.fetchall()
        return results

    def get_all_factoids(self):
        with self.database as db:
            return db.runInteraction(self._get_all_factoids_interaction)

    def add_factoid(self, caller, source, protocol, location, factoid, info):
        location = location.lower()
        factoid_key = factoid.lower()
        protocol_key = protocol.name.lower()
        channel_key = source.name.lower()
        try:
            location is None or self.valid_location(location, source)
        except Exception as ex:
            return defer.fail(ex)
        if not self.__check_perm(self.PERM_ADD % location,
                                 caller,
                                 source,
                                 protocol):
            return defer.fail(
                NoPermissionError(_("User does not have required permission"))
            )
        with self.database as db:
            return db.runInteraction(self._add_factoid_interaction,
                                     factoid_key,
                                     location,
                                     protocol_key,
                                     channel_key,
                                     factoid,
                                     info)

    def set_factoid(self, caller, source, protocol, location, factoid, info):
        location = location.lower()
        factoid_key = factoid.lower()
        protocol_key = protocol.name.lower()
        channel_key = source.name.lower()
        try:
            location is None or self.valid_location(location, source)
        except Exception as ex:
            return defer.fail(ex)
        if not self.__check_perm(self.PERM_SET % location,
                                 caller,
                                 source,
                                 protocol):
            return defer.fail(
                NoPermissionError(_("User does not have required permission"))
            )
        with self.database as db:
            return db.runQuery(
                "INSERT INTO factoids VALUES(?, ?, ?, ?, ?, ?)",
                (
                    to_unicode(factoid_key),
                    to_unicode(location),
                    to_unicode(protocol_key),
                    to_unicode(channel_key),
                    to_unicode(factoid),
                    to_unicode(info)
                ))

    def delete_factoid(self, caller, source, protocol, location, factoid):
        location = location.lower()
        factoid_key = factoid.lower()
        protocol_key = protocol.name.lower()
        channel_key = source.name.lower()
        try:
            location is None or self.valid_location(location, source)
        except Exception as ex:
            return defer.fail(ex)
        if not self.__check_perm(self.PERM_DEL % location,
                                 caller,
                                 source,
                                 protocol):
            return defer.fail(
                NoPermissionError(_("User does not have required permission"))
            )
        with self.database as db:
            return db.runInteraction(self._delete_factoid_interaction,
                                     factoid_key,
                                     location,
                                     protocol_key,
                                     channel_key)

    def get_factoid(self, caller, source, protocol, location, factoid):
        if location is not None:
            location = location.lower()
        factoid_key = factoid.lower()
        protocol_key = protocol.name.lower()
        channel_key = source.name.lower()
        try:
            location is None or self.valid_location(location, source)
        except Exception as ex:
            return defer.fail(ex)
        if not self.__check_perm(self.PERM_GET % location,
                                 caller,
                                 source,
                                 protocol):
            return defer.fail(
                NoPermissionError(_("User does not have required permission"))
            )
        with self.database as db:
            return db.runInteraction(self._get_factoid_interaction,
                                     factoid_key,
                                     location,
                                     protocol_key,
                                     channel_key)

    # endregion

    # region Command handlers for interacting with factoids

    def _factoid_command_fail(self, caller, failure):
        """
        :type failure: twisted.python.failure.Failure
        """
        if failure.check(InvalidLocationError):
            caller.respond(__("Invalid location given - possible locations "
                              "are: channel, protocol, global"))
        elif failure.check(InvalidMethodError):
            caller.respond(__("You must do that in a channel"))
        elif failure.check(NoPermissionError):
            caller.respond(__("You don't have permission to do that"))
        elif failure.check(MissingFactoidError):
            caller.respond(__("That factoid doesn't exist"))
        else:
            # TODO: We should probably handle this
            failure.raiseException()

    def _factoid_get_command_success(self, source, result, args=None):
        if not args:
            args = []

        for line in result[1]:
            # _tokens = tokens.find_tokens(line)
            _numerical = tokens.find_numerical_tokens(line)

            for i, arg in enumerate(args):
                line = line.replace("{%d}" % i, arg)

            for token in _numerical:
                line = line.replace(token, "")

            # TODO: Token handlers
            source.respond("(%s) %s" % (result[0], line))

    def factoid_add_command(self, protocol, caller, source, command, raw_args,
                            parsed_args):
        try:
            location, factoid, info = self._parse_args(raw_args)
        except Exception:
            caller.respond(__("Usage: %s <location> <factoid> <info>")
                           % command)
            return
        d = self.add_factoid(caller, source, protocol, location, factoid, info)
        d.addCallbacks(
            lambda r: caller.respond(__("Factoid added")),
            lambda f: self._factoid_command_fail(caller, f)
        )

    def factoid_set_command(self, protocol, caller, source, command, raw_args,
                            parsed_args):
        try:
            location, factoid, info = self._parse_args(raw_args)
        except Exception:
            caller.respond(__("Usage: %s <location> <factoid> <info>")
                           % command)
            return
        d = self.set_factoid(caller, source, protocol, location, factoid, info)
        d.addCallbacks(
            lambda r: caller.respond(__("Factoid set")),
            lambda f: self._factoid_command_fail(caller, f)
        )

    def factoid_delete_command(self, protocol, caller, source, command,
                               raw_args, parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) != 2:
            caller.respond(__("Usage: %s <location> <factoid>")
                           % command)
            return
        location = args[0]
        factoid = args[1]
        d = self.delete_factoid(caller, source, protocol, location, factoid)
        d.addCallbacks(
            lambda r: caller.respond(__("Factoid deleted")),
            lambda f: self._factoid_command_fail(caller, f)
        )

    def factoid_get_command(self, protocol, caller, source, command, raw_args,
                            parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) == 1:
            factoid = args[0]
            location = None
        elif len(args) == 2:
            location = args[0]
            factoid = args[1]
        else:
            caller.respond(__("Usage: %s [location] <factoid>")
                           % command)
            return

        d = self.get_factoid(caller, source, protocol, location, factoid)
        d.addCallbacks(
            lambda r: self._factoid_get_command_success(source, r),
            lambda f: self._factoid_command_fail(caller, f)
        )

    # endregion

    def _print_query(self, result):
        from pprint import pprint
        pprint(result)

    def web_routes(self, event=None):
        self.logger.info(_("Registering web route.."))

        #: :type: WebPlugin
        web = self.plugman.get_plugin("Web")

        if web is None:
            self.logger.debug("Web plugin not found.")
            return

        web.add_handler(r"/factoids", "plugins.factoids.route.Route")
        web.add_handler(r"/factoids/", "plugins.factoids.route.Route")

        web.add_navbar_entry("factoids", "/factoids", "text file outline")

    def message_handler(self, event):
        """
        Handle ??-style factoid "commands"
        :type event: MessageReceived
        """
        handlers = {
            "??": self._message_handler_get,
            "?<": self._message_handler_get_self,
            "??<": self._message_handler_get_self,
            "?>": self._message_handler_get_other,
            "??>": self._message_handler_get_other,
            "??+": self._message_handler_add,
            "??~": self._message_handler_set,
            "??-": self._message_handler_delete,
            "!?+": self._message_handler_add_global,
            "!?~": self._message_handler_set_global,
            "!?-": self._message_handler_delete_global,
            "@?+": self._message_handler_add_protocol,
            "@?~": self._message_handler_set_protocol,
            "@?-": self._message_handler_delete_protocol
        }
        msg = event.message
        command = None
        factoid = ""
        args = ""
        pos = msg.find(" ")
        split = msg.split(" ")
        if pos < 0:
            command = msg
        else:
            command = msg[:pos]
            pos2 = msg.find(" ", pos + 1)
            if pos2 < 0:
                factoid = msg[pos + 1:].strip()
            else:
                factoid = msg[pos + 1:pos2].strip()
                args = msg[pos2 + 1:].strip()
        if command in handlers:
            handlers[command](command, factoid, args, event, split)

    # ## Getting "commands"

    def _message_handler_get(self, command, factoid, args, event, split):
        """
        Handle ?? factoid "command"
        :type event: MessageReceived
        """
        if not factoid:
            event.source.respond(__("Usage: ?? <factoid>"))
            return
        d = self.get_factoid(event.source,
                             event.target,
                             event.caller,
                             None,
                             factoid)
        d.addCallbacks(
            lambda r: self._factoid_get_command_success(event.target, r,
                                                        split[2:]),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    def _message_handler_get_self(self, command, factoid, args, event, split):
        """
        Handle ?< factoid "command"
        :type event: MessageReceived
        """
        if not factoid:
            event.source.respond(__("Usage: ?< <factoid>"))
            return
        d = self.get_factoid(event.source,
                             event.target,
                             event.caller,
                             None,
                             factoid)
        d.addCallbacks(
            lambda r: self._factoid_get_command_success(event.source, r,
                                                        split[2:]),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    def _message_handler_get_other(self, command, factoid, args, event, split):
        """
        Handle ?> factoid "command"
        :type event: MessageReceived
        """
        if not len(split) > 2:
            event.source.respond(__("Usage: ?> <user> <factoid>"))
            return

        wanted = split[1]
        factoid = split[2]
        user = event.caller.get_user(wanted)

        if user is None:
            event.source.respond(__("Unable to find that user."))
            return

        d = self.get_factoid(event.source,
                             event.target,
                             event.caller,
                             None,
                             factoid)
        d.addCallbacks(
            lambda r: self._factoid_get_command_success(user, r, split[3:]),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    # ## Channel "commands"

    def _message_handler_add(self, command, factoid, args, event, split):
        """
        Handle ??+ factoid "command"
        :type event: MessageReceived
        """
        if not factoid or not args:
            event.source.respond(__("Usage: ??+ <factoid> <info>"))
            return
        d = self.add_factoid(event.source,
                             event.target,
                             event.caller,
                             self.CHANNEL,
                             factoid,
                             args)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid added")),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    def _message_handler_set(self, command, factoid, args, event, split):
        """
        Handle ??~ factoid "command"
        :type event: MessageReceived
        """
        if not factoid or not args:
            event.source.respond(__("Usage: ??~ <factoid> <info>"))
            return
        d = self.set_factoid(event.source,
                             event.target,
                             event.caller,
                             self.CHANNEL,
                             factoid,
                             args)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid set")),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    def _message_handler_delete(self, command, factoid, args, event, split):
        """
        Handle ??- factoid "command"
        :type event: MessageReceived
        """
        if factoid is None:
            event.source.respond(__("Usage: ??- <factoid>"))
            return
        d = self.delete_factoid(event.source,
                                event.target,
                                event.caller,
                                self.CHANNEL,
                                factoid)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid deleted")),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    # ## Global "commands"

    def _message_handler_add_global(self, command, factoid, args, event,
                                    split):
        """
        Handle !?+ factoid "command"
        :type event: MessageReceived
        """
        if not factoid or not args:
            event.source.respond(__("Usage: !?+ <factoid> <info>"))
            return
        d = self.add_factoid(event.source,
                             event.target,
                             event.caller,
                             self.GLOBAL,
                             factoid,
                             args)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid added")),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    def _message_handler_set_global(self, command, factoid, args, event,
                                    split):
        """
        Handle !?~ factoid "command"
        :type event: MessageReceived
        """
        if not factoid or not args:
            event.source.respond(__("Usage: !?~ <factoid> <info>"))
            return
        d = self.set_factoid(event.source,
                             event.target,
                             event.caller,
                             self.GLOBAL,
                             factoid,
                             args)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid set")),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    def _message_handler_delete_global(self, command, factoid, args, event,
                                       split):
        """
        Handle !?- factoid "command"
        :type event: MessageReceived
        """
        if factoid is None:
            event.source.respond(__("Usage: !?- <factoid>"))
            return
        d = self.delete_factoid(event.source,
                                event.target,
                                event.caller,
                                self.GLOBAL,
                                factoid)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid deleted")),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    # ## Protocol-specific "commands"

    def _message_handler_add_protocol(self, command, factoid, args, event,
                                      split):
        """
        Handle @?+ factoid "command"
        :type event: MessageReceived
        """
        if not factoid or not args:
            event.source.respond(__("Usage: @?+ <factoid> <info>"))
            return
        d = self.add_factoid(event.source,
                             event.target,
                             event.caller,
                             self.PROTOCOL,
                             factoid,
                             args)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid added")),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    def _message_handler_set_protocol(self, command, factoid, args, event,
                                      split):
        """
        Handle @?~ factoid "command"
        :type event: MessageReceived
        """
        if not factoid or not args:
            event.source.respond(__("Usage: @?~ <factoid> <info>"))
            return
        d = self.set_factoid(event.source,
                             event.target,
                             event.caller,
                             self.PROTOCOL,
                             factoid,
                             args)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid set")),
            lambda f: self._factoid_command_fail(event.source, f)
        )

    def _message_handler_delete_protocol(self, command, factoid, args, event,
                                         split):
        """
        Handle @?- factoid "command"
        :type event: MessageReceived
        """
        if factoid is None:
            event.source.respond(__("Usage: @?- <factoid>"))
            return
        d = self.delete_factoid(event.source,
                                event.target,
                                event.caller,
                                self.PROTOCOL,
                                factoid)
        d.addCallbacks(
            lambda r: event.source.respond(__("Factoid deleted")),
            lambda f: self._factoid_command_fail(event.source, f)
        )
Пример #31
0
class xkcdPlugin(plugin.PluginObject):

    _commands = None
    _config = None
    _storage = None

    _comic_cache = None
    _archive = None

    def setup(self):
        ### Grab important shit
        self._commands = CommandManager()
        self._storage = StorageManager()

        ### Initial config load
        try:
            self._config = self._storage.get_file(self, "config", YAML,
                                                  "plugins/xkcd.yml")
        except:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/xkcd.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        ### Same for the data files
        try:
            self._comic_cache = self._storage.get_file(
                self, "data", YAML, "plugins/xkcd/comic-cache.yml")
        except:
            self.logger.exception("Error loading comic-cache!")
            self.logger.error("Disabling...")
            self._disable_self()
        try:
            self._archive = self._storage.get_file(self, "data", YAML,
                                                   "plugins/xkcd/archive.yml")
        except:
            self.logger.exception("Error loading archive!")
            self.logger.error("Disabling...")
            self._disable_self()

        ### Initial data file setup and stuff
        self._load()

        self._config.add_callback(self._load)
        self._comic_cache.add_callback(self._load)
        self._archive.add_callback(self._load)

        ### Register commands
        self._commands.register_command("xkcd",
                                        self.xkcd_cmd,
                                        self,
                                        "xkcd.xkcd",
                                        default=True)

    def reload(self):
        # Reload config
        try:
            self._config.reload()
        except:
            self.logger.exception("Error reloading config file!")
            return False
        # Reload data
        try:
            self._comic_cache.reload()
            self._archive.reload()
        except:
            self.logger.exception("Error reloading data files!")
            return False
        # Everything went fine
        return True

    def _load(self):
        altered = False
        if "last-update" not in self._archive:
            self._archive["last-update"] = 0
            altered = True
        if "latest" not in self._archive:
            self._archive["latest"] = 0
            altered = True
        if "by-id" not in self._archive:
            self._archive["by-id"] = {}
            altered = True
        if altered:
            self._archive.save()

    def _archive_time(self):
        return self._config["archive-time"]

    def _respond(self, target, msg):
        """
        Convenience function for responding to something with a prefix. Not
        only does this avoid confusion, but it also stops people being able to
        execute other bot commands in the case that we need to put any
        user-supplied data at the start of a message.
        """
        target.respond("[xkcd] " + msg)

    def _log_failure(self, failure, msg="Exception occurred"):
        self.logger.error(msg,
                          exc_info=(failure.type, failure.value, failure.tb))

    def xkcd_cmd(self, protocol, caller, source, command, raw_args,
                 parsed_args):
        self.logger.trace("xkcd_cmd()")
        args = raw_args.split()  # Quick fix for new command handler signature
        ### Decide what they want to do
        if len(args) == 0:
            ### Get random
            self.logger.trace("xkcd_cmd - get random")
            d = self.get_random_comic()
            d.addCallbacks(self._xkcd_command_get_comic_callback,
                           self._log_failure, [source])
        else:
            ### Get specific
            ## Attempt to use single arg as ID if applicable
            cid = None
            if len(args) == 1:
                try:
                    cid = int(args[0])
                except ValueError:
                    pass
            ## Actually get the comic
            if cid is None:
                ## Get comic by title
                self.logger.trace("xkcd_cmd - get by term")
                d = self.get_comic_by_term(raw_args)
                d.addCallbacks(self._xkcd_command_get_comic_callback,
                               self._xkcd_command_get_comic_errback,
                               callbackArgs=[source],
                               errbackArgs=[caller, cid, False])
            else:
                ## Get comic by id
                self.logger.trace("xkcd_cmd - get by ID")
                d = self.get_comic_by_id(cid)
                d.addCallbacks(self._xkcd_command_get_comic_callback,
                               self._xkcd_command_get_comic_errback,
                               callbackArgs=[source],
                               errbackArgs=[caller, cid, True])

    def _xkcd_command_get_comic_callback(self, comic, target):
        self.logger.trace("_xkcd_command_get_comic_callback()")
        self._respond(
            target, '"%s" - %s' % (comic["title"],
                                   ("http://xkcd.com/%s/" % comic["num"])))

    def _xkcd_command_get_comic_errback(self, failure, target, cid, is_id):
        if failure.check(NoSuchComicError):
            if is_id:
                self._respond(target, "No comic with that ID")
            else:
                self._respond(
                    target, "Could not find a comic matching that term - "
                    "if you know one, tell a bot admin")
        elif failure.check(ConnectionError):
            self._log_failure(failure, "Error while getting comic '%s'" % cid)
            self._respond(target,
                          "Error while fetching comic info - try again later")
        else:
            self._log_failure(failure, "Unexpected exception occurred")

    def get_comic(self, url):
        """
        Returns the info for the given comic
        :param url: Comic URL in format http://xkcd.com/1316/
        :return: Deferred that fires with a dict of info
        """
        self.logger.debug("Getting comic (from url)")
        term = "xkcd.com/"
        pos = url.find(term)
        if pos < 0:
            return defer.succeed(None)
        pos += len(term)
        end = url.find("/")
        if end < 0:
            end = len(url) - 1
        comic_id = url[pos:end]
        return self.get_comic_by_id(comic_id)

    def get_random_comic(self):
        """
        Returns the info for a random comic
        :return: Deferred that fires with a dict of info
        """
        self.logger.debug("Getting random comic")
        d = self._ensure_archive_freshness()
        d.addBoth(lambda r: self._get_random_comic())
        return d

    def _get_random_comic(self):
        self.logger.trace("_get_random_comic()")
        latest = self._archive["latest"]
        cid = random.randint(1, latest)
        while cid not in self._archive["by-id"]:
            # In case a comic is ever removed/skipped (paranoid programming)
            cid = random.randint(1, latest)
        return self.get_comic_by_id(cid)

    def get_comic_by_term(self, term):
        """
        Returns the info for a comic that matches the given term (title)
        :param url: Comic ID number
        :return: Deferred that fires with a dict of info
        """
        self.logger.debug("Getting comic by term")
        ### Update the archive, if necessary
        d = self._ensure_archive_freshness()
        d.addBoth(lambda r: self._get_comic_by_term(term))
        return d

    def _get_comic_by_term(self, term):
        self.logger.trace("_get_comic_by_term()")
        ### Search the archive for the given term
        term = term.lower()
        half_match = None
        with self._archive.mutex:
            for cid, item in self._archive["by-id"].iteritems():
                if term == item:
                    half_match = cid
                    break
                elif term in item:
                    half_match = cid
        if half_match is None:
            return defer.succeed(None)
        return self.get_comic_by_id(half_match)

    def get_comic_by_id(self, comic_id):
        """
        Returns the info for the given comic
        :param url: Comic ID number
        :return: Deferred that fires with a dict of info
        """
        self.logger.debug("Getting comic by ID")
        if comic_id in self._comic_cache:
            return defer.succeed(dict(self._comic_cache[comic_id]))
        else:
            d = treq.get("http://xkcd.com/%s/info.0.json" % comic_id)
            d.addCallbacks(self._get_comic_result, self._get_comic_error,
                           [comic_id])
            return d

    def _get_comic_result(self, result, comic_id):
        self.logger.trace("_get_comic_result()")
        if result.code == 200:
            d = result.json()
            d.addCallbacks(self._get_comic_result_json, self._get_comic_error,
                           [comic_id])
            return d
        elif result.code == 404:
            return defer.fail(NoSuchComicError(comic_id))
        else:
            return defer.fail(
                ConnectionError("Unexpected response code: %s" % result.code))

    def _get_comic_result_json(self, result, comic_id):
        self.logger.trace("_get_comic_result_json()")
        with self._comic_cache:
            self._comic_cache[comic_id] = result
        return result

    def _get_comic_error(self, failure):
        self._log_failure(failure, "Error while fetching comic")

    def _ensure_archive_freshness(self):
        self.logger.trace("Ensuring archive freshness")
        if time.time() - self._archive["last-update"] > self._archive_time:
            return self._update_archive()
        else:
            return defer.succeed(True)

    def _update_archive(self):
        self.logger.debug("Updating archive...")
        d = treq.get("http://xkcd.com/archive/")
        d.addCallbacks(self._update_archive_callback,
                       self._log_failure,
                       errbackArgs=["Error while updating archive (fetching)"])
        return d

    def _update_archive_callback(self, response):
        self.logger.trace("_update_archive_callback()")
        d = response.content()
        d.addCallbacks(self._update_archive_content_callback,
                       self._log_failure,
                       errbackArgs=["Error while updating archive (reading)"])
        return d

    def _update_archive_content_callback(self, content):
        self.logger.trace("_update_archive_content_callback()")
        with self._archive.mutex:
            try:
                soup = BeautifulSoup(content)
                links = soup.select("#middleContainer a")
                latest = 1
                for link in links:
                    href = None
                    try:
                        href = link["href"]
                        cid = int(href.strip("/"))
                        self._archive["by-id"][cid] = link.text.lower()
                        if cid > latest:
                            latest = cid
                    except Exception as e:
                        self.logger.exception("Error while updating archive "
                                              "cache - unexpected href '%s'" %
                                              href)
                self._archive["latest"] = latest
                self._archive["last-update"] = time.time()
                return defer.succeed(True)
            except Exception as e:
                self.logger.exception("Error while updating archive cache "
                                      "- using old version")
                return defer.fail()
Пример #32
0
class WoWPlugin(plugin.PluginObject):
    commands = None

    def setup(self):
        self.commands = CommandManager()

        self.commands.register_command('armoury',
                                       self.armoury,
                                       self,
                                       'wow.armoury',
                                       aliases=['armory'],
                                       default=True)

    @run_async_threadpool
    def armoury(self, protocol, caller, source, command, raw_args,
                parsed_args):
        """
        armoury [realm] [character name] [region = EU]
        Look up character and returns API data.
        """

        if parsed_args is None:
            parsed_args = raw_args.split()

        # Splits input, builds the API url, and returns the formatted data to
        # user.

        if len(parsed_args) < 2:
            return caller.respond('{CHARS}armoury [realm] [character name] '
                                  '[region = EU] - Look up character and '
                                  'returns API data.')

        realm = parsed_args[0].replace('_', '-').lower()
        charname = parsed_args[1].lower()

        # Sets the default region to EU if none specified.
        if len(parsed_args) < 3:
            region = 'eu'
        else:
            region = parsed_args[2].lower()

        if not re.match(r'^[a-z]{1,3}$', region):
            return caller.respond('The region specified is not a valid '
                                  'region. Valid regions: eu, us, sea, kr, '
                                  'tw.')

        if re.match(r'^[^\d]$', charname) or len(charname) > 18:
            # May not contain digits, repeat the same letter three times,
            # or contain non-word characters.
            # Special characters are permitted, such as ������.
            return caller.respond('The character name is not a valid name.'
                                  'Character names can only contain letters, '
                                  'special characters, and be 18 characters '
                                  'long.')

        if not re.match(r'^[a-z\' _-]{3,32}$', realm):
            # Realm names can have spaces in them, use dashes for this.
            return caller.respond('The realm name is not a valid name. '
                                  'Realm names can only contain letters, '
                                  'dashes, and apostrophes, up to 32 '
                                  'characters')

        region_short = self.wow_region_shortname(region)

        if not region_short:
            return caller.respond(
                'The region \'{}\' does not exist.'.format(region))

        link = 'http://{0}.battle.net/api/wow/character/{1}/{2}'.format(
            region, realm, charname)

        source.respond(self.wow_armoury_data(link))

    def wow_armoury_data(self, link):
        """
        Sends the API request, and returns the data accordingly
        (in json if raw, nicely formatted if not).
        """
        try:
            data = requests.get(link)
        except Exception as e:
            return 'Unable to fetch information for {}. ' + \
                'Does the realm or character exist? ({})'.format(link, str(e))

        return self.wow_armoury_format(data, link)

    def wow_armoury_format(self, data, link):
        """Format armoury data into a human readable string"""

        if data.status_code != 200 and data.status_code != 404:
            # The page returns 404 if the character or realm is not found.
            try:
                data.raise_for_status()
            except Exception as e:
                return ('An error occurred while trying to fetch the data. '
                        '({})'.format(str(e)))

        data = data.json()

        if len(data) == 0:
            return 'Could not find any results.'

        if 'reason' in data:
            # Something went wrong
            # (i.e. realm does not exist, character does not exist).
            return data['reason']

        if 'name' in data:
            niceurl = link.replace('/api/wow/', '/wow/en/') + '/simple'

            try:
                return (
                    '{0} is a level \x0307{1}\x0F {2} {3} on {4} with \x0307'
                    '{5}\x0F achievement points and \x0307{6}\x0F honourable '
                    'kills. Armoury Profile: {7}'.format(
                        data['name'], data['level'],
                        self.wow_get_gender(data['gender']),
                        self.wow_get_class(data['class'], True), data['realm'],
                        data['achievementPoints'], data['totalHonorableKills'],
                        niceurl))
            except Exception as e:
                return (
                    'Unable to fetch information for {}. Does the realm or '
                    'character exist? ({})'.format(niceurl, str(e)))

        return 'An unexpected error occured.'

    def wow_get_gender(self, genderid):
        """Formats a gender ID to a readable gender name"""
        gender = 'unknown'

        if genderid == 0:
            gender = 'male'
        elif genderid == 1:
            gender = 'female'

        return gender

    def wow_get_class(self, classid, colours=False):
        """
        Formats a class ID to a readable name
        Data from http://eu.battle.net/api/wow/data/character/classes
        """
        if colours:
            # Format their colours according to class colours.
            classids = {
                1: '\x0305Warrior\x0F',
                2: '\x0313Paladin\x0F',
                3: '\x0303Hunter\x0F',
                4: '\x0308Rogue\x0F',
                5: 'Priest',
                6: '\x0304Death Knight\x0F',
                7: '\x0310Shaman\x0F',
                8: '\x0311Mage\x0F',
                9: '\x0306Warlock\x0F',
                10: '\x0309Monk\x0F',
                11: '\x0307Druid\x0F'
            }
        else:
            classids = {
                1: 'Warrior',
                2: 'Paladin',
                3: 'Hunter',
                4: 'Rogue',
                5: 'Priest',
                6: 'Death Knight',
                7: 'Shaman',
                8: 'Mage',
                9: 'Warlock',
                10: 'Monk',
                11: 'Druid'
            }

        if classid in classids:
            return classids[classid]
        else:
            return 'unknown'

    def wow_get_race(self, raceid):
        """
        Formats a race ID to a readable race name
        Data from http://eu.battle.net/api/wow/data/character/races
        """
        raceids = {
            1: 'Human',
            2: 'Orc',
            3: 'Dwarf',
            4: 'Night Elf',
            5: 'Undead',
            6: 'Tauren',
            7: 'Gnome',
            8: 'Troll',
            9: 'Goblin',
            10: 'Blood Elf',
            11: 'Draenei',
            22: 'Worgen',
            24: 'Pandaren (neutral)',
            25: 'Pandaren (alliance)',
            26: 'Pandaren (horde)'
        }

        if raceid in raceids:
            return raceids[raceid]
        else:
            return 'unknown'

    def wow_region_shortname(self, region):
        """
        Returns a short region name,
        which functions as battle.net their subdomain (i.e. eu.battle.net)
        """
        validregions = {
            'eu': 'eu',
            'europe': 'eu',
            'us': 'us',
            'sea': 'sea',
            'asia': 'sea',
            'kr': 'kr',
            'korea': 'kr',
            'tw': 'tw',
            'taiwan': 'tw'
        }

        if region in validregions:
            return validregions[region]
        else:
            return False
Пример #33
0
class AuthPlugin(plugin.PluginObject):
    """
    Auth plugin. In charge of logins and permissions.
    """

    config = None
    passwords = None
    permissions = None
    blacklist = None

    commands = None
    events = None
    storage = None

    auth_h = None
    perms_h = None

    def setup(self):
        """
        Called when the plugin is loaded. Performs initial setup.
        """

        reload(auth_handler)
        reload(permissions_handler)

        self.logger.trace(_("Entered setup method."))

        self.storage = StorageManager()
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/auth.yml")
        except Exception:
            self.logger.exception(_("Error loading configuration!"))
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return
        if not self.config.exists:
            self.logger.error(_("Unable to find config/plugins/auth.yml"))
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return

        self.commands = CommandManager()
        self.events = EventManager()

        if self.config["use-permissions"]:
            try:
                self.permissions = self.storage.get_file(self, "data", YAML,
                                                         "plugins/auth/"  # PEP
                                                         "permissions.yml")
            except Exception:
                self.logger.exception(_("Unable to load permissions. They "
                                        "will be unavailable!"))
            else:
                self.perms_h = permissions_handler.permissionsHandler(
                    self, self.permissions)
                result = self.commands.set_permissions_handler(self.perms_h)
                if not result:
                    self.logger.warn(_("Unable to set permissions handler!"))

        if self.config["use-auth"]:
            try:
                self.passwords = self.storage.get_file(self, "data", YAML,
                                                       "plugins/auth/"  # PEP!
                                                       "passwords.yml")
                self.blacklist = self.storage.get_file(self, "data", YAML,
                                                       "plugins/auth/"  # PEP!
                                                       "blacklist.yml")
            except Exception:
                self.logger.exception(_("Unable to load user accounts. They "
                                        "will be unavailable!"))
            else:
                self.auth_h = auth_handler.authHandler(self, self.passwords,
                                                       self.blacklist)
                result = self.commands.set_auth_handler(self.auth_h)
                if not result:
                    self.logger.warn(_("Unable to set auth handler!"))

        self.logger.debug(_("Registering commands."))
        self.commands.register_command("login", self.login_command,
                                       self, "auth.login", default=True)
        self.commands.register_command("logout", self.logout_command,
                                       self, "auth.login", default=True)
        self.commands.register_command("register", self.register_command,
                                       self, "auth.register", default=True)
        self.commands.register_command("passwd", self.passwd_command, self,
                                       "auth.passwd", default=True)

        self.events.add_callback("PreCommand", self, self.pre_command, 10000)

    def pre_command(self, event=PreCommand):
        """
        Pre-command hook to remove passwords from the log output.
        """

        self.logger.trace(_("Command: %s") % event.command)
        if event.command.lower() in ["login", "register"]:
            if len(event.args) >= 2:
                split_ = event.printable.split("%s " % event.command)
                second_split = split_[1].split()
                second_split[1] = _("[REDACTED]")
                split_[1] = " ".join(second_split)
                donestr = "%s " % event.command
                done = donestr.join(split_)
                event.printable = done
        elif event.command.lower() == "passwd":
            split_ = event.printable.split("%s " % event.command)
            second_split = split_[1].split()

            dsplit = []
            for x in second_split:
                dsplit.append(_("[REDACTED]"))

            split_[1] = " ".join(dsplit)
            donestr = "%s " % event.command
            done = donestr.join(split_)
            event.printable = done

    def login_command(self, protocol, caller, source, command, raw_args,
                      parsed_args):
        """
        Command handler for the login command - for logging users in.
        """

        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 2:
            caller.respond(__("Usage: {CHARS}login <username> <password>"))
        else:
            if self.auth_h.authorized(caller, source, protocol):
                caller.respond(__("You're already logged in. "
                                  "Try logging out first!"))
                return
            username = args[0]
            password = args[1]

            result = self.auth_h.login(caller, protocol, username, password)
            if not result:
                self.logger.warn(_("%s failed to login as %s")
                                 % (caller.nickname, username))
                caller.respond(__("Invalid username or password!"))
            else:
                self.logger.info(_("%s logged in as %s")
                                 % (caller.nickname, username))
                caller.respond(__("You are now logged in as %s.")
                               % username)

    def logout_command(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        """
        Command handler for the logout command - for logging users out.
        """

        if self.auth_h.authorized(caller, source, protocol):
            self.auth_h.logout(caller, protocol)
            caller.respond(__("You have been logged out successfully."))
        else:
            caller.respond(__("You're not logged in."))

    def register_command(self, protocol, caller, source, command, raw_args,
                         parsed_args):
        """
        Command handler for the register command - for creating new user
        accounts.
        """

        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 2:
            caller.respond(__("Usage: {CHARS}register <username> <password>"))
            return
        username = args[0]
        password = args[1]

        if isinstance(source, Channel):
            source.respond(__("You can't create an account in a channel."))
            caller.respond(__("Don't use this command in a channel!"))
            caller.respond(__("You should only use it in a private message."))
            caller.respond(__("For your security, the password you used has "
                              "been blacklisted."))
            self.auth_h.blacklist_password(password, username)
            return

        if self.auth_h.user_exists(username):
            caller.respond(__("That username already exists!"))
            return

        if self.auth_h.password_backlisted(password, username):
            caller.respond(__("That password has been blacklisted. "
                              "Try another!"))
            return

        if self.auth_h.create_user(username, password):
            caller.respond(__("Your account has been created and you will now "
                              "be logged in. Thanks for registering!"))
            self.perms_h.create_user(username)
            self.login_command(caller, source, [username, password], protocol,
                               raw_args, parsed_args)
        else:
            caller.respond(__("Something went wrong when creating your "
                              "account! You should ask the bot operators "
                              "about this."))

    def passwd_command(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        """
        Command handler for the passwd command - for changing passwords.
        """

        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 2:
            caller.respond(__("Usage: {CHARS}passwd <old password> "
                              "<new password>"))
            return
        if not self.auth_h.authorized(caller, source, protocol):
            caller.respond(__("You must be logged in to change your "
                              "password."))
            return

        username = caller.auth_name

        old = args[0]
        new = args[1]

        if self.auth_h.password_backlisted(new, username):
            caller.respond(__("That password has been blacklisted. Try "
                              "another!"))
            return

        if self.auth_h.change_password(username, old, new):
            caller.respond(__("Your password has been changed successfully."))
        else:
            caller.respond(__("Old password incorrect - please try again!"))
            self.logger.warn(_("User %s failed to change the password for %s")
                             % (caller, username))

    def get_auth_handler(self):
        """
        API function for getting the auth handler.

        This will return None if no handler is registered.
        """

        if self.config["use-auth"]:
            return self.auth_h
        return None

    def get_permissions_handler(self):
        """
        API function for getting the permissions handler.

        This will return None if no handler is registered.
        """
        if self.config["use-permissions"]:
            return self.perms_h
        return None

    def deactivate(self):
        """
        Called when the plugin is deactivated. Does nothing right now.
        """
        if self.config["use-auth"]:
            if isinstance(
                    self.commands.auth_handler, auth_handler.authHandler
            ):
                self.commands.auth_handler = None

        if self.config["use-permissions"]:
            if isinstance(
                    self.commands.perm_handler,
                    permissions_handler.permissionsHandler
            ):
                self.commands.perm_handler = None
Пример #34
0
class DicePlugin(plugin.PluginObject):

    _MODS_REGEX = re.compile(
        r"(?P<sort>s)|(?P<total>t)|(?:\^(?P<high>\d+))|(?:v(?P<low>\d+))")
    _ROLL_REGEX = re.compile(
        r"^(?P<dice>\d+)?(?:d(?P<sides>\d+))?(?P<mods>(?:t|s|\^\d+|v\d+)*)?$")

    _commands = None
    _config = None
    _storage = None

    def setup(self):
        ### Grab important shit
        self._commands = CommandManager()
        self._storage = StorageManager()

        ### Initial config load
        try:
            self._config = self._storage.get_file(self, "config", YAML,
                                                  "plugins/dice.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/dice.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Register commands
        self._commands.register_command("roll",
                                        self.roll_cmd,
                                        self,
                                        "dice.roll",
                                        aliases=["dice"],
                                        default=True)

    @property
    def max_dice(self):
        return self._config["max_dice"]

    @property
    def max_sides(self):
        return self._config["max_sides"]

    @property
    def default_dice(self):
        return self._config["default_dice"]

    @property
    def default_sides(self):
        return self._config["default_sides"]

    def _respond(self, target, msg):
        target.respond("Dice: %s" % msg)

    def roll_cmd(self, protocol, caller, source, command, raw_args,
                 parsed_args):
        try:
            result = self.roll(raw_args)
            self._respond(source, str(result))
        except DiceFormatError:
            self._respond(caller, "Usage: {CHARS}%s [roll info]" % command)
        except NotEnoughDice:
            self._respond(caller, "Too many dice.")
        except NotEnoughSides:
            self._respond(caller, "Too many sides.")

    def roll(self, description=""):
        match = self._ROLL_REGEX.match(description.strip())
        if match is None:
            raise DiceFormatError("Invalid dice roll expression")

        parts = match.groupdict()
        dice = int(parts["dice"] or self.default_dice)
        sides = int(parts["sides"] or self.default_sides)
        mods = parts["mods"] or ""

        if dice > self.max_dice:
            raise NotEnoughDice()
        if sides > self.max_sides:
            raise NotEnoughSides()

        # Roll
        result = [random.randint(1, sides) for x in xrange(dice)]
        return self.apply_mods(result, mods)

    def apply_mods(self, numbers, mods):
        pos = 0
        while True:
            match = self._MODS_REGEX.match(mods, pos=pos)
            if match is None:
                break
            if match.lastgroup == "sort":
                numbers.sort()
                pos += 1
            elif match.lastgroup == "total":
                numbers = [sum(numbers)]
                pos += 1
            elif match.lastgroup == "high":
                count = match.group("high")
                numbers.sort()
                numbers = numbers[-int(count):]
                pos += len(count) + 1
            elif match.lastgroup == "low":
                count = match.group("low")
                numbers.sort()
                numbers = numbers[:int(count)]
                pos += len(count) + 1
        return numbers
Пример #35
0
class LastseenPlugin(plugin.PluginObject):

    commands = None
    events = None
    storage = None

    data = None  # SQLite for a change

    def setup(self):
        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()
        self.data = self.storage.get_file(
            self,
            "data",
            DBAPI,
            "sqlite3:data/plugins/lastseen/users.sqlite",
            "data/plugins/lastseen/users.sqlite",
            check_same_thread=False
        )

        self.data.runQuery("CREATE TABLE IF NOT EXISTS users ("
                           "user TEXT, "
                           "protocol TEXT, "
                           "at INTEGER)")

        self.commands.register_command("seen", self.seen_command, self,
                                       "seen.seen", default=True)

        # General events

        self.events.add_callback("PreMessageReceived", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_source_caller])
        self.events.add_callback("PreCommand", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_source_caller])
        self.events.add_callback("NameChanged", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("UserDisconnected", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_user_caller])

        # Mumble events

        self.events.add_callback("Mumble/UserRemove", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserJoined", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserMoved", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserSelfMuteToggle", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserSelfDeafToggle", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_user_caller])
        self.events.add_callback("Mumble/UserRecordingToggle", self,
                                 self.event_handler, 0, cancelled=True,
                                 extra_args=[self.event_user_caller])

    def _get_user_txn(self, txn, user, protocol):
        user = user.lower()
        txn.execute("SELECT * FROM users WHERE user=? AND protocol=?",
                    (user, protocol))
        r = txn.fetchone()
        return r

    def _get_user_callback(self, result, user, protocol, source):
        if result is None:
            source.respond("User '%s' not found." % user)
        else:
            then = math.floor(result[2])
            now = math.floor(time.time())
            seconds = now - then

            m, s = divmod(seconds, 60)
            h, m = divmod(m, 60)
            d, h = divmod(h, 24)

            s = int(s)
            m = int(m)
            h = int(h)
            d = int(d)

            if (s + m + h + d) == 0:
                source.respond("'%s' was seen just now!" % user)
            else:
                constructed = "'%s' was seen" % user
                to_append = []
                if d > 0:
                    to_append.append("%s days" % d)
                if h > 0:
                    to_append.append("%s hours" % h)
                if m > 0:
                    to_append.append("%s minutes" % m)
                if s > 0:
                    to_append.append("%s seconds" % s)

                length = len(to_append)
                i = 1

                for x in to_append:
                    if length - i == 0:
                        if i != 1:
                            constructed += " and %s" % x
                            i += 1
                            continue
                    if i != 1:
                        constructed += ", %s" % x
                    else:
                        constructed += " %s" % x
                    i += 1

                constructed += " ago."

                source.respond(constructed)

    def _get_user_callback_fail(self, failure, user, protocol, source):
        source.respond("Error while finding user %s: %s" % (user, failure))

    def get_user(self, user, protocol):
        return self.data.runInteraction(self._get_user_txn, user, protocol)

    def _insert_or_update_user(self, txn, user, protocol):
        user = user.lower()
        txn.execute("SELECT * FROM users WHERE user=? AND protocol=?",
                    (user, protocol))
        r = txn.fetchone()

        now = time.time()

        if r is None:
            txn.execute(
                "INSERT INTO users VALUES (?, ?, ?)",
                (user,
                 protocol,
                 now)
            )
            return False
        else:
            txn.execute(
                "UPDATE users SET at=? WHERE user=? AND protocol=?",
                (now,
                 user,
                 protocol)
            )
            return True

    def insert_or_update_user(self, user, protocol):
        self.data.runInteraction(self._insert_or_update_user, user, protocol)

    def seen_command(self, protocol, caller, source, command, raw_args,
                     parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if not args:
            caller.respond("Usage: {CHARS}seen <username>")
        else:
            user = "******".join(args)

            if user.lower() == protocol.ourselves.nickname.lower():
                source.respond("I'm right here, smartass.")
                return

            if user.lower() == caller.nickname.lower():
                source.respond("Having a bit of an out-of-body experience, "
                               "%s?" % caller.nickname)
                return

            d = self.get_user(user, protocol.name)

            d.addCallbacks(self._get_user_callback,
                           self._get_user_callback_fail,
                           callbackArgs=(user.lower(), protocol.name, source),
                           errbackArgs=(user.lower(), protocol.name, source))

    def event_handler(self, event, handler):
        """
        This is a generic function so that other plugins can catch events
        and cause a user's last seen value to update.

        The handler should return (username, protocol name) as a tuple,
        or a list of tuples if it needs to do more than one update.
        """
        data = handler(event)
        if not isinstance(data, list):
            data = [data]

        for element in data:
            user, proto = element
            self.insert_or_update_user(user, proto)

    def event_source_caller(self, event):
        user = event.source.nickname
        proto = event.caller.name

        return user, proto

    def event_user_caller(self, event):
        user = event.user.nickname
        proto = event.caller.name

        return user, proto
Пример #36
0
class ReplPlugin(plugin.PluginObject):
    commands = None
    events = None
    storage = None

    config = None

    proto = None
    channel = None
    formatting = None

    def __init__(self):
        self.protocol_events = {
            "general": [
                # This is basically just *args.
                ["MessageReceived", self, self.message_received, 0]
            ],
            "inter": [
                ["Inter/PlayerConnected", self,
                    self.inter_player_connected, 0],
                ["Inter/PlayerDisconnected", self,
                    self.inter_player_disconnected, 0],
                ["Inter/ServerConnected", self,
                    self.inter_server_connected, 0],
                ["Inter/ServerDisconnected", self,
                    self.inter_server_disconnected, 0]
            ]
        }

        super(ReplPlugin, self).__init__()

    def setup(self):
        self.logger.trace("Entered setup method.")

        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()

        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/inter.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return

        if not self.config.exists:
            self.logger.error("Unable to find config/plugins/inter.yml")
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return

        self.config.add_callback(self.reload)

        self.commands.register_command("players", self.players_command, self,
                                       "inter.players", default=True)

        self.events.add_callback("ReactorStarted", self, self.first_load, 0)

    def first_load(self, event):
        if not self.reload():
            self.logger.error(_("Disabling.."))
            self._disable_self()

    def reload(self):
        self.events.remove_callbacks_for_plugin(self.info.name)
        proto = self.factory_manager.get_protocol(self.config["protocol"])

        if proto is None:
            self.logger.error(_("Unknown protocol: %s")
                              % self.config["protocol"])
            return False

        if proto.TYPE == "inter":
            self.logger.error(_("You cannot relay between two Inter "
                                "protocols!"))
            return False

        self.proto = proto
        self.channel = self.config["channel"]
        self.formatting = self.config["formatting"]

        for event in self.protocol_events["general"]:
            self.events.add_callback(*event)

        for event in self.protocol_events["inter"]:
            self.events.add_callback(*event)

        if proto.TYPE in self.protocol_events:
            for event in self.protocol_events[proto.TYPE]:
                self.events.add_callback(*event)

        return True

    def get_inters(self):
        inters = {}

        for key in self.factory_manager.factories.keys():
            if self.factory_manager.get_protocol(key).TYPE == "inter":
                inters[key] = self.factory_manager.get_protocol(key)

        return inters

    def players_command(self, protocol, caller, source, command, raw_args,
                        args):
        if protocol.TYPE == "inter":
            caller.respond("This command cannot be used via Inter.")
            return

        inters = self.get_inters()

        if len(inters) < 1:
            caller.respond("No Inter protocols were found.")
        elif len(inters) == 1:
            servers = inters[inters.keys()[0]].inter_servers

            for key in servers.keys():
                formatting = self.formatting["player"]["list"]

                done = formatting["message"]
                _done = []

                for x in servers[key]:
                    _done.append(str(x))

                if len(_done):
                    players = formatting["join"].join(_done)
                else:
                    players = "No players online."

                done = done.replace("{SERVER}", key)
                done = done.replace("{PLAYERS}", players)
                source.respond(done)
        else:
            if len(args) < 1:
                caller.respond("Usage: {CHARS}%s <inter server>")
                caller.respond("Servers: %s" % ", ".join(inters.keys()))
                return

            srv = args[1]
            if srv not in inters:
                caller.respond("Unknown inter server: %s" % srv)
                caller.respond("Servers: %s" % ", ".join(inters.keys()))
                return

            servers = inters[srv].inter_servers

            for key in servers.keys():
                formatting = self.formatting["player"]["list"]

                done = formatting["message"]
                _done = []

                for x in servers[key]:
                    _done.append(str(x))

                if len(_done):
                    players = formatting["join"].join(_done)
                else:
                    players = "No players online."

                done = done.replace("{SERVER}", key)
                done = done.replace("{PLAYERS}", players)
                source.respond(done)

    def message_received(self, event=MessageReceived):
        caller = event.caller
        user = event.source
        message = event.message
        target = event.target

        if target is None:
            return
        if caller is None:
            return

        if isinstance(caller, Protocol):
            if caller.TYPE == "inter":
                f_str = self.formatting["player"]["message"]

                f_str = f_str.replace("{SERVER}", user.server)
                f_str = f_str.replace("{USER}", str(user))
                f_str = f_str.replace("{MESSAGE}", message)

                self.proto.send_msg(self.channel, f_str)
            else:
                if caller.name == self.proto.name:
                    if target.name.lower() == self.channel.lower():
                        inters = self.get_inters()

                        for proto in inters.values():
                            proto.send_msg_other(user, message)

    def inter_server_connected(self, event=InterServerConnected):
        f_str = self.formatting["server"]["connected"]
        f_str = f_str.replace("{SERVER}", event.name)

        self.proto.send_msg(self.channel, f_str)

    def inter_server_disconnected(self, event=InterServerDisonnected):
        f_str = self.formatting["server"]["disconnected"]
        f_str = f_str.replace("{SERVER}", event.name)

        self.proto.send_msg(self.channel, f_str)

    def inter_player_connected(self, event=InterPlayerConnected):
        f_str = self.formatting["player"]["connected"]
        f_str = f_str.replace("{SERVER}", event.user.server)
        f_str = f_str.replace("{USER}", str(event.user))

        self.proto.send_msg(self.channel, f_str)

    def inter_player_disconnected(self, event=InterPlayerDisonnected):
        f_str = self.formatting["player"]["disconnected"]
        f_str = f_str.replace("{SERVER}", event.user.server)
        f_str = f_str.replace("{USER}", str(event.user))

        self.proto.send_msg(self.channel, f_str)
Пример #37
0
class EightBallPlugin(plugin.PluginObject):

    commands = None
    storage = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self._config = self.storage.get_file(self, "config", YAML,
                                                 "plugins/8ball.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/8ball.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Setup some stuff
        self._random = random.Random()
        self._question_regex = re.compile("[\W_]+")

        ### Register commands
        self.commands.register_command("8ball",
                                       self.eight_ball_cmd,
                                       self,
                                       "8ball.8ball",
                                       default=True)

    def reload(self):
        try:
            self._config.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        return True

    @property
    def yes_chance(self):
        return self._config["chances"]["yes"]

    @property
    def no_chance(self):
        return self._config["chances"]["yes"]

    @property
    def maybe_chance(self):
        return 100 - self.yes_chance - self.no_chance

    @property
    def same_answers(self):
        # Default False to keep old behaviour
        return self._config.get("same_answers", False)

    def eight_ball_cmd(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        source.respond("[8ball] " + self.get_response(raw_args))

    def get_response(self, question=None):
        if self.same_answers and question is not None:
            try:
                qseed = question.encode("ascii", "ignore").strip().lower()
                qseed = self._question_regex.sub("", qseed)
                self.logger.debug("qseed: %s" % qseed)
                self._random.seed(qseed)
            except Exception:
                self.logger.exception(
                    "Error while reducing question. Please alert the author.")
        # Use self._random so that we can seed it (above) to always get the
        # same answer.
        choice = self._random.randint(1, 100)
        reply_type = "maybe"
        if choice <= self.yes_chance:
            reply_type = "yes"
        elif choice <= self.yes_chance + self.no_chance:
            reply_type = "no"
        # We don't want to use the re-seeded random here or we'll always get
        # the exact same response.
        return random.choice(self._config["responses"][reply_type])
Пример #38
0
class WolframPlugin(plugin.PluginObject):

    app = None

    config = None
    commands = None
    storage = None

    def setup(self):
        self.logger.trace("Entered setup method.")
        self.storage = StorageManager()
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/wolfram.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling..")
            self._disable_self()
            return
        if not self.config.exists:
            self.logger.error("Unable to find config/plugins/wolfram.yml")
            self.logger.error("Disabling..")
            self._disable_self()
            return

        self.commands = CommandManager()

        self._load()
        self.config.add_callback(self._load)

        self.commands.register_command("wolfram", self.wolfram_command, self,
                                       "wolfram.wolfram", default=True)

    def _load(self):
        self.app = wolframalpha.Client(self.config["app_id"])

    @run_async_threadpool
    def wolfram_command(self, protocol, caller, source, command, raw_args,
                        parsed_args):
        target = caller
        if isinstance(source, Channel):
            target = source

        if len(raw_args):
            try:
                res = self.app.query(raw_args)
                first = next(res.results)
                text = first.text.replace("\n", " ")

                target.respond(text)
            except Exception as e:
                if len(str(e)):
                    raise e
                else:
                    target.respond("No answer was found for your query.")

        else:
            caller.respond("Usage: .wolfram <query>")

    def deactivate(self):
        pass
Пример #39
0
class DrunkPlugin(plugin.PluginObject):

    commands = None
    config = None
    storage = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/drunkoctopus.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self.config.exists:
            self.logger.error("Unable to find config/plugins/drunkoctopus.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Create vars and stuff
        self._sobering_call = None
        self._drunktalk = DrunkTalk()

        ### Load options from config
        self._load()

        self.config.add_callback(self._load)

        ### Register events and commands

        self.events.add_callback("MessageSent", self,
                                 self.outgoing_message_handler, 1)
        self.commands.register_command("drunkenness",
                                       self.drunkenness_command,
                                       self,
                                       "drunkoctopus.drunkenness",
                                       default=True)
        self.commands.register_command("drink", self.drink_command, self,
                                       "drunkoctopus.drink")

    def reload(self):
        try:
            self.config.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        return True

    def _load(self):
        self._drunktalk.drunkenness = self.config["drunkenness"]
        self._cooldown_enabled = self.config["cooldown"]["enabled"]
        self._cooldown_time = self.config["cooldown"]["time"]
        self._cooldown_amount = self.config["cooldown"]["amount"]
        self._drinks = self.config["drinks"]

        # Sort out the sobering deferred as necessary
        if self._cooldown_enabled:
            if self._sobering_call is None:
                self.logger.trace("Starting sobering call due to config "
                                  "change")
                self._sobering_call = reactor.callLater(
                    self._cooldown_time, self._sober_up)
        else:
            if self._sobering_call is not None:
                self.logger.trace("Cancelling sobering call due to config "
                                  "change")
                self._sobering_call.cancel()

    def _sober_up(self):
        self.logger.trace("Sobering up")
        drunk = self._drunktalk.drunkenness
        drunk -= self._cooldown_amount
        if drunk < 0:
            drunk = 0
        self._drunktalk.drunkenness = drunk
        if self._cooldown_enabled:
            reactor.callLater(self._cooldown_time, self._sober_up)

    def drunkenness_command(self, protocol, caller, source, command, raw_args,
                            parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) == 0:
            caller.respond("Drunkenness level: %s" %
                           self._drunktalk.drunkenness)
            return
        elif len(args) == 1:
            try:
                new_drunk = int(args[0])
                self._drunktalk.drunkenness = new_drunk
                caller.respond("New drunkenness level: %s" %
                               self._drunktalk.drunkenness)
                return
            except:
                caller.respond("Invalid drunkenness level (use without "
                               "arguments for usage)")
        else:
            caller.respond("Usage: {CHARS}drunkenness [percent level]")

    def drink_command(self, protocol, caller, source, command, raw_args,
                      parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) == 0:
            caller.respond("Usage: {CHARS}drink <type of drink>")
            return
        drink = " ".join(args)
        drinkl = drink.lower()
        if drinkl in self._drinks:
            protocol.send_action(source, "drinks {}".format(drink))
            self._drunktalk.drunkenness += self._drinks[drinkl]
        else:
            caller.respond("I don't have any of that.")

    def outgoing_message_handler(self, event):
        """
        :type event: MessageSent
        """
        self.logger.trace("RECEIVED %s EVENT: %s" %
                          (event.type, event.message))
        event.message = self._drunktalk.drunk_typing(event.message)
Пример #40
0
class BrainfuckPlugin(plugin.PluginObject):

    commands = None
    config = None
    storage = None

    @property
    def timeout(self):
        return self.config["timeout"]

    def setup(self):
        self.commands = CommandManager()
        self.storage = StorageManager()

        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/brainfuck.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling..")
            self._disable_self()
            return
        if not self.config.exists:
            self.logger.error("Unable to find config/plugins/brainfuck.yml")
            self.logger.error("Disabling..")
            self._disable_self()
            return

        self.commands.register_command("bf", self.bf_command, self,
                                       "brainfuck.exec", default=True)

    def reload(self):
        try:
            self.config.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        return True

    def bf_command(self, protocol, caller, source, command, raw_args,
                   parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) < 1:
            caller.respond("Usage: {CHARS}bf <brainfuck program>")
            return

        start_time = datetime.now()
        ended_early = False
        code = args[0]

        i, j = 0, 0
        l = -1
        loops = [0] * 16
        buf = [0] * 30000
        out = ''
        buf_max = 0
        while j < len(code):
            if (datetime.now() - start_time).microseconds / 1000 \
                    >= self.timeout:
                ended_early = True
                break
            if code[j] == '+':
                buf[i] += 1
            elif code[j] == '-':
                buf[i] -= 1
            elif code[j] == '>':
                i += 1
                buf_max = max(buf_max, i)
            elif code[j] == '<':
                i = abs(i - 1)
            elif code[j] == '[':
                l += 1
                loops[l] = j
            elif code[j] == ']':
                if buf[i] == 0:
                    j += 1
                    loops[l] = 0
                    l -= 1
                    continue
                else:
                    j = loops[l]
            elif code[j] == '.':
                out += chr(buf[i])
            j += 1

        if ended_early:
            if isinstance(source, Channel):
                source.respond("KILLED | %s %s" % (buf[:buf_max], out))
            else:
                caller.respond("KILLED | %s %s" % (buf[:buf_max], out))
        else:
            if isinstance(source, Channel):
                source.respond("RESULT | %s %s" % (buf[:buf_max], out))
            else:
                caller.respond("RESULT | %s %s" % (buf[:buf_max], out))
Пример #41
0
class DomainrPlugin(plugin.PluginObject):

    _commands = None

    def setup(self):
        ### Grab important shit
        self._commands = CommandManager()

        ### Load stuff
        self._load()

        ### Register commands
        self._commands.register_command("domainrsearch",
                                        self.search_cmd,
                                        self,
                                        "domainr.search",
                                        aliases=["domainr"],
                                        default=True)
        self._commands.register_command("domainrinfo",
                                        self.info_cmd,
                                        self,
                                        "domainr.info",
                                        default=True)

    def reload(self):
        self._load()
        return True

    def _load(self):
        self.api = Domainr()

    def _respond(self, target, msg):
        """
        Convenience function for responding to something with a prefix. Not
        only does this avoid confusion, but it also stops people being able to
        execute other bot commands in the case that we need to put any
        user-supplied data at the start of a message.
        """
        target.respond("Domainr: " + msg)

    def _msg(self, protocol, target, *args):
        """
        Sends messages different ways, depending on protocol can_flood setting.
        If can_flood, each arg (and item in arg, if arg is list) is sent on its
        own line.
        If not can_flood, all args and join with a space, and args that are a
        list are joined with comma-space.
        Example:
            Call:
                _msg(proto, target, "Foo:", ["bar", "baz", "quux"])
            Can flood output:
                Foo:
                bar
                baz
                quux
            Cannot flood output:
            Foo: bar, baz, quux
        :param protocol:
        :param target:
        :param args:
        :return:
        """
        if protocol.can_flood:
            for msg in args:
                if isinstance(msg, list):
                    for m in msg:
                        self._respond(target, m)
                else:
                    self._respond(target, msg)
        else:
            to_send = []
            for msg in args:
                if isinstance(msg, list):
                    msg = ", ".join(msg)
                to_send.append(msg)
            self._respond(target, " ".join(to_send))

    def search_cmd(self, protocol, caller, source, command, raw_args,
                   parsed_args):
        if len(raw_args) == 0:
            caller.respond("Usage: {CHARS}%s <query>" % command)
            return
        else:
            try:
                deferred = self.api.search(raw_args)
                deferred.addCallbacks(
                    lambda r: self._search_cmd_result(protocol, caller, source,
                                                      r),
                    lambda f: self._cmd_error(caller, f))
            except RateLimitExceededError:
                caller.respond("Command on cooldown - try again later")

    def info_cmd(self, protocol, caller, source, command, raw_args,
                 parsed_args):
        if len(raw_args) == 0:
            caller.respond("Usage: {CHARS}%s <domain>" % command)
            return
        else:
            try:
                deferred = self.api.info(raw_args)
                deferred.addCallbacks(
                    lambda r: self._info_cmd_result(protocol, caller, source, r
                                                    ),
                    lambda f: self._cmd_error(caller, f))
            except RateLimitExceededError:
                caller.respond("Command on cooldown - try again later")

    def _search_cmd_result(self, protocol, caller, source, result):
        """
        Receives the API response for search
        """
        loud = self._commands.perm_handler.check("domainr.search.loud", caller,
                                                 source, protocol)
        target = None
        if loud:
            target = source
        else:
            target = caller
        try:
            msgs = []
            for res in result["results"]:
                self.logger.trace(res)
                msg = u"%s%s - %s" % (res["domain"], res["path"],
                                      res["availability"])
                msgs.append(msg)
            self._msg(protocol, target, msgs)
        except:
            self.logger.exception("Please tell the developer about this error")

    def _info_cmd_result(self, protocol, caller, source, result):
        """
        Receives the API response for info
        """
        loud = self._commands.perm_handler.check("domainr.info.loud", caller,
                                                 source, protocol)
        target = None
        if loud:
            target = source
        else:
            target = caller
        try:
            msgs = []
            msgs.append(u"Availability: %s" % result["availability"])
            if result["availability"] in (Domainr.AVAILABLE, Domainr.MAYBE):
                msgs.append(u"Register: %s" % result["register_url"])
            self._msg(protocol, target, msgs)
        except:
            self.logger.exception("Please tell the developer about this error")

    def _cmd_error(self, caller, failure):
        """
        :type failure: twisted.python.failure.Failure
        """
        # Some errors will be caused by user input
        if failure.check(DomainrError):
            self._respond(caller, failure.value.message)
        else:
            self.logger.debug("Error while fetching info",
                              exc_info=(failure.type, failure.value,
                                        failure.tb))
            caller.respond("There was an error while contacting Domainr - "
                           "please alert a bot admin or try again later")
Пример #42
0
class W3validatorPlugin(plugin.PluginObject):
    commands = None

    def setup(self):
        self.commands = CommandManager()

        self.commands.register_command(
            "w3validate", self.w3validate, self, "w3.w3validate", aliases=["validate", "valid"], default=True
        )

    def w3validate(self, protocol, caller, source, command, raw_args, parsed_args):
        """Syntax: w3validate [website to validate]"""

        if parsed_args is None:
            parsed_args = raw_args.split()

        if len(parsed_args) < 1:
            return caller.respond("{CHARS}w3validate [url] [...] - Validate a " "website using validator.w3.org")

        if len(parsed_args) > 3:
            caller.respond(
                "You cannot request more than three websites "
                "to be validated at a time, to prevent "
                "flooding the validator."
            )

        # If we're sending too many requests, exit the loop.
        # Also used to calculate how long we should wait.
        itter = 0

        for website in parsed_args:
            reactor.callLater(itter, self.w3_validate_url, website, source)

            itter += 1
            if itter > 2:
                break

    @run_async_threadpool
    def w3_validate_url(self, website, source):
        """Retrieve and return the validity of a website"""
        val_link = "http://validator.w3.org/check?uri=" + website

        r = requests.get(val_link)
        valid = r.headers["x-w3c-validator-status"].lower()
        col_valid = self.w3_colour_status(valid)

        if valid == "abort":
            # Possibility that the URL is invalid
            return source.respond(
                '[{}] The validation for "{}" '
                "\x0314Aborted\x0F unexpectedly, "
                "is this a valid URI?".format(col_valid, website)
            )

        warnings = r.headers["x-w3c-validator-warnings"]
        errors = r.headers["x-w3c-validator-errors"]
        recursion = r.headers["x-w3c-validator-recursion"]

        errstring = ""
        if errors != "0":
            errstring += "Errors: \x0304{}\x0F, ".format(errors)
        if warnings != "0":
            errstring += "Warnings: \x0307{}\x0F, ".format(warnings)
        if recursion != "1":
            # Level of recursion is one by default.
            errstring += "Recursion: \x0314{}\x0F, ".format(recursion)

        # Remove the ", " from the string.
        errstring = errstring[:-2]

        if errstring:
            return source.respond(
                "[{}] The website {} is {}. {} ({})".format(col_valid, website, col_valid, errstring, val_link)
            )

        return source.respond("[{}] The website {} is {}. ({})".format(col_valid, website, col_valid, val_link))

    def w3_colour_status(self, status):
        """
        Colourise the status header,
        green is valid, red is invalid, the rest is gray
        """
        col_valid = ""

        valid = status.lower()
        if valid == "valid":
            col_valid = "\x0303Valid\x0F"
        elif valid == "invalid":
            col_valid = "\x0304Invalid\x0F"
        else:
            col_valid = "\x0314{}\x0F".format(status[0].upper() + status[1:])

        return col_valid
Пример #43
0
class DrunkPlugin(plugin.PluginObject):

    commands = None
    config = None
    storage = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/drunkoctopus.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self.config.exists:
            self.logger.error("Unable to find config/plugins/drunkoctopus.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Create vars and stuff
        self._sobering_call = None
        self._drunktalk = DrunkTalk()

        ### Load options from config
        self._load()

        self.config.add_callback(self._load)

        ### Register events and commands

        self.events.add_callback("MessageSent",
                                 self,
                                 self.outgoing_message_handler,
                                 1)
        self.commands.register_command("drunkenness",
                                       self.drunkenness_command,
                                       self,
                                       "drunkoctopus.drunkenness",
                                       default=True)
        self.commands.register_command("drink",
                                       self.drink_command,
                                       self,
                                       "drunkoctopus.drink")

    def reload(self):
        try:
            self.config.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        return True

    def _load(self):
        self._drunktalk.drunkenness = self.config["drunkenness"]
        self._cooldown_enabled = self.config["cooldown"]["enabled"]
        self._cooldown_time = self.config["cooldown"]["time"]
        self._cooldown_amount = self.config["cooldown"]["amount"]
        self._drinks = self.config["drinks"]

        # Sort out the sobering deferred as necessary
        if self._cooldown_enabled:
            if self._sobering_call is None:
                self.logger.trace("Starting sobering call due to config "
                                  "change")
                self._sobering_call = reactor.callLater(self._cooldown_time,
                                                        self._sober_up)
        else:
            if self._sobering_call is not None:
                self.logger.trace("Cancelling sobering call due to config "
                                  "change")
                self._sobering_call.cancel()

    def _sober_up(self):
        self.logger.trace("Sobering up")
        drunk = self._drunktalk.drunkenness
        drunk -= self._cooldown_amount
        if drunk < 0:
            drunk = 0
        self._drunktalk.drunkenness = drunk
        if self._cooldown_enabled:
            reactor.callLater(self._cooldown_time, self._sober_up)

    def drunkenness_command(self, protocol, caller, source, command, raw_args,
                            parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) == 0:
            caller.respond("Drunkenness level: %s" %
                           self._drunktalk.drunkenness)
            return
        elif len(args) == 1:
            try:
                new_drunk = int(args[0])
                self._drunktalk.drunkenness = new_drunk
                caller.respond("New drunkenness level: %s" %
                               self._drunktalk.drunkenness)
                return
            except:
                caller.respond("Invalid drunkenness level (use without "
                               "arguments for usage)")
        else:
            caller.respond("Usage: {CHARS}drunkenness [percent level]")

    def drink_command(self, protocol, caller, source, command, raw_args,
                      parsed_args):
        args = raw_args.split()  # Quick fix for new command handler signature
        if len(args) == 0:
            caller.respond("Usage: {CHARS}drink <type of drink>")
            return
        drink = " ".join(args)
        drinkl = drink.lower()
        if drinkl in self._drinks:
            protocol.send_action(source, "drinks {}".format(drink))
            self._drunktalk.drunkenness += self._drinks[drinkl]
        else:
            caller.respond("I don't have any of that.")

    def outgoing_message_handler(self, event):
        """
        :type event: MessageSent
        """
        self.logger.trace("RECEIVED %s EVENT: %s" % (event.type,
                                                     event.message))
        event.message = self._drunktalk.drunk_typing(event.message)
Пример #44
0
class DomainrPlugin(plugin.PluginObject):

    _commands = None

    def setup(self):
        ### Grab important shit
        self._commands = CommandManager()

        ### Load stuff
        self._load()

        ### Register commands
        self._commands.register_command("domainrsearch",
                                        self.search_cmd,
                                        self,
                                        "domainr.search",
                                        aliases=[
                                            "domainr"
                                        ], default=True)
        self._commands.register_command("domainrinfo",
                                        self.info_cmd,
                                        self,
                                        "domainr.info", default=True)

    def reload(self):
        self._load()
        return True

    def _load(self):
        self.api = Domainr()

    def _respond(self, target, msg):
        """
        Convenience function for responding to something with a prefix. Not
        only does this avoid confusion, but it also stops people being able to
        execute other bot commands in the case that we need to put any
        user-supplied data at the start of a message.
        """
        target.respond("Domainr: " + msg)

    def _msg(self, protocol, target, *args):
        """
        Sends messages different ways, depending on protocol can_flood setting.
        If can_flood, each arg (and item in arg, if arg is list) is sent on its
        own line.
        If not can_flood, all args and join with a space, and args that are a
        list are joined with comma-space.
        Example:
            Call:
                _msg(proto, target, "Foo:", ["bar", "baz", "quux"])
            Can flood output:
                Foo:
                bar
                baz
                quux
            Cannot flood output:
            Foo: bar, baz, quux
        :param protocol:
        :param target:
        :param args:
        :return:
        """
        if protocol.can_flood:
            for msg in args:
                if isinstance(msg, list):
                    for m in msg:
                        self._respond(target, m)
                else:
                    self._respond(target, msg)
        else:
            to_send = []
            for msg in args:
                if isinstance(msg, list):
                    msg = ", ".join(msg)
                to_send.append(msg)
            self._respond(target, " ".join(to_send))

    def search_cmd(self, protocol, caller, source, command, raw_args,
                   parsed_args):
        if len(raw_args) == 0:
            caller.respond("Usage: {CHARS}%s <query>" % command)
            return
        else:
            try:
                deferred = self.api.search(raw_args)
                deferred.addCallbacks(
                    lambda r: self._search_cmd_result(protocol,
                                                      caller,
                                                      source,
                                                      r),
                    lambda f: self._cmd_error(caller, f)
                )
            except RateLimitExceededError:
                caller.respond("Command on cooldown - try again later")

    def info_cmd(self, protocol, caller, source, command, raw_args,
                 parsed_args):
        if len(raw_args) == 0:
            caller.respond("Usage: {CHARS}%s <domain>" % command)
            return
        else:
            try:
                deferred = self.api.info(raw_args)
                deferred.addCallbacks(
                    lambda r: self._info_cmd_result(protocol,
                                                    caller,
                                                    source,
                                                    r),
                    lambda f: self._cmd_error(caller, f)
                )
            except RateLimitExceededError:
                caller.respond("Command on cooldown - try again later")

    def _search_cmd_result(self, protocol, caller, source, result):
        """
        Receives the API response for search
        """
        loud = self._commands.perm_handler.check("domainr.search.loud",
                                                 caller,
                                                 source,
                                                 protocol)
        target = None
        if loud:
            target = source
        else:
            target = caller
        try:
            msgs = []
            for res in result["results"]:
                self.logger.trace(res)
                msg = u"%s%s - %s" % (res["domain"],
                                      res["path"],
                                      res["availability"])
                msgs.append(msg)
            self._msg(protocol, target, msgs)
        except:
            self.logger.exception("Please tell the developer about this error")

    def _info_cmd_result(self, protocol, caller, source, result):
        """
        Receives the API response for info
        """
        loud = self._commands.perm_handler.check("domainr.info.loud",
                                                 caller,
                                                 source,
                                                 protocol)
        target = None
        if loud:
            target = source
        else:
            target = caller
        try:
            msgs = []
            msgs.append(u"Availability: %s" % result["availability"])
            if result["availability"] in (Domainr.AVAILABLE, Domainr.MAYBE):
                msgs.append(u"Register: %s" % result["register_url"])
            self._msg(protocol, target, msgs)
        except:
            self.logger.exception("Please tell the developer about this error")

    def _cmd_error(self, caller, failure):
        """
        :type failure: twisted.python.failure.Failure
        """
        # Some errors will be caused by user input
        if failure.check(DomainrError):
            self._respond(caller, failure.value.message)
        else:
            self.logger.debug("Error while fetching info",
                              exc_info=(
                                  failure.type,
                                  failure.value,
                                  failure.tb
                              ))
            caller.respond("There was an error while contacting Domainr - "
                           "please alert a bot admin or try again later")
Пример #45
0
class ControlPlugin(plugin.PluginObject):
    """
    Control plugin object
    """

    commands = None

    def setup(self):
        """
        The list of bridging rules
        """

        self.commands = CommandManager()

        self.commands.register_command("join", self.join_command,
                                       self, "control.join")
        self.commands.register_command("leave", self.leave_command,
                                       self, "control.leave")
        self.commands.register_command("say", self.say_command,
                                       self, "control.say")
        self.commands.register_command("action", self.action_command,
                                       self, "control.action")
        self.commands.register_command("raw", self.raw_command,
                                       self, "control.raw")
        self.commands.register_command("func", self.func_command,
                                       self, "control.func")

    def join_command(self, protocol, caller, source, command, raw_args,
                     args):
        """
        Command handler for the join command
        """

        if not len(args) > 0:
            caller.respond(__("Usage: {CHARS}join <channel>"))
            return

        if hasattr(protocol, "join_channel"):
            result = protocol.join_channel(args[0])

            if result:
                caller.respond(__("Done!"))
            else:
                caller.respond(__("Unable to join channel. Does this "
                                  "protocol support joining channels?"))
        else:
            caller.respond(__("This protocol doesn't support channels."))

    def leave_command(self, protocol, caller, source, command, raw_args,
                      args):
        """
        Command handler for the leave command
        """

        if not len(args) > 0:
            caller.respond(__("Usage: {CHARS}leave <channel>"))
            return

        if hasattr(protocol, "leave_channel"):
            result = protocol.leave_channel(args[0])

            if result:
                caller.respond(__("Done!"))
            else:
                caller.respond(__("Unable to leave channel. Does this "
                                  "protocol support leaving channels?"))
        else:
            caller.respond(__("This protocol doesn't support channels."))

    def raw_command(self, protocol, caller, source, command, raw_args,
                    args):
        """
        Command handler for the raw command
        """

        if not len(args) > 0:
            caller.respond(__("Usage: {CHARS}raw <data>"))
            return

        if hasattr(protocol, "send_raw"):
            protocol.send_raw(raw_args)

            caller.respond(__("Done!"))
        else:
            caller.respond(__("This protocol doesn't support sending raw "
                              "data."))

    def say_command(self, protocol, caller, source, command, raw_args,
                    args):
        """
        Command handler for the say command
        """

        if not len(args) > 1:
            caller.respond(__("Usage: {CHARS}say <target> <message>"))
            return

        channel = args[0]
        message = raw_args[len(channel):].strip()

        if hasattr(protocol, "send_msg"):
            protocol.send_msg(channel, message)

            caller.respond(__("Done!"))
        else:
            caller.respond(__("This protocol doesn't support sending "
                              "messages."))

    def action_command(self, protocol, caller, source, command, raw_args,
                       args):
        """
        Command handler for the action command
        """

        if not len(args) > 1:
            caller.respond(__("Usage: {CHARS}action <target> <message>"))
            return

        channel = args[0]
        message = raw_args[len(channel):].strip()

        if hasattr(protocol, "send_action"):
            protocol.send_action(channel, message)

            caller.respond(__("Done!"))
        else:
            caller.respond(__("This protocol doesn't support sending "
                              "actions."))

    def func_command(self, protocol, caller, source, command, raw_args,
                     args):
        """
        Command handler for the func command
        """

        if not len(args) > 1:
            caller.respond(__("Usage: {CHARS}func <function> <data>"))
            return

        func = args[0]
        arguments = args[1:]

        _args = []
        _kwargs = {}

        for arg in arguments:
            if "=" in arg:
                pos = arg.find("=")
                if arg[pos - 1] != "\\":
                    split = arg.split("=", 1)
                    _kwargs[split[0]] = split[1]
            else:
                _args.append(arg)

        try:
            x = getattr(protocol, func, None)

            if not x:
                return caller.respond(__("No such function: %s") % func)

            r = x(*_args, **_kwargs)
        except Exception as e:
            self.logger.exception(_("Error running 'func' command!"))
            caller.respond(__("Error: %s") % e)
        else:
            caller.respond(__("Done! Call returned: %s") % r)
Пример #46
0
class AoSPlugin(plugin.PluginObject):

    commands = None
    storage = None

    _STEAM_PLAYERS_REGEX = re.compile(
        r'apphub_NumInApp">(?P<players>.+) In-Game'
    )
    _IP_REGEX = re.compile(
        r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9]'
        r'[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
    )

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self._config = self.storage.get_file(self,
                                                 "config",
                                                 YAML,
                                                 "plugins/aoshelper.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/aoshelper.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Load options from config
        self._load()

        self._config.add_callback(self._load)

        ### Register commands
        self.commands.register_command("aosplayercount",
                                       self.playercount_cmd,
                                       self,
                                       "aoshelper.playercount",
                                       [
                                           "playercount"
                                       ], default=True)
        self.commands.register_command("aostoip",
                                       self.aos_to_ip_command,
                                       self,
                                       "aoshelper.aostoip",
                                       [
                                           "aos2ip"
                                       ])
        self.commands.register_command("iptoaos",
                                       self.ip_to_aos_command,
                                       self,
                                       "aoshelper.iptoaos",
                                       [
                                           "ip2aos"
                                       ])

        ### Setup soem variables
        self._last_update_voxlap = 0
        self._last_update_steam = 0
        self._last_voxlap_player_count = -1
        self._last_steam_player_count = -1

    def reload(self):
        try:
            self._config.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        return True

    def _load(self):
        self.cache_time = self._config["cache-time"]
        self.max_misses = self._config["max-misses"]

    def playercount_cmd(self, protocol, caller, source, command, raw_args,
                        parsed_args):
        voxlap_players = self.get_voxlap_player_count()
        steam_players = self.get_steam_player_count()
        percentage_players = "-1%"
        if voxlap_players is not None and steam_players is not None:
            percentage_players = str(
                round(
                    voxlap_players / float(
                        steam_players.translate(None, " ,")
                    ) * 100, 1
                )
            ) + '%'
        source.respond(
            "There are currently %s people playing 0.x, and %s people playing "
            "1.0. 0.x player count is %s the size of 1.0's. Graph at "
            "http://goo.gl/M5h3q" % (
                voxlap_players,
                steam_players,
                percentage_players
            )
        )

    def aos_to_ip_command(self, protocol, caller, source, command, raw_args,
                          parsed_args):
        if len(parsed_args) == 1:
            result = self.convert_aos_address_to_ip(parsed_args[0])
            if not result:
                source.respond("Could not get IP for %s" % parsed_args[0])
            else:
                source.respond("IP for %s is %s" % (parsed_args[0], result))
        else:
            caller.respond("Usage: {CHARS}%s <AoS address>" % command)

    def ip_to_aos_command(self, protocol, caller, source, command, raw_args,
                          parsed_args):
        if len(parsed_args) == 1:
            result = self.convert_ip_to_aos_address(parsed_args[0])
            if not result:
                source.respond("Could not get AoS address for %s" %
                               parsed_args[0])
            else:
                source.respond("AoS address for %s is %s" % (
                    parsed_args[0], result))
        else:
            caller.respond("Usage: {CHARS}%s <IP address>" % command)

    def get_voxlap_player_count(self):
        now = time.time()
        time_since_last_update = now - self._last_update_voxlap
        if time_since_last_update > self.cache_time:
            try:
                server_list = json.loads(urllib2.urlopen(
                    "http://services.buildandshoot.com/serverlist.json").read()
                )
                players = 0
                for server in server_list:
                    players += server["players_current"]
                self._last_update_voxlap = now
                self._last_voxlap_player_count = players
                return players
            except:
                if time_since_last_update > self.cache_time * self.max_misses:
                    return None
                else:
                    return self._last_voxlap_player_count
        else:
            return self._last_voxlap_player_count

    def get_steam_player_count(self):
        now = time.time()
        time_since_last_update = now - self._last_update_steam
        if time_since_last_update > self.cache_time:
            try:
                page = urllib2.urlopen(
                    "http://steamcommunity.com/app/224540").read()
                match = self._STEAM_PLAYERS_REGEX.search(page)
                if match:
                    self._last_update_steam = now
                    self._last_steam_player_count = match.group("players")
                    return self._last_steam_player_count
                else:
                    raise Exception()
            except:
                if time_since_last_update > self.cache_time * self.max_misses:
                    return None
                else:
                    return self._last_steam_player_count
        else:
            return self._last_steam_player_count

    def convert_aos_address_to_ip(self, address):
        port = -1
        if address.startswith('aos://'):
            address = address[6:]
        if ':' in address:
            colon = address.index(':')
            port = address[colon + 1:]
            address = address[:colon]
        try:
            address = int(address)
        except:
            return False
        ip = "%i.%i.%i.%i" % tuple(
            (address >> (i * 8)) & 255 for i in xrange(4)
        )
        if port > -1:
            ip += ":" + port
        return ip

    def convert_ip_to_aos_address(self, address):
        port = -1
        if ':' in address:
            colon = address.index(':')
            port = address[colon + 1:]
            address = address[:colon]
        parts = address.split('.')
        try:
            parts = [int(part) for part in parts]
        except:
            return False
        ip = (((parts[3] * 256) + parts[2]) * 256 + parts[1]) * 256 + parts[0]
        if port > -1:
            ip = str(ip) + ":" + port
        return "aos://" + str(ip)

    def is_ip(self, string):
        return self._IP_REGEX.match(string)
Пример #47
0
class RoulettePlugin(plugin.PluginObject):

    commands = None

    channels = {}
    users = {}

    def setup(self):
        self.commands = CommandManager()
        self.commands.register_command("rroulette",
                                       self.play,
                                       self,
                                       "russianroulette.rroulette",
                                       aliases=["roulette"],
                                       default=True)

    def getChannel(self, channel):
        if channel not in self.channels.keys():
            self.channels[channel] = {
                "players": [],
                "shots": 0,
                "deaths": 0,
                "chambers": 6,
                "curplayers": []
            }
        return self.channels[channel]

    def setChambers(self, channel, chambers):
        self.channels[channel]["chambers"] = chambers

    def play(self, protocol, caller, source, command, raw_args, parsed_args):
        self.logger.trace("Caller: %s" % repr(caller))
        self.logger.trace("Source: %s" % repr(source))
        self.logger.trace("Result: %s" % isinstance(source, Channel))

        if not isinstance(source, Channel):
            caller.respond("This command may only be used in a channel.")
            return

        chan = self.getChannel("{}/{}".format(protocol.name, source.name))

        chambers_left = chan["chambers"]

        random.seed()

        if random.randint(1, chambers_left) == 1:
            # Boom!
            if isinstance(protocol, ChannelsProtocol):
                attempt = protocol.channel_kick(caller, source, "BANG")

                if not attempt:
                    source.respond("BANG")
            else:
                attempt = protocol.global_kick(caller, "BANG")

                if not attempt:
                    source.respond("BANG")

            protocol.send_action(source, "*reloads the gun*")
            chambers_left = 6
            source.respond(
                'There are %s new chambers. You have a %s%% chance of dying.' %
                (chambers_left, int(100.0 / chambers_left)))

        else:
            # Click..
            chambers_left -= 1
            source.respond(
                '*click* You\'re safe for now. There are %s chambers left. '
                'You have a %s%% chance of dying.' %
                (chambers_left, int(100.0 / chambers_left)))

        self.setChambers("{}/{}".format(protocol.name, source.name),
                         chambers_left)
Пример #48
0
class DicePlugin(plugin.PluginObject):

    _MODS_REGEX = re.compile(
        r"(?P<sort>s)|(?P<total>t)|(?:\^(?P<high>\d+))|(?:v(?P<low>\d+))"
    )
    _ROLL_REGEX = re.compile(
        r"^(?P<dice>\d+)?(?:d(?P<sides>\d+))?(?P<mods>(?:t|s|\^\d+|v\d+)*)?$"
    )

    _commands = None
    _config = None
    _storage = None

    def setup(self):
        ### Grab important shit
        self._commands = CommandManager()
        self._storage = StorageManager()

        ### Initial config load
        try:
            self._config = self._storage.get_file(self, "config", YAML,
                                                  "plugins/dice.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/dice.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Register commands
        self._commands.register_command("roll",
                                        self.roll_cmd,
                                        self,
                                        "dice.roll",
                                        aliases=["dice"],
                                        default=True)

    @property
    def max_dice(self):
        return self._config["max_dice"]

    @property
    def max_sides(self):
        return self._config["max_sides"]

    @property
    def default_dice(self):
        return self._config["default_dice"]

    @property
    def default_sides(self):
        return self._config["default_sides"]

    def _respond(self, target, msg):
        target.respond("Dice: %s" % msg)

    def roll_cmd(self, protocol, caller, source, command, raw_args,
                 parsed_args):
        try:
            result = self.roll(raw_args)
            self._respond(source, str(result))
        except DiceFormatError:
            self._respond(caller, "Usage: {CHARS}%s [roll info]" % command)
        except NotEnoughDice:
            self._respond(caller, "Too many dice.")
        except NotEnoughSides:
            self._respond(caller, "Too many sides.")

    def roll(self, description=""):
        match = self._ROLL_REGEX.match(description.strip())
        if match is None:
            raise DiceFormatError("Invalid dice roll expression")

        parts = match.groupdict()
        dice = int(parts["dice"] or self.default_dice)
        sides = int(parts["sides"] or self.default_sides)
        mods = parts["mods"] or ""

        if dice > self.max_dice:
            raise NotEnoughDice()
        if sides > self.max_sides:
            raise NotEnoughSides()

        # Roll
        result = [random.randint(1, sides) for x in xrange(dice)]
        return self.apply_mods(result, mods)

    def apply_mods(self, numbers, mods):
        pos = 0
        while True:
            match = self._MODS_REGEX.match(mods, pos=pos)
            if match is None:
                break
            if match.lastgroup == "sort":
                numbers.sort()
                pos += 1
            elif match.lastgroup == "total":
                numbers = [sum(numbers)]
                pos += 1
            elif match.lastgroup == "high":
                count = match.group("high")
                numbers.sort()
                numbers = numbers[-int(count):]
                pos += len(count) + 1
            elif match.lastgroup == "low":
                count = match.group("low")
                numbers.sort()
                numbers = numbers[:int(count)]
                pos += len(count) + 1
        return numbers
Пример #49
0
class ManagementPlugin(plugin.PluginObject):
    """
    A plugin designed for on-the-fly management and configuration.

    This plugin has a few goals..

    * Storage management

        * Allow listing of files and their owners
        * Allow reloading of specific files
        * Allow reloading of files for a specific owner

    * Plugin management

        * Allow listing of plugins (available and loaded)
        * Allow plugin loading, reloading and unloading
        * Allow retrieval of plugin information

    * Package management

        * Allow listing of packages (available and installed)
        * Allow package installation and removal
        * Allow retrieval of package information
        * Cache list of packages for speed, configurable interval

    * Permissions management

        * Allow listing of permissions in all contexts

            * Point out where they come from

        * Allow addition and removal of permissions in all contexts
        * Allow listing and setting options on users and groups

    * User management

        * Allow listing of users and password resets
        * Allow management of blacklisted passwords
    """

    commands = None

    @property
    def pages(self):
        return self.factory_manager.plugman.get_plugin("Pages")

    def setup(self):
        """
        Called when the plugin is loaded. Performs initial setup.
        """

        self.commands = CommandManager()

        self.commands.register_command("storage", self.storage_command, self,
                                       "management.storage",
                                       ["st", "files", "file"])
        self.commands.register_command("protocols", self.protocols_command,
                                       self, "management.protocols",
                                       ["pr", "protos", "proto"])
        self.commands.register_command("plugins", self.plugins_command, self,
                                       "management.plugins",
                                       ["pl", "plugs", "plug"])
        self.commands.register_command("packages", self.packages_command, self,
                                       "management.packages",
                                       ["pa", "packs", "pack"])
        self.commands.register_command("permissions", self.permissions_command,
                                       self, "management.permissions",
                                       ["pe", "perms", "perm"])
        self.commands.register_command("users", self.users_command, self,
                                       "management.users", ["us", "user"])
        self.commands.register_command("shutdown", self.shutdown_command, self,
                                       "management.shutdown")

    def storage_command(self, protocol, caller, source, command, raw_args,
                        args):
        """
        Command handler for the storage command
        """

        if args is None:
            args = raw_args.split()

        if len(args) < 1:
            caller.respond(
                __("Usage: {CHARS}%s <operation> [params]") % command)
            caller.respond(__("Operations: None yet"))
            # caller.respond("Operations: help, list, [...]")

        operation = args[0]
        caller.respond(__("Unknown operation: %s") % operation)

    def protocols_command(self, protocol, caller, source, command, raw_args,
                          args):
        """
        Command handler for the protocols command
        """

        if args is None:
            args = raw_args.split()

        if len(args) < 1:
            caller.respond(
                __("Usage: {CHARS}%s <operation> [params]") % command)
            caller.respond(__("Operations: None yet"))
            # caller.respond("Operations: help, list, [...]")

        operation = args[0]
        for case, default in switch(operation):

            if default:
                caller.respond(__("Unknown operation: %s") % operation)
                break

    def plugins_command(self, protocol, caller, source, command, raw_args,
                        args):
        """
        Command handler for the plugins command
        """

        if args is None:
            args = raw_args.split()

        if len(args) < 1:
            caller.respond(
                __("Usage: {CHARS}%s <operation> [params]") % command)
            caller.respond(
                __("Operations: help, info, list, load, reload, unload"))
            return

        operation = args[0].lower()

        if operation == "help":
            lines = [
                __(  # Yey, PEP
                    "{CHARS}%s <operation> [params] - the plugin "
                    "management command. Operations:" % command),
                __("> help - This help"),
                __("> info <plugin> - Get information on an available "
                   "plugin"),
                __("> list - List all available plugins"),
                __("> load <plugin> - Load a plugin that's available"),
                __(  # Yeeeeeey, PEP
                    "> reload <plugin> - Reload a plugin that's already "
                    "loaded"),
                __("> unload <plugin> - Unload a currently-loaded plugin"),
            ]

            page_set = self.pages.get_pageset(protocol, source)
            self.pages.page(page_set, lines)
            self.pages.send_page(page_set, 1, source)
        elif operation == "info":
            if len(args) < 2:
                caller.respond(__("Usage: {CHARS}%s info <plugin>") % command)
                return

            name = args[1]
            plug = self.factory_manager.plugman.get_plugin_info(name)

            if plug is None:
                source.respond(__("Unknown plugin: %s") % name)
                return

            source.respond(  # F*****g PEP8
                "%s v%s (%s): %s" %
                (plug.name, plug.version, plug.author,
                 (__("Loaded") if self.factory_manager.plugman.plugin_loaded(
                     plug.name) is not None else __("Unloaded"))))

            source.respond("> %s" % plug.description)
            source.respond(__("Website: %s") % plug.website)
        elif operation == "list":
            self.factory_manager.plugman.scan()
            done = {}
            lines = []

            for info in self.factory_manager.plugman.info_objects.values():
                done["%s v%s" % (info.name, info.version)] = (
                    self.factory_manager.plugman.plugin_loaded(info.name))

            for key in sorted(done.keys()):
                if done[key]:
                    lines.append(__("%s: Loaded") % key)
                else:
                    lines.append(__("%s: Unloaded") % key)

            page_set = self.pages.get_pageset(protocol, source)
            self.pages.page(page_set, lines)
            self.pages.send_page(page_set, 1, source)
        elif operation == "load":
            if len(args) < 2:
                caller.respond(__("Usage: {CHARS}%s load <plugin>") % command)
                return

            name = args[1]
            result = self.factory_manager.plugman.load_plugin(name)
            info = self.factory_manager.plugman.get_plugin_info(name)

            if result is PluginState.AlreadyLoaded:
                source.respond(
                    __("Unable to load plugin %s: The plugin "
                       "is already loaded.") % info.name)
            elif result is PluginState.NotExists:
                source.respond(__("Unknown plugin: %s") % name)
            elif result is PluginState.LoadError:
                source.respond(
                    __("Unable to load plugin %s: An error "
                       "occurred.") % info.name)
            elif result is PluginState.DependencyMissing:
                source.respond(
                    __("Unable to load plugin %s: Another "
                       "plugin this one depends on is "
                       "missing.") % info.name)
            elif result is PluginState.Loaded:
                source.respond(__("Loaded plugin: %s") % info.name)
            elif result is PluginState.Unloaded:
                source.respond(__("Unloaded plugin: %s") % info.name)
                source.respond(__("This means the plugin disabled itself!"))
            else:  # THIS SHOULD NEVER HAPPEN
                source.respond(
                    __("Error while loading plugin %s: Unknown "
                       "return code %s") % (name, result))
        elif operation == "reload":
            if len(args) < 2:
                caller.respond(
                    __("Usage: {CHARS}%s reload <plugin>") % command)
                return

            name = args[1]

            result = self.factory_manager.plugman.reload_plugin(name)
            info = self.factory_manager.plugman.get_plugin_info(name)

            if result is PluginState.NotExists:
                source.respond(
                    __("Unknown plugin or plugin not loaded: "
                       "%s") % name)
            elif result is PluginState.LoadError:
                source.respond(
                    __("Unable to reload plugin %s: An error "
                       "occurred.") % info.name)
            elif result is PluginState.DependencyMissing:
                source.respond(
                    __("Unable to reload plugin %s: Another "
                       "plugin this one depends on is missing.") % info.name)
            elif result is PluginState.Loaded:
                source.respond(__("Reloaded plugin: %s") % info.name)
            elif result is PluginState.Unloaded:
                source.respond(__("Unloaded plugin: %s") % info.name)
                source.respond(__("This means the plugin disabled itself!"))
            else:  # THIS SHOULD NEVER HAPPEN
                source.respond(
                    __("Error while reloading plugin %s: "
                       "Unknown return code %s") % (name, result))
        elif operation == "unload":
            if len(args) < 2:
                caller.respond(
                    __("Usage: {CHARS}%s unload <plugin>") % command)
                return

            name = args[1]

            result = self.factory_manager.plugman.unload_plugin(name)
            info = self.factory_manager.plugman.get_plugin_info(name)

            if result is PluginState.NotExists:
                source.respond(__("Unknown plugin: %s") % name)
            elif result is PluginState.Unloaded:
                source.respond(__("Unloaded plugin: %s") % info.name)
            else:  # THIS SHOULD NEVER HAPPEN
                source.respond(
                    __("Error while loading plugin %s: Unknown "
                       "return code %s") % (name, result))
        else:
            caller.respond(__("Unknown operation: %s") % operation)

    def packages_command(self, protocol, caller, source, command, raw_args,
                         args):
        """
        Command handler for the packages command
        """

        if args is None:
            args = raw_args.split()

        if len(args) < 1:
            caller.respond(
                __("Usage: {CHARS}%s <operation> [params]") % command)
            caller.respond(__("Operations: None yet"))
            # caller.respond("Operations: help, list, [...]")

        operation = args[0]
        caller.respond(__("Unknown operation: %s") % operation)

    def permissions_command(self, protocol, caller, source, command, raw_args,
                            args):
        """
        Command handler for the permissions command
        """

        if args is None:
            args = raw_args.split()

        if len(args) < 1:
            caller.respond(
                __("Usage: {CHARS}%s <operation> [params]") % command)
            caller.respond(__("Operations: None yet"))
            # caller.respond("Operations: help, list, [...]")

        operation = args[0]
        caller.respond(__("Unknown operation: %s") % operation)

    def users_command(self, protocol, caller, source, command, raw_args, args):
        """
        Command handler for the users command
        """

        if args is None:
            args = raw_args.split()

        if len(args) < 1:
            caller.respond(
                __("Usage: {CHARS}%s <operation> [params]") % command)
            caller.respond(__("Operations: None yet"))
            # caller.respond("Operations: help, list, [...]")

        operation = args[0]
        caller.respond(__("Unknown operation: %s") % operation)

    def shutdown_command(self, protocol, caller, source, command, raw_args,
                         args):
        """
        Command handler for the shutdown command
        """

        self.factory_manager.unload()
Пример #50
0
class ReplPlugin(plugin.PluginObject):
    commands = None
    events = None
    storage = None

    config = None

    proto = None
    channel = None
    formatting = None

    def __init__(self):
        self.protocol_events = {
            "general": [
                # This is basically just *args.
                ["MessageReceived", self, self.message_received, 0]
            ],
            "inter": [
                ["Inter/PlayerConnected", self,
                    self.inter_player_connected, 0],
                ["Inter/PlayerDisconnected", self,
                    self.inter_player_disconnected, 0],
                ["Inter/ServerConnected", self,
                    self.inter_server_connected, 0],
                ["Inter/ServerDisconnected", self,
                    self.inter_server_disconnected, 0]
            ]
        }

        super(ReplPlugin, self).__init__()

    def setup(self):
        self.logger.trace("Entered setup method.")

        self.commands = CommandManager()
        self.events = EventManager()
        self.storage = StorageManager()

        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/inter.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return

        if not self.config.exists:
            self.logger.error("Unable to find config/plugins/inter.yml")
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return

        self.config.add_callback(self.reload)

        self.commands.register_command("players", self.players_command, self,
                                       "inter.players", default=True)

        if not reactor.running:
            self.events.add_callback(
                "ReactorStarted", self, self.first_load, 0
            )
        else:
            self.first_load()

    def first_load(self, _=None):
        if not self.reload():
            self.logger.error(_("Disabling.."))
            self._disable_self()

    def reload(self):
        self.events.remove_callbacks_for_plugin(self.info.name)
        proto = self.factory_manager.get_protocol(self.config["protocol"])

        if proto is None:
            self.logger.error(_("Unknown protocol: %s")
                              % self.config["protocol"])
            return False

        if proto.TYPE == "inter":
            self.logger.error(_("You cannot relay between two Inter "
                                "protocols!"))
            return False

        self.proto = proto
        self.channel = self.config["channel"]
        self.formatting = self.config["formatting"]

        for event in self.protocol_events["general"]:
            self.events.add_callback(*event)

        for event in self.protocol_events["inter"]:
            self.events.add_callback(*event)

        if proto.TYPE in self.protocol_events:
            for event in self.protocol_events[proto.TYPE]:
                self.events.add_callback(*event)

        return True

    def get_inters(self):
        inters = {}

        for key in self.factory_manager.factories.keys():
            if self.factory_manager.get_protocol(key).TYPE == "inter":
                inters[key] = self.factory_manager.get_protocol(key)

        return inters

    def players_command(self, protocol, caller, source, command, raw_args,
                        args):
        if protocol.TYPE == "inter":
            caller.respond("This command cannot be used via Inter.")
            return

        inters = self.get_inters()

        if len(inters) < 1:
            caller.respond("No Inter protocols were found.")
        elif len(inters) == 1:
            servers = inters[inters.keys()[0]].inter_servers

            for key in servers.keys():
                formatting = self.formatting["player"]["list"]

                done = formatting["message"]
                _done = []

                for x in servers[key]:
                    _done.append(str(x))

                if len(_done):
                    players = formatting["join"].join(_done)
                else:
                    players = "No players online."

                done = done.replace("{SERVER}", key)
                done = done.replace("{PLAYERS}", players)
                source.respond(done)
        else:
            if len(args) < 1:
                caller.respond("Usage: {CHARS}%s <inter server>")
                caller.respond("Servers: %s" % ", ".join(inters.keys()))
                return

            srv = args[1]
            if srv not in inters:
                caller.respond("Unknown inter server: %s" % srv)
                caller.respond("Servers: %s" % ", ".join(inters.keys()))
                return

            servers = inters[srv].inter_servers

            for key in servers.keys():
                formatting = self.formatting["player"]["list"]

                done = formatting["message"]
                _done = []

                for x in servers[key]:
                    _done.append(str(x))

                if len(_done):
                    players = formatting["join"].join(_done)
                else:
                    players = "No players online."

                done = done.replace("{SERVER}", key)
                done = done.replace("{PLAYERS}", players)
                source.respond(done)

    def message_received(self, event=MessageReceived):
        caller = event.caller
        user = event.source
        message = event.message
        target = event.target

        if target is None:
            return
        if caller is None:
            return

        if isinstance(caller, Protocol):
            if caller.TYPE == "inter":
                f_str = self.formatting["player"]["message"]

                f_str = f_str.replace("{SERVER}", user.server)
                f_str = f_str.replace("{USER}", str(user))
                f_str = f_str.replace("{MESSAGE}", message)

                self.proto.send_msg(self.channel, f_str)
            else:
                if caller.name == self.proto.name:
                    if target.name.lower() == self.channel.lower():
                        inters = self.get_inters()

                        for proto in inters.values():
                            proto.send_msg_other(user, message)

    def inter_server_connected(self, event=InterServerConnected):
        f_str = self.formatting["server"]["connected"]
        f_str = f_str.replace("{SERVER}", event.name)

        self.proto.send_msg(self.channel, f_str)

    def inter_server_disconnected(self, event=InterServerDisonnected):
        f_str = self.formatting["server"]["disconnected"]
        f_str = f_str.replace("{SERVER}", event.name)

        self.proto.send_msg(self.channel, f_str)

    def inter_player_connected(self, event=InterPlayerConnected):
        f_str = self.formatting["player"]["connected"]
        f_str = f_str.replace("{SERVER}", event.user.server)
        f_str = f_str.replace("{USER}", str(event.user))

        self.proto.send_msg(self.channel, f_str)

    def inter_player_disconnected(self, event=InterPlayerDisonnected):
        f_str = self.formatting["player"]["disconnected"]
        f_str = f_str.replace("{SERVER}", event.user.server)
        f_str = f_str.replace("{USER}", str(event.user))

        self.proto.send_msg(self.channel, f_str)
Пример #51
0
class EightBallPlugin(plugin.PluginObject):

    commands = None
    storage = None

    def setup(self):
        ### Grab important shit
        self.commands = CommandManager()
        self.storage = StorageManager()

        ### Initial config load
        try:
            self._config = self.storage.get_file(self,
                                                 "config",
                                                 YAML,
                                                 "plugins/8ball.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self.logger.error("Disabling...")
            self._disable_self()
            return
        if not self._config.exists:
            self.logger.error("Unable to find config/plugins/8ball.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Setup some stuff
        self._random = random.Random()
        self._question_regex = re.compile("[\W_]+")

        ### Register commands
        self.commands.register_command("8ball",
                                       self.eight_ball_cmd,
                                       self,
                                       "8ball.8ball",
                                       default=True)

    def reload(self):
        try:
            self._config.reload()
        except Exception:
            self.logger.exception("Error reloading configuration!")
            return False
        return True

    @property
    def yes_chance(self):
        return self._config["chances"]["yes"]

    @property
    def no_chance(self):
        return self._config["chances"]["yes"]

    @property
    def maybe_chance(self):
        return 100 - self.yes_chance - self.no_chance

    @property
    def same_answers(self):
        # Default False to keep old behaviour
        return self._config.get("same_answers", False)

    def eight_ball_cmd(self, protocol, caller, source, command, raw_args,
                       parsed_args):
        source.respond("[8ball] " + self.get_response(raw_args))

    def get_response(self, question=None):
        if self.same_answers and question is not None:
            try:
                qseed = question.encode("ascii", "ignore").strip().lower()
                qseed = self._question_regex.sub("", qseed)
                self.logger.debug("qseed: %s" % qseed)
                self._random.seed(qseed)
            except Exception:
                self.logger.exception(
                    "Error while reducing question. Please alert the author."
                )
        # Use self._random so that we can seed it (above) to always get the
        # same answer.
        choice = self._random.randint(1, 100)
        reply_type = "maybe"
        if choice <= self.yes_chance:
            reply_type = "yes"
        elif choice <= self.yes_chance + self.no_chance:
            reply_type = "no"
        # We don't want to use the re-seeded random here or we'll always get
        # the exact same response.
        return random.choice(self._config["responses"][reply_type])
Пример #52
0
class MinecraftPlugin(plugin.PluginObject):

    config = None
    commands = None
    storage = None

    status_url = "http://status.mojang.com/check"
    status_refresh_rate = 600

    task = None

    statuses = {
        "minecraft.net": "???",
        "login.minecraft.net": "???",
        "session.minecraft.net": "???",
        "account.mojang.com": "???",
        "auth.mojang.com": "???",
        "skins.minecraft.net": "???",
        "authserver.mojang.com": "???",
        "sessionserver.mojang.com": "???",
        "api.mojang.com": "???",
        "textures.minecraft.net": "???"
    }
    status_friendly_names = {
        "minecraft.net": "Website",
        "login.minecraft.net": "Login",
        "session.minecraft.net": "Session",
        "account.mojang.com": "Account",
        "auth.mojang.com": "Auth",
        "skins.minecraft.net": "Skins",
        "authserver.mojang.com": "Auth server",
        "sessionserver.mojang.com": "Session server",
        "api.mojang.com": "API",
        "textures.minecraft.net": "Textures"
    }

    @property
    def do_relay(self):
        if not self.relay_targets:
            return False

        return self.config["relay_status"]

    @property
    def relay_targets(self):
        return self.config["targets"]

    def setup(self):
        self.logger.trace("Entered setup method.")
        self.storage = StorageManager()
        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/minecraft.yml")
        except Exception:
            self.logger.exception("Error loading configuration!")
            self._disable_self()
            return
        if not self.config.exists:
            self.logger.error("Unable to find config/plugins/minecraft.yml")
            self.logger.error("Disabling..")
            self._disable_self()
            return

        if not self.relay_targets:
            self.logger.warn("No valid target protocols found. "
                             "Disabling status relaying.")

        self.commands = CommandManager()
        self.commands.register_command("mcquery", self.query_command, self,
                                       "minecraft.query", default=True)

        if self.do_relay:
            reactor.callLater(30, self.start_relay)

    def start_relay(self):
        self.check_status(True)

        self.task = LoopingCall(self.check_status)
        self.task.start(self.status_refresh_rate)

    def deactivate(self):
        if self.task:
            self.task.stop()

    @run_async_threadpool
    def query_command(self, protocol, caller, source, command, raw_args,
                      parsed_args):
        if len(parsed_args) < 1:
            caller.respond("Usage: {CHARS}mcquery <address[:port]>")
        address = parsed_args[0]
        target = source

        if isinstance(source, User):
            target = caller

        try:
            q = MinecraftServer.lookup(address)
            status = q.status()
        except Exception as e:
            target.respond("Error retrieving status: %s" % e)
            self.logger.exception("Error retrieving status")
            return

        servername = status.description

        if isinstance(servername, dict):
            servername = servername.get("text", "<Unknown server name>")

        done = ""
        done += "[%s] %s | " % (status.version.name, servername)
        done += "%s/%s " % (status.players.online, status.players.max)
        if "plugins" in status.raw:
            done += "| %s plugins" % len(status.raw["plugins"])

        target.respond(done)

        if protocol.can_flood and status.players.sample:
            players = ", ".join([x.name for x in status.players.sample])
            target.respond("Players: %s" % players)

    @run_async_threadpool
    def check_status(self, firstparse=False):
        try:
            r = urllib.urlopen(self.status_url)
            d = r.read()

            parsed_statuses = {}

            online = []
            offline = []
            problems = []

            data = json.loads(d)
            for server in data:
                for key, value in server.items():
                    parsed_statuses[key] = value
                    if self.statuses[key] != value:
                        self.logger.trace(u"%s » %s" % (key, value))
                        if value == "green":
                            online.append(self.status_friendly_names[key])
                        elif value == "yellow":
                            problems.append(self.status_friendly_names[key])
                        else:
                            offline.append(self.status_friendly_names[key])

            self.logger.trace("%s status changes found." % (len(online) +
                                                            len(offline) +
                                                            len(problems)))

            parts = []

            for element in online:
                parts.append("%s » Online" % element)
            for element in problems:
                parts.append("%s » Problems" % element)
            for element in offline:
                parts.append("%s » Offline" % element)

            if parts:
                message = "Mojang status report [%s]" % " | ".join(parts)
                self.relay_message(message, firstparse)

            self.statuses = parsed_statuses
        except Exception:
            self.logger.exception("Error checking Mojang status")

    def relay_message(self, message, first=False):
        for target in self.relay_targets:
            proto = self.factory_manager.get_protocol(target["protocol"])
            if not proto:
                self.logger.warn("Protocol not found: %s" % target["protocol"])
            if first and not target.get("initial-relay", False):
                continue
            proto.send_msg(target["target"], message, target["target-type"])