infocalypse
 
(Steve Dougherty)
2013-06-18: Remove redundant full-key local identity matching.

Remove redundant full-key local identity matching. Partial key matching works for full key matching too.

diff --git a/infocalypse/__init__.py b/infocalypse/__init__.py
--- a/infocalypse/__init__.py
+++ b/infocalypse/__init__.py
@@ -353,7 +353,7 @@ from wikicmds import execute_wiki, execu
 from arccmds import execute_arc_create, execute_arc_pull, execute_arc_push, \
      execute_arc_reinsert
 
-from config import read_freesite_cfg
+from config import read_freesite_cfg, Config
 from validate import is_hex_string, is_fms_id
 
 def set_target_version(ui_, repo, opts, params, msg_fmt):
@@ -555,6 +555,13 @@ def infocalypse_pull(ui_, repo, **opts):
     # Hmmmm... can't really implement rev.
     execute_pull(ui_, repo, params, stored_cfg)
 
+
+def infocalypse_pull_request(ui, repo, **opts):
+    if not opts['wot']:
+        ui.warning("Who do you want to send the pull request to? Set --wot.")
+        return
+
+
 def infocalypse_push(ui_, repo, **opts):
     """ Push to an Infocalypse repository in Freenet. """
     params, stored_cfg = get_config_info(ui_, opts)
@@ -798,6 +805,20 @@ def infocalypse_setupwot(ui_, **opts):
     wot.execute_setup_wot(ui_, opts)
 
 
+# TODO: Should Freemail setup also be part of fn-setup?
+# TODO: Should there be per-Identity config? Then each one would have a list
+# of repos and optionally a Freemail password.
+# Nah, FMS config is global.
+def infocalypse_setupfreemail(ui, **opts):
+    if 'truster' in opts:
+        identity = opts['truster']
+    else:
+        cfg = Config().from_ui(ui)
+        identity = cfg.defaults['TRUSTER']
+    import wot
+    # TODO: Should this be part of the normal fn-setup?
+    wot.execute_setup_freemail(ui, identity)
+
 #----------------------------------------------------------"
 def do_archive_create(ui_, opts, params, stored_cfg):
     """ fn-archive --create."""
@@ -920,6 +941,11 @@ cmdtable = {
                 + AGGRESSIVE_OPT,
                 "[options]"),
 
+    "fn-pull-request": (infocalypse_pull_request,
+                        WOT_OPTS +
+                        FCP_OPTS,
+                        "--wot id@key/repo"),
+
     "fn-push": (infocalypse_push,
                 [('', 'uri', '', 'insert URI to push to'),
                  # Buggy. Not well thought out.
@@ -1025,6 +1051,12 @@ cmdtable = {
                     WOT_OPTS,
                     "[options]"),
 
+    "fn-setupfreemail": (infocalypse_setupfreemail,
+                         [('', 'password', '', 'Freemail password')]
+                         + WOT_OPTS
+                         + FCP_OPTS,
+                         "[--truster nick@key] --password <password>"),
+
     "fn-archive": (infocalypse_archive,
                   [('', 'uri', '', 'Request URI for --pull, Insert URI ' +
                     'for --create, --push'),
diff --git a/infocalypse/wot.py b/infocalypse/wot.py
--- a/infocalypse/wot.py
+++ b/infocalypse/wot.py
@@ -2,6 +2,32 @@ import fcp
 from config import Config
 import xml.etree.ElementTree as ET
 from defusedxml.ElementTree import fromstring
+import smtplib
+from base64 import b32encode
+from fcp.node import base64decode
+
+
+def send_pull_request(ui, from_identity, to_identity):
+    local_identity = resolve_local_identity(ui, from_identity)
+    target_identity = resolve_identity(ui, from_identity, to_identity)
+
+    if local_identity is None or target_identity is None:
+        # Error.
+        return
+
+    from_address = to_freemail_address(local_identity)
+    to_address = to_freemail_address(to_identity)
+
+    if from_address is None or to_address is None:
+        ui.warn("At least one of {0} and {2} is not using Freemail."
+                .format(from_identity['Nickname'], to_identity['Nickname']))
+        return
+
+    # TODO: Use FCP host; default port.
+    smtp = smtplib.SMTP()
+    # TODO: Where to configure Freemail password?
+    smtp.login(from_address, )
+    smtp.sendmail()
 
 
 def update_repo_listing(ui, for_identity):
@@ -86,6 +112,12 @@ def execute_setup_wot(ui_, opts):
     Config.to_file(cfg)
 
 
+def execute_setup_freemail(ui, identity, password):
+
+    # TODO: get truster from config; check password.
+    pass
+
+
 def resolve_local_identity(ui, identity):
     """
     Mercurial ui for error messages.
@@ -109,19 +141,6 @@ def resolve_local_identity(ui, identity)
         ui.warn("Unexpected reply. Got {0}\n.".format(response))
         return
 
-    prefix = 'Replies.Identity'
-    id_num = -1
-    # Go by full key instead.
-    if nickname_prefix is None:
-        for item in response.iteritems():
-            if item[1] == key_prefix:
-                # Assuming identities will always be unique.
-                id_num = item[0][len(prefix):]
-                return read_local_identity(response, id_num)
-
-        ui.warn("No identity found with key '{0}'.\n".format(key_prefix))
-        return
-
     # Find nicknames starting with the supplied nickname prefix.
     prefix = 'Replies.Nickname'
     # Key: nickname, value (id_num, public key hash).
@@ -288,3 +307,22 @@ def parse_name(identity):
         key_prefix = split[1]
 
     return nickname_prefix, key_prefix
+
+
+def to_freemail_address(identity):
+    """
+    Return a Freemail address to contact the given identity if it has a
+    Freemail context. Return None if it does not have a Freemail context.
+    """
+
+    # Freemail addresses encode the public key hash with base32 instead of
+    # base64 as WoT does. This is to be case insensitive because email
+    # addresses are not case sensitive, so some clients may mangle case.
+    # See https://github.com/zidel/Freemail/blob/v0.2.2.1/docs/spec/spec.tex#L32
+
+    for item in identity.iteritem():
+        if item[1] == 'Freemail' and item[0].startswith('Context'):
+            return identity['Nickname'] + '@' + b32encode(base64decode(
+                identity['Identity'])) + 'freemail'
+
+    return None