Add support for TOTP.

It is a little wonky since SEQUENCE is not needed, and SEED really
means a name/alias for the stored secret.

* allow totp as an algorithm name in ~/.otp and on cmdline.
* pass algo to the password creation so that we use the special TOTP
  password construction function.
* adjust the command line parser for TOTP and the algo processor call
diff --git a/otp.py b/otp.py
index 181cca4..f2b4d58 100755
--- a/otp.py
+++ b/otp.py
@@ -14,10 +14,17 @@
 import platform
 import re
 
+try:
+  import pyotp
+except ImportError:
+  pyotp = None
+
 OTP_PWD_FILE = '.otp'
 DEFAULT_ALGO = 'otp-md5'  # if the PWD file doesn't specify
 PASSWORD_LEN = 20
 
+ALGO_TOTP = 'totp'
+
 
 def otp_path():
   "Compute the full path to the OTP password storage file."
@@ -43,20 +50,21 @@
   for l in open(path).readlines():
     m = RE_STORAGE.match(l)
     if m:
+      #print('MATCH:', m.groups())
       if m.group(2):
         algo = m.group(2).lower()
       else:
         algo = DEFAULT_ALGO
-      pwds[m.group(3).encode()] = (m.group(4).encode(), algo)
+      pwds[m.group(4).encode()] = (m.group(5).encode(), algo)
 
   return pwds
 
-RE_STORAGE = re.compile(r'((otp-\w+)\s+)?(\w+)\s+(.*)')
+RE_STORAGE = re.compile(r'(((otp-\w+)|totp)\s+)?(\w+)\s+(.*)')
 
 
 def add_password(seed, algo):
   "Construct a new password for a seed, and store/append it."
-  pwd = new_password()
+  pwd = new_password(algo)
   path = otp_path()
   fd = os.open(path, os.O_RDWR | os.O_APPEND | os.O_CREAT, mode=0o600)
   os.write(fd, algo.encode() + b' ' + seed + b' ' + pwd + b'\n')
@@ -64,8 +72,11 @@
   return pwd
 
 
-def new_password():
+def new_password(algo):
   "Generate a password."
+  if algo == ALGO_TOTP:
+    # Generate a proper/compatible password for most TOTP apps
+    return pyotp.random_base32().encode()
   # Stick to printable characters.
   return ''.join(chr(random.randint(33, 126)) for _ in range(PASSWORD_LEN)).encode()
 
@@ -130,6 +141,14 @@
   }
 
 
+# If the pyotp module is present, then add an algorithm for its use
+if pyotp:
+  def totp(password):
+    t = pyotp.TOTP(password)
+    return t.now()
+  ALGOS[ALGO_TOTP] = totp
+
+
 def main():
   cmd = os.path.basename(sys.argv[0])
   if cmd in ALGOS:
@@ -150,14 +169,25 @@
     parsed = parser.parse_args()
 
     parts = parsed.args
-    if len(parts) < 2:  # the [ALGO] SEQUENCE SEED are not on cmdline. ask.
+
+    # We have two possible forms:
+    #   $ CMD [ALGO] SEQUENCE SEED
+    #   $ CMD totp SEED
+
+    # If the [ALGO] SEQUENCE SEED is not on the cmdline, then ask.
+    if len(parts) < 2:
       line = input('Challenge? ')
       parts = line.split()
 
     if len(parts) == 2:
-      # presumably: SEQUENCE SEED
-      algo = None
-      seq = int(parts[0])
+      if parts[0] == ALGO_TOTP:
+        # form: totp SEED
+        algo = ALGO_TOTP
+        seq = None  # unused
+      else:
+        # form: SEQUENCE SEED
+        algo = None
+        seq = int(parts[0])
       seed = parts[1].lower().encode()
     elif len(parts) >= 3:
       algo = parts[0]
@@ -186,8 +216,11 @@
   processor = ALGOS.get(algo)
   assert processor, 'Unknown/unsupported algorithm: "%s"' % (algo,)
 
-  value = processor(seed + pwd, seq)
-  response = ' '.join(to_words(value))
+  if algo == ALGO_TOTP:
+    response = processor(pwd)
+  else:
+    value = processor(seed + pwd, seq)
+    response = ' '.join(to_words(value))
   print('Response:', response)
 
   osname = platform.system()