URI: 
       tcontacts.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tcontacts.py (4439B)
       ---
            1 # Electrum - Lightweight Bitcoin Client
            2 # Copyright (c) 2015 Thomas Voegtlin
            3 #
            4 # Permission is hereby granted, free of charge, to any person
            5 # obtaining a copy of this software and associated documentation files
            6 # (the "Software"), to deal in the Software without restriction,
            7 # including without limitation the rights to use, copy, modify, merge,
            8 # publish, distribute, sublicense, and/or sell copies of the Software,
            9 # and to permit persons to whom the Software is furnished to do so,
           10 # subject to the following conditions:
           11 #
           12 # The above copyright notice and this permission notice shall be
           13 # included in all copies or substantial portions of the Software.
           14 #
           15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           17 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
           19 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
           20 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
           21 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           22 # SOFTWARE.
           23 import re
           24 
           25 import dns
           26 from dns.exception import DNSException
           27 
           28 from . import bitcoin
           29 from . import dnssec
           30 from .util import read_json_file, write_json_file, to_string
           31 from .logging import Logger
           32 
           33 
           34 class Contacts(dict, Logger):
           35 
           36     def __init__(self, db):
           37         Logger.__init__(self)
           38         self.db = db
           39         d = self.db.get('contacts', {})
           40         try:
           41             self.update(d)
           42         except:
           43             return
           44         # backward compatibility
           45         for k, v in self.items():
           46             _type, n = v
           47             if _type == 'address' and bitcoin.is_address(n):
           48                 self.pop(k)
           49                 self[n] = ('address', k)
           50 
           51     def save(self):
           52         self.db.put('contacts', dict(self))
           53 
           54     def import_file(self, path):
           55         data = read_json_file(path)
           56         data = self._validate(data)
           57         self.update(data)
           58         self.save()
           59 
           60     def export_file(self, path):
           61         write_json_file(path, self)
           62 
           63     def __setitem__(self, key, value):
           64         dict.__setitem__(self, key, value)
           65         self.save()
           66 
           67     def pop(self, key):
           68         if key in self.keys():
           69             res = dict.pop(self, key)
           70             self.save()
           71             return res
           72 
           73     def resolve(self, k):
           74         if bitcoin.is_address(k):
           75             return {
           76                 'address': k,
           77                 'type': 'address'
           78             }
           79         if k in self.keys():
           80             _type, addr = self[k]
           81             if _type == 'address':
           82                 return {
           83                     'address': addr,
           84                     'type': 'contact'
           85                 }
           86         out = self.resolve_openalias(k)
           87         if out:
           88             address, name, validated = out
           89             return {
           90                 'address': address,
           91                 'name': name,
           92                 'type': 'openalias',
           93                 'validated': validated
           94             }
           95         raise Exception("Invalid Bitcoin address or alias", k)
           96 
           97     def resolve_openalias(self, url):
           98         # support email-style addresses, per the OA standard
           99         url = url.replace('@', '.')
          100         try:
          101             records, validated = dnssec.query(url, dns.rdatatype.TXT)
          102         except DNSException as e:
          103             self.logger.info(f'Error resolving openalias: {repr(e)}')
          104             return None
          105         prefix = 'btc'
          106         for record in records:
          107             string = to_string(record.strings[0], 'utf8')
          108             if string.startswith('oa1:' + prefix):
          109                 address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)')
          110                 name = self.find_regex(string, r'recipient_name=([^;]+)')
          111                 if not name:
          112                     name = address
          113                 if not address:
          114                     continue
          115                 return address, name, validated
          116 
          117     def find_regex(self, haystack, needle):
          118         regex = re.compile(needle)
          119         try:
          120             return regex.search(haystack).groups()[0]
          121         except AttributeError:
          122             return None
          123             
          124     def _validate(self, data):
          125         for k, v in list(data.items()):
          126             if k == 'contacts':
          127                 return self._validate(v)
          128             if not bitcoin.is_address(k):
          129                 data.pop(k)
          130             else:
          131                 _type, _ = v
          132                 if _type != 'address':
          133                     data.pop(k)
          134         return data
          135