[tor-commits] [gettor/master] Better debugging, error handling and mirrors option for xmpp

ilv at torproject.org ilv at torproject.org
Tue Nov 3 19:31:30 UTC 2015


commit d71e6838bda2a158963228f321d1cb682c5f4297
Author: ilv <ilv at users.noreply.github.com>
Date:   Thu Aug 27 15:17:21 2015 -0300

    Better debugging, error handling and mirrors option for xmpp
---
 gettor/xmpp.py                      |  200 +++++++++++++++++++++--------------
 lang/xmpp/i18n/en/LC_MESSAGES/en.po |   72 +++++++++++--
 process_chat.py                     |   22 ++++
 xmpp.cfg                            |    2 +
 4 files changed, 207 insertions(+), 89 deletions(-)

diff --git a/gettor/xmpp.py b/gettor/xmpp.py
index e9d2a10..f9b025d 100644
--- a/gettor/xmpp.py
+++ b/gettor/xmpp.py
@@ -5,8 +5,8 @@
 # :authors: Israel Leiva <ilv at riseup.net>
 #           see also AUTHORS file
 #
-# :copyright:   (c) 2008-2014, The Tor Project, Inc.
-#               (c) 2014, Israel Leiva
+# :copyright:   (c) 2008-2015, The Tor Project, Inc.
+#               (c) 2015, Israel Leiva
 #
 # :license: This is Free Software. See LICENSE for license information.
 
@@ -20,6 +20,7 @@ import logging
 import ConfigParser
 
 from sleekxmpp import ClientXMPP
+from sleekxmpp.xmlstream.stanzabase import JID
 from sleekxmpp.exceptions import IqError, IqTimeout
 
 import core
@@ -30,6 +31,14 @@ import blacklist
 """XMPP module for processing requests."""
 
 
+class ConfigError(Exception):
+    pass
+
+
+class InternalError(Exception):
+    pass
+
+
 class Bot(ClientXMPP):
     """XMPP bot.
 
@@ -52,10 +61,11 @@ class Bot(ClientXMPP):
             self.get_roster()
         except IqError as err:
             # error getting the roster
-            # logging.error(err.iq['error']['condition'])
+            self.xmpp.log.error(err.iq['error']['condition'])
             self.disconnect()
         except IqTimeout:
             # server is taking too long to respond
+            self.xmpp.log.error("Server is taking too long to respond")
             self.disconnect()
 
     def message(self, msg):
@@ -65,14 +75,6 @@ class Bot(ClientXMPP):
                 msg.reply(msg_to_send).send()
 
 
-class ConfigError(Exception):
-    pass
-
-
-class InternalError(Exception):
-    pass
-
-
 class XMPP(object):
     """Receive and reply requests by XMPP.
 
@@ -95,75 +97,63 @@ class XMPP(object):
 
         """
         # define a set of default values
-        DEFAULT_CONFIG_FILE = 'xmpp.cfg'
-
-        logging.basicConfig(format='[%(levelname)s] %(asctime)s - %(message)s',
-                            datefmt="%Y-%m-%d %H:%M:%S")
-        log = logging.getLogger(__name__)
+        default_cfg = 'xmpp.cfg'
         config = ConfigParser.ConfigParser()
 
         if cfg is None or not os.path.isfile(cfg):
-            cfg = DEFAULT_CONFIG_FILE
-
-        config.read(cfg)
+            cfg = default_cfg
 
         try:
-            self.user = config.get('account', 'user')
-        except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'user' from 'account'")
+            with open(cfg) as f:
+                config.readfp(f)
+        except IOError:
+            raise ConfigError("File %s not found!" % cfg)
 
         try:
+            self.user = config.get('account', 'user')
             self.password = config.get('account', 'password')
-        except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'password' from 'account'")
 
-        try:
-            self.core_cfg = config.get('general', 'core_cfg')
-        except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'core_cfg' from 'general'")
+            self.mirrors = config.get('general', 'mirrors')
+            self.max_words = config.get('general', 'max_words')
+            self.max_words = int(self.max_words)
+            core_cfg = config.get('general', 'core_cfg')
+            self.core = core.Core(core_cfg)
+            self.i18ndir = config.get('i18n', 'dir')
 
-        try:
             blacklist_cfg = config.get('blacklist', 'cfg')
-            self.bl = blacklist_cfg
-        except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'cfg' from 'blacklist'")
-
-        try:
+            self.bl = blacklist.Blacklist(blacklist_cfg)
             self.bl_max_req = config.get('blacklist', 'max_requests')
             self.bl_max_req = int(self.bl_max_req)
-        except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'max_requests' from 'blacklist'")
-
-        try:
             self.bl_wait_time = config.get('blacklist', 'wait_time')
             self.bl_wait_time = int(self.bl_wait_time)
-        except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'wait_time' from 'blacklist'")
-
-        try:
-            self.i18ndir = config.get('i18n', 'dir')
-        except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'dir' from 'i18n'")
 
-        try:
             logdir = config.get('log', 'dir')
             logfile = os.path.join(logdir, 'xmpp.log')
-        except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'dir' from 'log'")
-
-        try:
             loglevel = config.get('log', 'level')
+
         except ConfigParser.Error as e:
-            raise ConfigError("Couldn't read 'level' from 'log'")
+            raise ConfigError("Configuration error: %s" % str(e))
+        except blacklist.ConfigError as e:
+            raise InternalError("Blacklist error: %s" % str(e))
+        except core.ConfigError as e:
+            raise InternalError("Core error: %s" % str(e))
+
+        # logging
+        log = logging.getLogger(__name__)
 
-        # establish log level and redirect to log file
-        log.info('Redirecting logging to %s' % logfile)
+        logging_format = utils.get_logging_format()
+        date_format = utils.get_date_format()
+        formatter = logging.Formatter(logging_format, date_format)
+
+        log.info('Redirecting XMPP logging to %s' % logfile)
         logfileh = logging.FileHandler(logfile, mode='a+')
+        logfileh.setFormatter(formatter)
         logfileh.setLevel(logging.getLevelName(loglevel))
         log.addHandler(logfileh)
 
         # stop logging on stdout from now on
         log.propagate = False
+        self.log = log
 
     def start_bot(self):
         """Start the bot for handling requests.
@@ -171,6 +161,7 @@ class XMPP(object):
         Start a new sleekxmpp bot.
 
         """
+        self.log.info("Starting the bot with account %s" % self.user)
         xmpp = Bot(self.user, self.password, self)
         xmpp.connect()
         xmpp.process(block=True)
@@ -184,11 +175,11 @@ class XMPP(object):
 
         """
         anon_acc = utils.get_sha256(account)
-        bl = blacklist.Blacklist(self.bl)
 
         try:
-            bl.is_blacklisted(anon_acc, 'XMPP', self.bl_max_req,
-                              self.bl_wait_time)
+            self.bl.is_blacklisted(
+                anon_acc, 'XMPP', self.bl_max_req, self.bl_wait_time
+            )
             return False
         except blacklist.BlacklistError as e:
             return True
@@ -203,13 +194,17 @@ class XMPP(object):
 
         """
         # obtain the content in the proper language
-        t = gettext.translation(lc, self.i18ndir, languages=[lc])
-        _ = t.ugettext
+        self.log.debug("Trying to get translated text")
+        try:
+            t = gettext.translation(lc, self.i18ndir, languages=[lc])
+            _ = t.ugettext
 
-        msgstr = _(msgid)
-        return msgstr
+            msgstr = _(msgid)
+            return msgstr
+        except IOError as e:
+            raise ConfigError("%s" % str(e))
 
-    def _parse_text(self, msg, core_obj):
+    def _parse_text(self, msg):
         """Parse the text part of a message.
 
         Split the message in words and look for patterns for locale,
@@ -223,20 +218,21 @@ class XMPP(object):
 
         """
         # core knows what OS are supported
-        supported_os = core_obj.get_supported_os()
-        supported_lc = core_obj.get_supported_lc()
+        supported_os = self.core.get_supported_os()
+        supported_lc = self.core.get_supported_lc()
 
+        self.log.debug("Parsing text")
         # default values
         req = {}
         req['lc'] = 'en'
         req['os'] = None
         req['type'] = 'help'
+
         found_lc = False
         found_os = False
+        found_mirrors = False
 
         # analyze every word
-        # request shouldn't be more than 10 words long, so there should
-        # be a limit for the amount of words
         for word in msg.split(' '):
             # look for lc and os
             if not found_lc:
@@ -250,6 +246,14 @@ class XMPP(object):
                         found_os = True
                         req['os'] = os
                         req['type'] = 'links'
+            # mirrors
+            if not found_mirrors:
+                if re.match("mirrors?", word, re.IGNORECASE):
+                    found_mirrors = True
+                    req['type'] = 'mirrors'
+            if (found_lc and found_os) or (found_lc and found_mirrors):
+                break
+
         return req
 
     def parse_request(self, account, msg):
@@ -269,36 +273,70 @@ class XMPP(object):
         reply = ''
         status = ''
         req = None
-        core_obj = core.Core(self.core_cfg)
 
+        self.log.debug("Parsing request")
         try:
             if self._is_blacklisted(str(account)):
+                self.log.info("Request from blacklisted account!")
                 status = 'blacklisted'
                 bogus_request = True
 
+            # first let's find out how many words are in the message
+            # request shouldn't be longer than 3 words, but just in case
+            words = msg.split(' ')
+            if len(words) > self.max_words:
+                bogus_request = True
+                self.log.info("Message way too long")
+                status = 'error'
+                reply = self._get_msg('message_error', 'en')
+
             if not bogus_request:
+                self.log.debug("Request seems legit, let's parse it")
                 # let's try to guess what the user is asking
-                req = self._parse_text(str(msg), core_obj)
+                req = self._parse_text(str(msg))
 
                 if req['type'] == 'help':
+                    self.log.debug("Type of request: help")
                     status = 'success'
-                    reply = self._get_msg('help', req['lc'])
-                elif req['type'] == 'links':
+                    reply = self._get_msg('help', 'en')
+
+                elif req['type'] == 'mirrors':
+                    self.log.debug("Type of request: mirrors")
+                    status = 'success'
+                    reply = self._get_msg('mirrors', 'en')
                     try:
-                        links = core_obj.get_links("XMPP", req['os'],
-                                                   req['lc'])
-                        reply = self._get_msg('links', req['lc'])
-                        reply = reply % (req['os'], req['lc'], links)
-
-                        status = 'success'
-                    except (core.ConfigError, core.InternalError) as e:
-                        # if core failes, send the user an error message, but
-                        # keep going
-                        status = 'core_error'
-                        reply = self._get_msg('internal_error', req['lc'])
+                        with open(self.mirrors, "r") as list_mirrors:
+                            mirrors = list_mirrors.read()
+                        reply = reply % mirrors
+                    except IOError as e:
+                        reply = self._get_msg('mirrors_unavailable', 'en')
+
+                elif req['type'] == 'links':
+                    self.log.debug("Type of request: help")
+                    links = self.core.get_links(
+                        "XMPP",
+                        req['os'],
+                        req['lc']
+                    )
+                    reply = self._get_msg('links', 'en')
+                    reply = reply % (req['os'], req['lc'], links)
+
+                    status = 'success'
+
+        except (core.ConfigError, core.InternalError) as e:
+            # if core failes, send the user an error message, but keep going
+            self.log.error("Something went wrong internally: %s" % str(e))
+            status = 'core_error'
+            reply = self._get_msg('internal_error', req['lc'])
+
         finally:
             # keep stats
             if req:
-                core_obj.add_request_to_db()
+                self.log.debug("Adding request to database... ")
+                self.core.add_request_to_db()
 
+            if reply:
+                self.log.debug("Everything seems OK. Sending back the reply")
+            else:
+                self.log.debug("Nothing to reply!")
             return reply
diff --git a/lang/xmpp/i18n/en/LC_MESSAGES/en.po b/lang/xmpp/i18n/en/LC_MESSAGES/en.po
index d242625..8cc715e 100644
--- a/lang/xmpp/i18n/en/LC_MESSAGES/en.po
+++ b/lang/xmpp/i18n/en/LC_MESSAGES/en.po
@@ -2,21 +2,77 @@ domain "en"
 
 #: Links
 msgid "links"
-msgstr "Links %s-%s:\n %s"
+msgstr "Hello there! this is the 'GetTor' robot.\n\
+\n\
+Below are the links for your request (Tor Browser for %s, %s package):\n\
+\n\
+%s\n\
+\n\
+===========================================================================\n\
+Still need help? If you have any questions, trouble connecting to Tor\n\
+network, or need to talk to a human, please contact our support team at:\n\
+\n\
+    help at rt.torproject.org\n\
+\n\
+We are ready to answer your queries in English, Farsi, Chinese, Arabic,\n\
+French and Spanish."
 
-#: Links
-msgid "links_pt"
-msgstr "Links-PT for %s-%s:\n %s"
+#: Mirrors message
+msgid "mirrors"
+msgstr "Hello there! this is the 'GetTor' robot.\n\
+\n\
+Thank you for your request. Below you will find an updated list of mirrors\n\
+of Tor Project's website.\n\
+\n\
+%s\n\
+\n\
+Still need help? If you have any questions, trouble connecting to Tor\n\
+network, or need to talk to a human, please contact our support team at:\n\
+\n\
+    help at rt.torproject.org\n\
+\n\
+We are ready to answer your queries in English, Farsi, Chinese, Arabic,\n\
+French and Spanish."
 
 #: Help
 msgid "help"
-msgstr "*help*"
+msgstr "Hello there! this is the 'GetTor' robot.\n\
+\n\
+Thank you for your request. I am here to help you download the latest\n\
+Tor Browser.\n\
+\n\
+Please reply to this message with one of the options below (where lc stands\n\
+for the locale of the package you want e.g. en):\n\
+\n\
+    windows lc\n\
+    linux lc\n\
+    osx lc\n\
+    mirrors\n\
+\n\
+The currently supported locales are:\n\
+\n\
+    en: English\n\
+\n\
+And I will send you the download/access instructions quickly."
 
-#: Unsupported locale
-msgid "unsupported_lc"
-msgstr "Unsupported locale"
+#: Mirrors unavailable message
+msgid "mirrors_unavailable"
+msgstr "Hello there! this is the 'GetTor' robot.\n\
+\n\
+I'm sorry, I can't send you mirrors right now, please try again later.\n\
+\n\
+Still need help? If you have any questions, trouble connecting to Tor\n\
+network, or need to talk to a human, please contact our support team at:\n\
+\n\
+    help at rt.torproject.org\n\
+\n\
+We are ready to answer your queries in English, Farsi, Chinese, Arabic,\n\
+French and Spanish."
 
 #: Internal error
 msgid "internal_error"
 msgstr "Internal error"
 
+#: Internal error
+msgid "message_error"
+msgstr "Message too long."
diff --git a/process_chat.py b/process_chat.py
new file mode 100644
index 0000000..6ad7b59
--- /dev/null
+++ b/process_chat.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+import logging
+
+import gettor.xmpp
+
+def main():
+    try:
+        bot = gettor.xmpp.XMPP()
+        bot.start_bot()
+    except gettor.xmpp.ConfigError as e:
+        print "Configuration error: %s" % str(e)
+    except gettor.xmpp.InternalError as e:
+        print "Core module not working: %s" % str(e)
+    except Exception as e:
+        # in case something unexpected happens
+        print "Unexpected error: %s" % str(e)
+
+if __name__ == '__main__':
+    main()
diff --git a/xmpp.cfg b/xmpp.cfg
index 0b2f547..d81d4df 100644
--- a/xmpp.cfg
+++ b/xmpp.cfg
@@ -5,6 +5,8 @@ password:
 [general]
 basedir: /path/to/gettor/xmpp/
 core_cfg: /path/to/core.cfg
+max_words: 10
+db: /path/to/gettor.db
 
 [blacklist]
 cfg: /path/to/blacklist.cfg





More information about the tor-commits mailing list