示例#1
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.")
示例#2
0
class WebhooksPlugin(plugin.PluginObject):

    config = None
    data = None

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

    @property
    def web(self):
        """
        :rtype: plugins.web.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/webhooks.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/webhooks.yml")
                return self._disable_self()

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

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

    def _load(self):
        pass

    def add_routes(self, _=None):
        self.web.add_navbar_entry("Webhooks", "/webhooks", "url")
        self.web.add_handler("/webhooks",
                             "plugins.webhooks.routes.webhooks.Route")
        self.logger.info("Registered route: /webhooks")

    pass  # So the regions work in PyCharm
示例#3
0
class WebhooksPlugin(plugin.PluginObject):

    config = None
    data = None

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

    @property
    def web(self):
        """
        :rtype: plugins.web.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/webhooks.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/webhooks.yml")
                return self._disable_self()

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

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

    def _load(self):
        pass

    def add_routes(self, _=None):
        self.web.add_navbar_entry("Webhooks", "/webhooks", "url")
        self.web.add_handler("/webhooks", "plugins.webhooks.routes.webhooks.Route")
        self.logger.info("Registered route: /webhooks")

    pass  # So the regions work in PyCharm
示例#4
0
class AssPlugin(plugin.PluginObject):

    events = None
    regex = None

    def setup(self):
        self.events = EventManager()
        self.regex = re.compile(r"(\w+)-ass (\w+)")

        self.events.add_callback("MessageReceived", self, self.ass_swap, 1)

    def ass_swap(self, event=MessageReceived):
        source = event.source
        target = event.target
        message = event.message

        if re.search(self.regex, message) is None:
            return

        result = re.sub(self.regex, r"\1 ass-\2", message)

        target.respond("%s: %s" % (source.nickname, result))
示例#5
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
示例#6
0
class WebPlugin(PluginObject):
    """
    Web plugin object
    """

    api_log = None
    api_keys = None

    api_key_data = {}
    config = {}
    data = {}

    namespace = {}  # Global, not used right now

    handlers = {}  # Cyclone handlers

    interface = ""  # Listening interface
    listen_port = 8080

    running = False

    application = None  # Cyclone application
    port = None  # Twisted's server port
    storage = None
    template_loader = None

    navbar_items = {}

    ## Stuff plugins might find useful

    commands = None
    events = None
    packages = None
    plugins = None
    sessions = None
    stats = None

    ## Internal(ish) functions

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

        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/web.yml")
            self.logger.debug("Config loaded")
        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/web.yml"))
            self._disable_self()
            return

        try:
            self.data = self.storage.get_file(self, "data", JSON,
                                              "plugins/web/data.json")
            self.logger.debug("Data loaded")
        except Exception:
            self.logger.exception("Error loading data file!")
            return self._disable_self()

        try:
            _sessions = self.storage.get_file(self, "data", JSON,
                                              "plugins/web/sessions.json")
            self.logger.debug("Sessions loaded")
        except Exception:
            self.logger.exception("Error loading sessions file!")
            return self._disable_self()

        try:
            self.api_log = open("logs/api.log", "w")
        except Exception:
            self.logger.exception("Unable to open api log file!")
            return self._disable_self()

        try:
            self.api_key_data = self.storage.get_file(
                self, "data", JSON, "plugins/web/apikeys.json"
            )
            self.logger.debug("Sessions loaded")
        except Exception:
            self.logger.exception("Error loading API keys!")
            return self._disable_self()

        try:
            self.api_log = open("logs/api.log", "w")
        except Exception:
            self.logger.exception("Unable to open api log file!")
            return self._disable_self()

        self.config.add_callback(self.restart)
        self.data.add_callback(self.restart)

        # Index page

        self.add_handler(r"/", "plugins.web.routes.index.Route")

        # Login-related

        self.add_handler(r"/login", "plugins.web.routes.login.Route")
        self.add_handler(r"/logout", "plugins.web.routes.logout.Route")
        self.add_handler(
            r"/login/reset",
            "plugins.web.routes.login-reset.Route"
        )

        # Accounts-related

        self.add_handler(r"/account", "plugins.web.routes.account.index.Route")
        self.add_handler(
            r"/account/password/change",
            "plugins.web.routes.account.password.change.Route"
        )
        self.add_handler(
            r"/account/apikeys/create",
            "plugins.web.routes.account.apikeys.create.Route"
        )
        self.add_handler(
            r"/account/apikeys/delete",
            "plugins.web.routes.account.apikeys.delete.Route"
        )
        self.add_handler(
            r"/account/users/logout",
            "plugins.web.routes.account.users.logout.Route"
        )

        # Admin-related

        self.add_handler(
            r"/admin",
            "plugins.web.routes.admin.index.Route"
        )
        self.add_handler(
            r"/admin/files",
            "plugins.web.routes.admin.files.Route"
        )
        self.add_handler(
            r"/admin/files/(config|data)/(.*)",
            "plugins.web.routes.admin.file.Route"
        )
        self.add_handler(
            r"/api/admin/get_stats",
            "plugins.web.routes.api.admin.get_stats.Route"
        )

        self.add_navbar_entry("admin", "/admin", "settings")

        # API routes

        self.add_api_handler(
            r"/plugins/web/get_username",
            "plugins.web.routes.api.plugins.web.get_username.Route"
        )

        # Stuff routes might find useful

        self.api_keys = APIKeys(self, self.api_key_data)
        self.commands = CommandManager()
        self.events = EventManager()
        self.packages = Packages(False)
        self.plugins = PluginManager()
        self.sessions = Sessions(self, _sessions)
        self.stats = Stats()

        # Load 'er up!

        r = self.load()

        if not r:
            self._disable_self()
            return

        if not self.factory_manager.running:
            self.events.add_callback(
                "ReactorStarted", self, self.start, 0
            )
        else:
            self.start()

    def load(self):
        if "secret" not in self.data:
            self.logger.warn("Generating secret. DO NOT SHARE IT WITH ANYONE!")
            self.logger.warn("It's stored in data/plugins/web/data.json - "
                             "keep this file secure!")
            with self.data:
                self.data["secret"] = mkpasswd(60, 20, 20, 20)

        self.template_loader = TemplateLoader(self)

        if self.config.get("output_requests", True):
            log_function = self.log_request
        else:
            log_function = self.null_log

        self.application = Application(
            list(self.handlers.items()),  # Handler list

            ## General settings
            xheaders=True,
            log_function=log_function,
            gzip=True,  # Are there browsers that don't support this now?
            # error_handler=ErrorHandler,

            ## Security settings
            cookie_secret=self.data["secret"],
            login_url="/login",

            ## Template settings
            template_loader=self.template_loader,

            ## Static file settings
            static_path="web/static"
        )

        if self.config.get("hosted", False):
            hosted = self.config["hosted"]

            if isinstance(hosted, dict):
                self.interface = os.environ.get(hosted["hostname"], False)
                self.port = os.environ.get(hosted["port"], False)

                if not self.interface:
                    self.logger.error(
                        "Unknown env var: %s" % hosted["hostname"]
                    )
                    return False
                if not self.port:
                    self.logger.error(
                        "Unknown env var: %s" % hosted["port"]
                    )
                    return False
            else:
                if hosted in ["openshift"]:
                    self.interface = os.environ.get("OPENSHIFT__IP", False)
                    self.port = os.environ.get("OPENSHIFT__PORT", False)

                    if not self.interface:
                        self.logger.error(
                            "Unknown env var: OPENSHIFT__IP - Are you on "
                            "OpenShift?"
                        )
                        return False
                    if not self.port:
                        self.logger.error(
                            "Unknown env var: OPENSHIFT__PORT - Are you on "
                            "OpenShift?"
                        )
                        return False
                else:
                    self.logger.error("Unknown hosted service: %s" % hosted)
                    return False

        else:
            if self.config.get("hostname", "0.0.0.0").strip() == "0.0.0.0":
                self.interface = ""
            else:
                self.interface = self.config.get("hostname")

            self.listen_port = self.config.get("port", 8080)

        return True

    def start(self, _=None):
        self.stats.start()

        self.port = reactor.listenTCP(
            self.listen_port, self.application, interface=self.interface
        )

        self.logger.info("Server started")
        self.running = True

        self.events.run_callback(
            "Web/ServerStartedEvent",
            ServerStartedEvent(self, self.application)
        )

    def stop(self):
        self.application.doStop()
        d = self.port.stopListening()

        d.addCallback(lambda _: self.logger.info("Server stopped"))
        d.addCallback(lambda _: setattr(self, "running", False))
        d.addCallback(lambda _: self.events.run_callback(
            "Web/ServerStopped", ServerStoppedEvent(self)
        ))

        d.addErrback(lambda f: self.logger.error("Failed to stop: %s" % f))

        self.stats.stop()

        return d

    def restart(self):
        d = self.stop()

        d.addCallback(lambda _: [self.load(), self.start()])

    def deactivate(self):
        d = self.stop()

        self.handlers.clear()
        self.navbar_items.clear()

        return d

    def log_request(self, request):
        log = self.logger.info

        status_code = request.get_status()

        if status_code >= 500:
            log = self.logger.error
        elif status_code >= 400:
            log = self.logger.warn

        path = request.request.path

        # Check if this is an API method and hide the key if so

        matched = re.match(r"/api/v[0-9]/([a-zA-Z0-9]+)/.*", path)

        if matched:
            key = matched.groups()[0]
            user = self.api_keys.get_username(key)

            if user:
                path = path.replace(key, "<API: %s>" % user)
            else:
                path = path.replace(key, "<API: Invalid key>")

        log(
            "[%s] %s %s -> HTTP %s"
            % (
                request.request.remote_ip,
                request.request.method,
                path,
                request.get_status()
            )
        )

    def null_log(self, *args, **kwargs):
        pass

    ## Public API functions

    def add_api_handler(self, pattern, handler, version=1):
        if not pattern.startswith("/"):
            pattern = "/%s" % pattern
        pattern = "/api/v%s/([A-Za-z0-9]+)%s" % (version, pattern)

        return self.add_handler(pattern, handler)

    def add_handler(self, pattern, handler):
        self.logger.debug("Adding route: %s -> %s" % (pattern, handler))

        if pattern in self.handlers:
            self.logger.debug("Route already exists.")
            return False

        self.handlers[pattern] = handler

        if self.application is not None:
            self.application.add_handlers(r".*$", [(pattern, handler)])

        self.logger.debug("Handlers list: %s" % list(self.handlers.values()))

    def add_navbar_entry(self, title, url, icon="question"):
        if title in self.navbar_items:
            return False
        self.logger.debug("Adding navbar entry: %s -> %s" % (title, url))
        self.navbar_items[title] = {"url": url, "active": False, "icon": icon}
        return True

    def check_permission(self, perm, session=None):
        if session is None:
            username = None
        elif isinstance(session, str) or isinstance(session, unicode):
            username = session
        else:
            username = session["username"]

        return self.commands.perm_handler.check(
            perm, username, "web", "plugin-web"
        )

    def remove_api_handlers(self, *names, **kwargs):
        """
        :param names:
        :param version:
        :return:
        """

        version = kwargs.get("version", 1)
        patterns = []

        for pattern in names:
            if not pattern.startswith("/"):
                pattern = "/%s" % pattern
            pattern = "/api/v%s/([A-Za-z0-9]+)%s" % (version, pattern)

            patterns.append(pattern)

        return self.remove_handlers(*patterns)

    def remove_handlers(self, *names):
        found = False

        for name in names:
            if name in self.handlers:
                found = True
                del self.handlers[name]

        if found:
            self.restart()

    def write_api_log(self, address, key, username, message):
        self.api_log.write(
            "%s | %s (%s) | %s\n" % (address, key, username, message)
        )
示例#7
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)
示例#8
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
示例#9
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
示例#10
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
示例#11
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
示例#12
0
class BridgePlugin(plugin.PluginObject):
    """
    Message bridging plugin object
    """

    config = None
    events = None
    commands = None
    storage = None

    rules = {}

    @property
    def rules(self):
        """
        The list of bridging rules
        """

        return self.config["rules"]

    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/bridge.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/bridge.yml"))
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return

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

        # General

        self.events.add_callback("PreMessageReceived", self, self.handle_msg,
                                 1000)

        self.events.add_callback("MessageSent", self, self.handle_msg_sent, 0)

        # self.events.add_callback("PreCommand", self, self.handle_command,
        #                          1000)

        self.events.add_callback("ActionSent", self, self.handle_action_sent,
                                 0)

        self.events.add_callback("UserDisconnected", self,
                                 self.handle_disconnect, 1000)

        # IRC

        self.events.add_callback("IRC/UserJoined", self, self.handle_irc_join,
                                 1000)

        self.events.add_callback("IRC/UserParted", self, self.handle_irc_part,
                                 1000)

        self.events.add_callback("IRC/UserKicked", self, self.handle_irc_kick,
                                 1000)

        self.events.add_callback("IRC/UserQuit", self, self.handle_irc_quit,
                                 1000)

        self.events.add_callback("IRC/CTCPQueryReceived", self,
                                 self.handle_irc_action, 1000)

        # Mumble

        self.events.add_callback("Mumble/UserRemove", self,
                                 self.handle_mumble_remove, 1000)

        self.events.add_callback("Mumble/UserJoined", self,
                                 self.handle_mumble_join, 1000)

        self.events.add_callback("Mumble/UserMoved", self,
                                 self.handle_mumble_move, 1000)

    def handle_irc_join(self, event=UserJoinedEvent):
        """
        Event handler for IRC join events
        """

        self.do_rules("",
                      event.caller,
                      event.user,
                      event.channel,
                      f_str=["general", "join"],
                      tokens={"CHANNEL": event.channel.name})

    def handle_irc_part(self, event=UserPartedEvent):
        """
        Event handler for IRC part events
        """

        self.do_rules("",
                      event.caller,
                      event.user,
                      event.channel,
                      f_str=["general", "part"],
                      tokens={"CHANNEL": event.channel.name})

    def handle_irc_kick(self, event=UserKickedEvent):
        """
        Event handler for IRC kick events
        """

        self.do_rules(event.reason,
                      event.caller,
                      event.user,
                      event.channel,
                      f_str=["general", "kick"],
                      tokens={
                          "CHANNEL": event.channel.name,
                          "KICKER": event.kicker.nickname
                      })

    def handle_irc_quit(self, event=UserQuitEvent):
        """
        Event handler for IRC quit events
        """

        self.do_rules(event.message,
                      event.caller,
                      event.user,
                      event.user,
                      f_str=["irc", "disconnect"],
                      tokens={"USER": event.user.nickname})

    def handle_irc_action(self, event=CTCPQueryEvent):
        """
        Event handler for IRC CTCP query events
        """

        if event.action == "ACTION":
            self.do_rules(event.data,
                          event.caller,
                          event.user,
                          event.channel,
                          f_str=["general", "action"])

    def handle_disconnect(self, event=UserDisconnected):
        """
        Event handler for general disconnect events
        """

        self.do_rules("",
                      event.caller,
                      event.user,
                      event.user,
                      f_str=["general", "disconnect"],
                      tokens={"USER": event.user.nickname})

    def handle_mumble_join(self, event=UserJoined):
        """
        Event handler for Mumble join events
        """

        self.do_rules("",
                      event.caller,
                      event.user,
                      event.user,
                      f_str=["mumble", "connect"])

    def handle_mumble_move(self, event=UserMoved):
        """
        Event handler for Mumble move events
        """

        # Moving /from/ the configured channel to another one
        self.do_rules("",
                      event.caller,
                      event.user,
                      event.old_channel,
                      f_str=["mumble", "moved-from"],
                      tokens={"CHANNEL": event.channel.name})
        # Moving /to/ the configured channel
        self.do_rules("",
                      event.caller,
                      event.user,
                      event.channel,
                      f_str=["mumble", "moved-to"],
                      tokens={"CHANNEL": event.channel.name})

    def handle_mumble_remove(self, event=UserRemove):
        """
        Event handler for Mumble remove events
        """

        self.do_rules(event.reason,
                      event.caller,
                      event.user,
                      event.user,
                      f_str=["mumble", "remove"],
                      tokens={
                          "KICKER": event.kicker,
                          "BANNED?": "banned" if event.ban else "kicked"
                      })

    def handle_msg(self, event=MessageReceived):
        """
        Event handler for general message events
        """

        self.do_rules(event.message, event.caller, event.source, event.target)

    def handle_msg_sent(self, event=MessageSent):
        """
        Event handler for general message sent events
        """

        self.do_rules(event.message, event.caller, event.caller.ourselves,
                      event.target)

    def handle_action_sent(self, event=ActionSent):
        """
        Event handler for general action sent events
        """

        self.do_rules(event.message,
                      event.caller,
                      event.caller.ourselves,
                      event.target,
                      f_str=["general", "action"])

    def handle_command(self, event=PreCommand):
        """
        Event handler for general pre-command events
        """

        if event.printable:
            self.do_rules(event.message, event.caller, event.source,
                          event.target)

    def do_rules(self,
                 msg,
                 caller,
                 source,
                 target,
                 from_user=True,
                 to_user=True,
                 f_str=None,
                 tokens=None,
                 use_event=False):
        """
        Action the bridge ruleset based on input.

        :param msg: Message to relay
        :param caller: User that sent the message
        :param source: Protocol the message relates to
        :param target: User or Channel the message was sent to
        :param from_user: Whether to relay from a PM
        :param to_user: Whether to relay to a PM
        :param f_str: Definition of the formatting string to use. For example,
            ["general", "action"] or ["irc", "quit"]
        :param tokens: Dict of extra tokens to replace
        :param use_event: Whether to throw a MessageSent event

        :type msg: str
        :type caller: User
        :type source: Protocol
        :type target: User, Channel
        :type from_user: bool
        :type to_user: bool
        :type f_str: list
        :type tokens: dict
        :type use_event: bool
        """

        if not caller:
            return
        if not source:
            return
        if not target:
            return
        if not tokens:
            tokens = {}
        if not f_str:
            f_str = ["general", "message"]

        c_name = caller.name.lower()  # Protocol
        s_name = source.nickname  # User
        if isinstance(target, Channel):
            t_name = target.name  # Channel
        else:
            t_name = target.nickname

        for rule, data in self.rules.items():
            self.logger.debug(_("Checking rule: %s - %s") % (rule, data))
            from_ = data["from"]
            to_ = data["to"]

            if c_name != from_["protocol"].lower():
                self.logger.trace(_("Protocol doesn't match."))
                continue

            if not self.factory_manager.get_protocol(to_["protocol"]):
                self.logger.trace(_("Target protocol doesn't exist."))
                continue

            if isinstance(target, User):
                # We ignore the source name since there can only ever be one
                #     user: us.
                if not from_user:
                    self.logger.trace(
                        _("Function was called with relaying "
                          "from users disabled."))
                    continue
                if from_["source-type"].lower() != "user":
                    self.logger.trace(_("Target type isn't a user."))
                    continue
            elif isinstance(target, Channel):
                if from_["source-type"].lower() != "channel":
                    self.logger.trace(_("Target type isn't a Channel."))
                    continue
                if from_["source"].lower() != "*" \
                   and from_["source"].lower() != t_name.lower():
                    self.logger.trace(
                        _("Target name doesn't match the "
                          "source."))
                    continue
            else:
                self.logger.trace(_("Target isn't a known type."))
                continue

            if to_["target"] == "user" and not to_user:
                self.logger.trace(
                    _("Function was called with relaying to "
                      "users disabled."))
                continue

            # If we get this far, we've matched the incoming rule.

            format_string = None

            formatting = data["formatting"]
            if f_str[0] in formatting:
                if f_str[1] in formatting[f_str[0]]:
                    format_string = formatting[f_str[0]][f_str[1]]

            if not format_string:
                self.logger.trace(
                    _("Not relaying message as the format "
                      "string was empty or missing."))
                continue

            else:
                sf_name = s_name
                tf_name = t_name
                if from_.get("obfuscate-names", False):
                    sf_name = s_name[:-1] + "_" + s_name[-1]
                    if "USER" in tokens:
                        _u = tokens["USER"]
                        tokens["USER"] = _u[:-1] + "_" + _u[-1]

                if to_.get("obfuscate-names", False):
                    tf_name = t_name[:-1] + "_" + t_name[-1]
                    if "TARGET" in tokens:
                        _u = tokens["TARGET"]
                        tokens["TARGET"] = _u[:-1] + "_" + _u[-1]

                if "disconnected" in format_string:
                    pass

                for line in msg.strip("\r").split("\n"):
                    format_string = formatting[f_str[0]][f_str[1]]

                    for k, v in tokens.items():
                        format_string = format_string.replace("{%s}" % k, v)

                    format_string = format_string.replace("{MESSAGE}", line)
                    format_string = format_string.replace("{USER}", sf_name)
                    format_string = format_string.replace("{TARGET}", tf_name)
                    format_string = format_string.replace(
                        "{PROTOCOL}", caller.name)

                    prot = self.factory_manager.get_protocol(to_["protocol"])
                    prot.send_msg(to_["target"],
                                  format_string,
                                  target_type=to_["target-type"],
                                  use_event=use_event)
示例#13
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)
示例#14
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
示例#15
0
class BlowfishPlugin(plugin.PluginObject):
    """
    Blowfish-CBC support, as seen in Eggdrop and mIRC scripts
    """

    config = None
    events = None
    storage = None

    def get_target(self, protocol, target):
        return self.config.get(  # PEP! \o/
            protocol, {}).get("targets", {}).get(target, None)

    def get_global(self, protocol):
        return self.config.get(protocol, {}).get("global", None)

    def setup(self):
        self.events = EventManager()
        self.storage = StorageManager()

        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/blowfish.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/blowfish.yml")
            self._disable_self()
            return

        self.events.add_callback("PreMessageReceived", self, self.pre_message,
                                 10001)

        self.events.add_callback("MessageSent", self, self.message_sent, 10001)

        self.events.add_callback("ActionReceived", self, self.message_sent,
                                 10001)

        self.events.add_callback("ActionSent", self, self.message_sent, 10001)

    def pre_message(self, event=PreMessageReceived):
        if not event.caller:
            return

        target = event.target
        if not target:
            target = event.source

        key = self.get_target(event.caller.name, target)
        if not key:
            key = self.get_global(event.caller.name)
        if key:
            if event.message.startswith("+OK "):
                message = event.message[4:]

                try:
                    result = self.decode(key, message)
                except Exception:
                    self.logger.exception("Unable to decode message")
                    event.cancelled = True
                else:
                    event.message = result
            else:
                event.cancelled = True

    def message_sent(self, event=MessageSent):
        if not event.caller:
            return

        key = self.get_target(event.caller.name, event.target.name)
        if not key:
            key = self.get_global(event.caller.name)
        if key:
            message = event.message

            try:
                result = self.encode(key, message)
            except Exception:
                self.logger.exception("Unable to encode message")
                event.cancelled = True
            else:
                event.message = "+OK %s" % result

    def action_received(self, event=ActionReceived):
        if not event.caller:
            return

        target = event.target
        if not target:
            target = event.source

        key = self.get_target(event.caller.name, target)
        if not key:
            key = self.get_global(event.caller.name)
        if key:
            if event.message.startswith("+OK "):
                message = event.message[4:]

                try:
                    result = self.decode(key, message)
                except Exception:
                    self.logger.exception("Unable to decode message")
                    event.cancelled = True
                else:
                    event.message = result
            else:
                event.cancelled = True

    def action_sent(self, event=ActionSent):
        if not event.caller:
            return

        key = self.get_target(event.caller.name, event.target.name)
        if not key:
            key = self.get_global(event.caller.name)
        if key:
            message = event.message

            try:
                result = self.encode(key, message)
            except Exception:
                self.logger.exception("Unable to encode message")
                event.cancelled = True
            else:
                event.message = "+OK %s" % result

    def decode(self, key, text):
        binary = base64.b64decode(text)
        iv = binary[:8]
        encrypted = binary[8:]
        cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)

        decrypted = cipher.decrypt(encrypted)
        return decrypted.rstrip("\0")

    def encode(self, key, text):
        iv = "%s" % "".join(
            [random.choice(string.printable) for x in range(8)])
        cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)
        length = len(text)
        text += "\0" * abs((length % 8) - 8)
        binary = cipher.encrypt(text)
        return base64.b64encode("%s%s" % (iv, binary))
示例#16
0
class TriggersPlugin(plugin.PluginObject):

    commands = None
    events = None
    storage = None

    _config = 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/triggers.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/triggers.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Register event handlers
        def _message_event_filter(event=MessageReceived):
            return isinstance(event.target, Channel)

        self.events.add_callback("MessageReceived",
                                 self,
                                 self.message_handler,
                                 1,
                                 _message_event_filter)

        self.events.add_callback("ActionReceived",
                                 self,
                                 self.action_handler,
                                 1,
                                 _message_event_filter)

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

    @property
    def _triggers(self):
        return self._config.get("triggers", {})

    def message_handler(self, event=MessageReceived):
        self.event_handler(
            event.caller,
            event.source,
            event.target,
            event.message,
            event.type
        )

    def action_handler(self, event=ActionReceived):
        self.event_handler(
            event.caller,
            event.source,
            event.target,
            event.message,
            "action"
        )

    def event_handler(self, protocol, source, target, message, e_type):
        """
        Event handler for general messages
        """

        allowed = self.commands.perm_handler.check("triggers.trigger",
                                                   source,
                                                   target,
                                                   protocol)
        if not allowed:
            return

        # TODO: Rewrite this when Matcher is finished

        # TODO: We check the types of half of these - do the rest

        global_triggers = self._triggers.get("global", [])
        proto_trigger_block = self._triggers.get("protocols", {})

        proto_triggers = proto_trigger_block.get(protocol.name, {})
        if not isinstance(proto_triggers, dict):
            self.logger.error(
                "Invalid triggers for protocol '%s'" % protocol.name
            )
            return

        proto_triggers_global = proto_triggers.get("global", [])
        channel_triggers_block = proto_triggers.get("channels", {})

        channel_triggers = []
        _channel = None

        for _channel, _triggers in channel_triggers_block.iteritems():
            if protocol.get_channel(_channel) == target:
                channel_triggers = _triggers
                break

        if not isinstance(channel_triggers, list):
            self.logger.error(
                "Invalid triggers for channel '%s' in protocol  '%s'" %
                (
                    _channel,
                    protocol.name
                )
            )
            return

        for trigger in itertools.chain(channel_triggers,
                                       proto_triggers_global,
                                       global_triggers):
            try:
                trigger_regex = trigger["trigger"]
                responses = trigger["response"]
                chance = trigger.get("chance", 100)
                flags = trigger.get("flags", "")
                trigger_types = trigger.get("trigger_types", {"message": True})
                response_type = trigger.get("response_type", "message")

                if not trigger_types.get(e_type, False):
                    continue

                if random.random() * 100 >= chance:
                    continue

                response = random.choice(responses)
                response_type = response_type.lower()

                flags_parsed = 0

                for flag in flags.lower():
                    if flag == "i":
                        flags_parsed += re.I
                    elif flag == "u":
                        flags_parsed += re.U
                    elif flag == "l":
                        flags_parsed += re.L
                    elif flag == "m":
                        flags_parsed += re.M
                    elif flag == "x":
                        flags_parsed += re.X
                    elif flag == "s":
                        flags_parsed += re.S
                    elif flag == "d":
                        flags_parsed += re.DEBUG
                    else:
                        self.log.warning("Unknown regex flag '%s'" % flag)

                # TODO: Rate limiting
                # re caches compiled patterns internally, so we don't have to
                match = re.search(trigger_regex, message, flags_parsed)
                if match:
                    # Hack to get around the fact that regex groups start at
                    # one, but formatting args start at 0
                    format_args = [""]
                    format_args.extend(match.groups())
                    format_kwargs = {}
                    for k, v in match.groupdict().iteritems():
                        format_kwargs[k] = v
                    format_kwargs["channel"] = _channel
                    format_kwargs["source"] = source
                    format_kwargs["target"] = target
                    format_kwargs["message"] = message
                    format_kwargs["protocol"] = protocol.name
                    response_formatted = response.format(
                        *format_args,
                        **format_kwargs
                    )
                    if response_type == "message":
                        protocol.send_msg(target, response_formatted)
                    elif response_type == "action":
                        protocol.send_action(target, response_formatted)
                    elif response_type == "notice":
                        if hasattr(protocol, "send_notice"):
                            protocol.send_notice(target, response_formatted)
                        else:
                            self.logger.error(
                                "Cannot respond with notice on protocol: '%s'"
                                % protocol.name
                            )
                    else:
                        self.logger.error(
                            "Invalid response_type '%s'" % response_type
                        )
            except Exception:
                self.logger.exception(
                    "Invalid trigger for channel '%s' in protocol  '%s'" %
                    (
                        _channel,
                        protocol.name
                    )
                )
示例#17
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
示例#18
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)
示例#19
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
示例#20
0
class BlowfishPlugin(plugin.PluginObject):
    """
    Blowfish-CBC support, as seen in Eggdrop and mIRC scripts
    """

    config = None
    events = None
    storage = None

    def get_target(self, protocol, target):
        return self.config.get(  # PEP! \o/
            protocol, {}
        ).get(
            "targets", {}
        ).get(
            target, None
        )

    def get_global(self, protocol):
        return self.config.get(protocol, {}).get("global", None)

    def setup(self):
        self.events = EventManager()
        self.storage = StorageManager()

        try:
            self.config = self.storage.get_file(self, "config", YAML,
                                                "plugins/blowfish.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/blowfish.yml")
            self._disable_self()
            return

        self.events.add_callback(
            "PreMessageReceived", self, self.pre_message, 10001
        )

        self.events.add_callback(
            "MessageSent", self, self.message_sent, 10001
        )

        self.events.add_callback(
            "ActionReceived", self, self.message_sent, 10001
        )

        self.events.add_callback(
            "ActionSent", self, self.message_sent, 10001
        )

    def pre_message(self, event=PreMessageReceived):
        if not event.caller:
            return

        target = event.target
        if not target:
            target = event.source

        key = self.get_target(event.caller.name, target)
        if not key:
            key = self.get_global(event.caller.name)
        if key:
            if event.message.startswith("+OK "):
                message = event.message[4:]

                try:
                    result = self.decode(key, message)
                except Exception:
                    self.logger.exception("Unable to decode message")
                    event.cancelled = True
                else:
                    event.message = result
            else:
                event.cancelled = True

    def message_sent(self, event=MessageSent):
        if not event.caller:
            return

        key = self.get_target(event.caller.name, event.target.name)
        if not key:
            key = self.get_global(event.caller.name)
        if key:
            message = event.message

            try:
                result = self.encode(key, message)
            except Exception:
                self.logger.exception("Unable to encode message")
                event.cancelled = True
            else:
                event.message = "+OK %s" % result

    def action_received(self, event=ActionReceived):
        if not event.caller:
            return

        target = event.target
        if not target:
            target = event.source

        key = self.get_target(event.caller.name, target)
        if not key:
            key = self.get_global(event.caller.name)
        if key:
            if event.message.startswith("+OK "):
                message = event.message[4:]

                try:
                    result = self.decode(key, message)
                except Exception:
                    self.logger.exception("Unable to decode message")
                    event.cancelled = True
                else:
                    event.message = result
            else:
                event.cancelled = True

    def action_sent(self, event=ActionSent):
        if not event.caller:
            return

        key = self.get_target(event.caller.name, event.target.name)
        if not key:
            key = self.get_global(event.caller.name)
        if key:
            message = event.message

            try:
                result = self.encode(key, message)
            except Exception:
                self.logger.exception("Unable to encode message")
                event.cancelled = True
            else:
                event.message = "+OK %s" % result

    def decode(self, key, text):
        binary = base64.b64decode(text)
        iv = binary[:8]
        encrypted = binary[8:]
        cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)

        decrypted = cipher.decrypt(encrypted)
        return decrypted.rstrip("\0")

    def encode(self, key, text):
        iv = "%s" % "".join(
            [random.choice(
                string.printable
            ) for x in range(8)]
        )
        cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)
        length = len(text)
        text += "\0" * abs((length % 8) - 8)
        binary = cipher.encrypt(text)
        return base64.b64encode("%s%s" % (iv, binary))
示例#21
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)
示例#22
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)
示例#23
0
class TriggersPlugin(plugin.PluginObject):

    commands = None
    events = None
    storage = None

    _config = 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/triggers.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/triggers.yml")
            self.logger.error("Disabling...")
            self._disable_self()
            return

        ### Register event handlers
        def _message_event_filter(event=MessageReceived):
            return isinstance(event.target, Channel)

        self.events.add_callback("MessageReceived", self, self.message_handler,
                                 1, _message_event_filter)

        self.events.add_callback("ActionReceived", self, self.action_handler,
                                 1, _message_event_filter)

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

    @property
    def _triggers(self):
        return self._config.get("triggers", {})

    def message_handler(self, event=MessageReceived):
        self.event_handler(event.caller, event.source, event.target,
                           event.message, event.type)

    def action_handler(self, event=ActionReceived):
        self.event_handler(event.caller, event.source, event.target,
                           event.message, "action")

    def event_handler(self, protocol, source, target, message, e_type):
        """
        Event handler for general messages
        """

        allowed = self.commands.perm_handler.check("triggers.trigger", source,
                                                   target, protocol)
        if not allowed:
            return

        # TODO: Rewrite this when Matcher is finished

        # TODO: We check the types of half of these - do the rest

        global_triggers = self._triggers.get("global", [])
        proto_trigger_block = self._triggers.get("protocols", {})

        proto_triggers = proto_trigger_block.get(protocol.name, {})
        if not isinstance(proto_triggers, dict):
            self.logger.error("Invalid triggers for protocol '%s'" %
                              protocol.name)
            return

        proto_triggers_global = proto_triggers.get("global", [])
        channel_triggers_block = proto_triggers.get("channels", {})

        channel_triggers = []
        _channel = None

        for _channel, _triggers in channel_triggers_block.iteritems():
            if protocol.get_channel(_channel) == target:
                channel_triggers = _triggers
                break

        if not isinstance(channel_triggers, list):
            self.logger.error(
                "Invalid triggers for channel '%s' in protocol  '%s'" %
                (_channel, protocol.name))
            return

        for trigger in itertools.chain(channel_triggers, proto_triggers_global,
                                       global_triggers):
            try:
                trigger_regex = trigger["trigger"]
                responses = trigger["response"]
                chance = trigger.get("chance", 100)
                flags = trigger.get("flags", "")
                trigger_types = trigger.get("trigger_types", {"message": True})
                response_type = trigger.get("response_type", "message")

                if not trigger_types.get(e_type, False):
                    continue

                if random.random() * 100 >= chance:
                    continue

                response = random.choice(responses)
                if isinstance(response, dict):
                    response_type = response.get("type", response_type)
                    response = response["response"]
                response_type = response_type.lower()

                flags_parsed = 0

                for flag in flags.lower():
                    if flag == "i":
                        flags_parsed += re.I
                    elif flag == "u":
                        flags_parsed += re.U
                    elif flag == "l":
                        flags_parsed += re.L
                    elif flag == "m":
                        flags_parsed += re.M
                    elif flag == "x":
                        flags_parsed += re.X
                    elif flag == "s":
                        flags_parsed += re.S
                    elif flag == "d":
                        flags_parsed += re.DEBUG
                    else:
                        self.log.warning("Unknown regex flag '%s'" % flag)

                # TODO: Rate limiting
                # re caches compiled patterns internally, so we don't have to
                match = re.search(trigger_regex, message, flags_parsed)
                if match:
                    # Hack to get around the fact that regex groups start at
                    # one, but formatting args start at 0
                    format_args = [""]
                    format_args.extend(match.groups())
                    format_kwargs = {}
                    for k, v in match.groupdict().iteritems():
                        format_kwargs[k] = v
                    format_kwargs["channel"] = _channel
                    format_kwargs["source"] = source
                    format_kwargs["target"] = target
                    format_kwargs["message"] = message
                    format_kwargs["protocol"] = protocol.name
                    response_formatted = response.format(
                        *format_args, **format_kwargs)
                    if response_type == "message":
                        protocol.send_msg(target, response_formatted)
                    elif response_type == "action":
                        protocol.send_action(target, response_formatted)
                    elif response_type == "notice":
                        if hasattr(protocol, "send_notice"):
                            protocol.send_notice(target, response_formatted)
                        else:
                            self.logger.error(
                                "Cannot respond with notice on protocol: '%s'"
                                % protocol.name)
                    elif response_type == "channel_kick":
                        attempted = protocol.channel_kick(
                            source,
                            channel=_channel,
                            reason=response_formatted)
                        if not attempted:
                            self.logger.warning("Couldn't kick %s from %s" %
                                                (source, _channel))
                    elif response_type == "channel_ban":
                        attempted = protocol.channel_ban(
                            source,
                            channel=_channel,
                            reason=response_formatted)
                        if not attempted:
                            self.logger.warning("Couldn't ban %s from %s" %
                                                (source, _channel))
                    elif response_type == "global_kick":
                        attempted = protocol.global_kick(
                            source, reason=response_formatted)
                        if not attempted:
                            self.logger.warning("Couldn't global kick %s" %
                                                source)
                    elif response_type == "global_ban":
                        attempted = protocol.global_ban(
                            source, reason=response_formatted)
                        if not attempted:
                            self.logger.warning("Couldn't global ban %s" %
                                                source)
                    else:
                        self.logger.error("Invalid response_type '%s'" %
                                          response_type)
            except Exception:
                self.logger.exception(
                    "Invalid trigger for channel '%s' in protocol  '%s'" %
                    (_channel, protocol.name))
示例#24
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)
        )
示例#25
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)
示例#26
0
class BridgePlugin(plugin.PluginObject):
    """
    Message bridging plugin object
    """

    config = None
    events = None
    commands = None
    storage = None

    rules = {}

    @property
    def rules(self):
        """
        The list of bridging rules
        """

        return self.config["rules"]

    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/bridge.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/bridge.yml"))
            self.logger.error(_("Disabling.."))
            self._disable_self()
            return

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

        # General

        self.events.add_callback("PreMessageReceived", self, self.handle_msg,
                                 1000)

        self.events.add_callback("MessageSent", self, self.handle_msg_sent,
                                 0)

        # self.events.add_callback("PreCommand", self, self.handle_command,
        #                          1000)

        self.events.add_callback("ActionSent", self, self.handle_action_sent,
                                 0)

        self.events.add_callback("UserDisconnected", self,
                                 self.handle_disconnect, 1000)

        # IRC

        self.events.add_callback("IRC/UserJoined", self,
                                 self.handle_irc_join, 1000)

        self.events.add_callback("IRC/UserParted", self,
                                 self.handle_irc_part, 1000)

        self.events.add_callback("IRC/UserKicked", self,
                                 self.handle_irc_kick, 1000)

        self.events.add_callback("IRC/UserQuit", self,
                                 self.handle_irc_quit, 1000)

        self.events.add_callback("IRC/CTCPQueryReceived", self,
                                 self.handle_irc_action, 1000)

        # Mumble

        self.events.add_callback("Mumble/UserRemove", self,
                                 self.handle_mumble_remove, 1000)

        self.events.add_callback("Mumble/UserJoined", self,
                                 self.handle_mumble_join, 1000)

        self.events.add_callback("Mumble/UserMoved", self,
                                 self.handle_mumble_move, 1000)

    def handle_irc_join(self, event=UserJoinedEvent):
        """
        Event handler for IRC join events
        """

        self.do_rules("", event.caller, event.user, event.channel,
                      f_str=["general", "join"],
                      tokens={"CHANNEL": event.channel.name})

    def handle_irc_part(self, event=UserPartedEvent):
        """
        Event handler for IRC part events
        """

        self.do_rules("", event.caller, event.user, event.channel,
                      f_str=["general", "part"],
                      tokens={"CHANNEL": event.channel.name})

    def handle_irc_kick(self, event=UserKickedEvent):
        """
        Event handler for IRC kick events
        """

        self.do_rules(event.reason, event.caller, event.user, event.channel,
                      f_str=["general", "kick"],
                      tokens={"CHANNEL": event.channel.name,
                              "KICKER": event.kicker.nickname})

    def handle_irc_quit(self, event=UserQuitEvent):
        """
        Event handler for IRC quit events
        """

        self.do_rules(event.message, event.caller, event.user, event.user,
                      f_str=["irc", "disconnect"],
                      tokens={"USER": event.user.nickname})

    def handle_irc_action(self, event=CTCPQueryEvent):
        """
        Event handler for IRC CTCP query events
        """

        if event.action == "ACTION":
            self.do_rules(event.data, event.caller, event.user,
                          event.channel, f_str=["general", "action"])

    def handle_disconnect(self, event=UserDisconnected):
        """
        Event handler for general disconnect events
        """

        self.do_rules("", event.caller, event.user, event.user,
                      f_str=["general", "disconnect"],
                      tokens={"USER": event.user.nickname})

    def handle_mumble_join(self, event=UserJoined):
        """
        Event handler for Mumble join events
        """

        self.do_rules("", event.caller, event.user, event.user,
                      f_str=["mumble", "connect"])

    def handle_mumble_move(self, event=UserMoved):
        """
        Event handler for Mumble move events
        """

        # Moving /from/ the configured channel to another one
        self.do_rules("", event.caller, event.user, event.old_channel,
                      f_str=["mumble", "moved-from"],
                      tokens={"CHANNEL": event.channel.name})
        # Moving /to/ the configured channel
        self.do_rules("", event.caller, event.user, event.channel,
                      f_str=["mumble", "moved-to"],
                      tokens={"CHANNEL": event.channel.name})

    def handle_mumble_remove(self, event=UserRemove):
        """
        Event handler for Mumble remove events
        """

        self.do_rules(event.reason, event.caller, event.user, event.user,
                      f_str=["mumble", "remove"],
                      tokens={"KICKER": event.kicker,
                              "BANNED?": "banned" if event.ban else "kicked"})

    def handle_msg(self, event=MessageReceived):
        """
        Event handler for general message events
        """

        self.do_rules(event.message, event.caller, event.source, event.target)

    def handle_msg_sent(self, event=MessageSent):
        """
        Event handler for general message sent events
        """

        self.do_rules(event.message, event.caller, event.caller.ourselves,
                      event.target)

    def handle_action_sent(self, event=ActionSent):
        """
        Event handler for general action sent events
        """

        self.do_rules(event.message, event.caller, event.caller.ourselves,
                      event.target, f_str=["general", "action"])

    def handle_command(self, event=PreCommand):
        """
        Event handler for general pre-command events
        """

        if event.printable:
            self.do_rules(event.message, event.caller, event.source,
                          event.target)

    def do_rules(self, msg, caller, source, target, from_user=True,
                 to_user=True, f_str=None, tokens=None, use_event=False):
        """
        Action the bridge ruleset based on input.

        :param msg: Message to relay
        :param caller: User that sent the message
        :param source: Protocol the message relates to
        :param target: User or Channel the message was sent to
        :param from_user: Whether to relay from a PM
        :param to_user: Whether to relay to a PM
        :param f_str: Definition of the formatting string to use. For example,
            ["general", "action"] or ["irc", "quit"]
        :param tokens: Dict of extra tokens to replace
        :param use_event: Whether to throw a MessageSent event

        :type msg: str
        :type caller: User
        :type source: Protocol
        :type target: User, Channel
        :type from_user: bool
        :type to_user: bool
        :type f_str: list
        :type tokens: dict
        :type use_event: bool
        """

        if not caller:
            return
        if not source:
            return
        if not target:
            return
        if not tokens:
            tokens = {}
        if not f_str:
            f_str = ["general", "message"]

        c_name = caller.name.lower()  # Protocol
        s_name = source.nickname  # User
        if isinstance(target, Channel):
            t_name = target.name  # Channel
        else:
            t_name = target.nickname

        for rule, data in self.rules.items():
            self.logger.debug(_("Checking rule: %s - %s") % (rule, data))
            from_ = data["from"]
            to_ = data["to"]

            if c_name != from_["protocol"].lower():
                self.logger.trace(_("Protocol doesn't match."))
                continue

            if not self.factory_manager.get_protocol(to_["protocol"]):
                self.logger.trace(_("Target protocol doesn't exist."))
                continue

            if isinstance(target, User):
                # We ignore the source name since there can only ever be one
                #     user: us.
                if not from_user:
                    self.logger.trace(_("Function was called with relaying "
                                        "from users disabled."))
                    continue
                if from_["source-type"].lower() != "user":
                    self.logger.trace(_("Target type isn't a user."))
                    continue
            elif isinstance(target, Channel):
                if from_["source-type"].lower() != "channel":
                    self.logger.trace(_("Target type isn't a Channel."))
                    continue
                if from_["source"].lower() != "*" \
                   and from_["source"].lower() != t_name.lower():
                    self.logger.trace(_("Target name doesn't match the "
                                        "source."))
                    continue
            else:
                self.logger.trace(_("Target isn't a known type."))
                continue

            if to_["target"] == "user" and not to_user:
                self.logger.trace(_("Function was called with relaying to "
                                    "users disabled."))
                continue

            # If we get this far, we've matched the incoming rule.

            format_string = None

            formatting = data["formatting"]
            if f_str[0] in formatting:
                if f_str[1] in formatting[f_str[0]]:
                    format_string = formatting[f_str[0]][f_str[1]]

            if not format_string:
                self.logger.trace(_("Not relaying message as the format "
                                    "string was empty or missing."))
                continue

            else:
                sf_name = s_name
                tf_name = t_name
                if from_.get("obfuscate-names", False):
                    sf_name = s_name[:-1] + "_" + s_name[-1]
                    if "USER" in tokens:
                        _u = tokens["USER"]
                        tokens["USER"] = _u[:-1] + "_" + _u[-1]

                if to_.get("obfuscate-names", False):
                    tf_name = t_name[:-1] + "_" + t_name[-1]
                    if "TARGET" in tokens:
                        _u = tokens["TARGET"]
                        tokens["TARGET"] = _u[:-1] + "_" + _u[-1]

                if "disconnected" in format_string:
                    pass

                for line in msg.strip("\r").split("\n"):
                    format_string = formatting[f_str[0]][f_str[1]]

                    for k, v in tokens.items():
                        format_string = format_string.replace("{%s}" % k, v)

                    format_string = format_string.replace("{MESSAGE}", line)
                    format_string = format_string.replace("{USER}", sf_name)
                    format_string = format_string.replace("{TARGET}", tf_name)
                    format_string = format_string.replace("{PROTOCOL}",
                                                          caller.name)

                    prot = self.factory_manager.get_protocol(to_["protocol"])
                    prot.send_msg(to_["target"], format_string,
                                  target_type=to_["target-type"],
                                  use_event=use_event)