1import hashlib 2import re 3import six 4from binascii import unhexlify 5from natural.constant import PHONE_PREFIX, PHONE_E161_ALPHABET 6from natural.language import _ 7from natural.util import luhn_append, luhn_calc, strip, to_decimal 8 9 10def e123(number, areasize=3, groupsize=4, national=False): 11 ''' 12 Printable E.123 (Notation for national and international telephone numbers 13 from ITU) numbers. 14 15 :param number: string 16 :param areasize: int 17 :param groupsize: int 18 :param national: bool 19 20 >>> e123(155542315678) 21 '+1 555 4231 5678' 22 >>> e123('+31654231567', areasize=1) 23 '+31 6 5423 1567' 24 >>> e123('+3114020', areasize=2) 25 '+31 14 020' 26 >>> e123('+312054231567', areasize=2, national=True) 27 '(020) 5423 1567' 28 ''' 29 30 if isinstance(number, six.integer_types): 31 return e123('+%s' % number, areasize, groupsize) 32 33 elif isinstance(number, six.string_types): 34 number = strip(number, '-. ()') 35 if number.startswith('+'): 36 number = number[1:] 37 38 if not number.isdigit(): 39 raise ValueError(_('Invalid telephone number')) 40 41 groups = [] 42 prefix = '' 43 remain = number 44 45 if national: 46 for x in six.moves.xrange(3, 0, -1): 47 if number[:x] in PHONE_PREFIX: 48 groups.append('(0%s)' % number[x:x + areasize]) 49 remain = number[x + areasize:] 50 break 51 52 else: 53 prefix = '+' 54 for x in six.moves.xrange(3, 0, -1): 55 if number[:x] in PHONE_PREFIX: 56 groups.append(number[:x]) 57 groups.append(number[x:x + areasize]) 58 remain = number[x + areasize:] 59 break 60 61 for x in six.moves.xrange(0, len(remain) + 1, groupsize): 62 groups.append(remain[x:x + groupsize]) 63 64 return '%s%s' % (prefix, ' '.join(list(filter(None, groups)))) 65 66 67def e161(number, alphabet=PHONE_E161_ALPHABET): 68 ''' 69 Printable a 26 Latin letters (A to Z) phone number to the 12-key telephone 70 keypad number. 71 72 :param number: string 73 :param alphabet: dict 74 75 >>> e161('0800-PIZZA123') 76 '080074992123' 77 >>> e161('0800^PIZZA123') 78 Traceback (most recent call last): 79 ... 80 ValueError: Character "^" (0x5e) is not in the E.161 alphabet 81 ''' 82 83 digits = [] 84 for char in strip(number, '+-. ()').lower(): 85 length = len(digits) 86 for group, digit in alphabet.items(): 87 if char in group: 88 digits.append(digit) 89 break 90 91 if len(digits) == length: 92 raise ValueError( 93 _('Character "%s" (0x%02x) is not in the E.161 alphabet') % 94 (char, ord(char)) 95 ) 96 97 return u''.join(digits) 98 99 100def e164(number): 101 ''' 102 Printable E.164 (The international public telecommunication numbering plan 103 from ITU) numbers. 104 105 :param number: string 106 107 >>> e164(155542315678) 108 '+155542315678' 109 >>> e164('+31 20 5423 1567') 110 '+312054231567' 111 ''' 112 if isinstance(number, six.integer_types): 113 return e164('+%s' % number) 114 115 elif isinstance(number, six.string_types): 116 number = strip(number, '-. ()') 117 if number.startswith('+'): 118 number = number[1:] 119 120 return u'+%s' % number 121 122 123def enum(number, zone='e164.arpa'): 124 ''' 125 Printable DNS ENUM (telephone number mapping) record. 126 127 :param number: string 128 :param zone: string 129 130 131 >>> enum('+31 20 5423 1567') 132 '7.6.5.1.3.2.4.5.0.2.1.3.e164.arpa.' 133 >>> enum('+31 97 99 6642', zone='e164.spacephone.org') 134 '2.4.6.6.9.9.7.9.1.3.e164.spacephone.org.' 135 136 ''' 137 number = e164(number).lstrip('+') 138 return u'.'.join([ 139 u'.'.join(number[::-1]), 140 zone.strip(u'.'), 141 '', 142 ]) 143 144 145def imei(number): 146 ''' 147 Printable International Mobile Station Equipment Identity (IMEI) numbers. 148 149 :param number: string or int 150 151 >>> imei(12345678901234) 152 '12-345678-901234-7' 153 >>> imei(1234567890123456) 154 '12-345678-901234-56' 155 ''' 156 number = to_decimal(number) 157 length = len(number) 158 if length not in (14, 15, 16): 159 raise ValueError( 160 _('Invaid International Mobile Station Equipment Identity') 161 ) 162 163 if len(number) == 14: 164 # Add Luhn check digit 165 number = luhn_append(number) 166 167 groups = (number[:2], number[2:8], number[8:14], number[14:]) 168 return u'-'.join(list(filter(None, groups))) 169 170 171def imsi(number): 172 ''' 173 Printable International Mobile Subscriber Identity (IMSI) numbers. Mind 174 that there is no validation done on the actual correctness of the MCC/MNC. 175 If you wish to validate IMSI numbers, take a look at `python-stdnum`_. 176 177 :param number: string or int 178 179 >>> imsi(2042312345) 180 '204-23-12345' 181 182 .. _python-stdnum: https://pypi.python.org/pypi/python-stdnum/ 183 ''' 184 number = to_decimal(number) 185 groups = (number[:3], number[3:5], number[5:]) 186 return u'-'.join(list(filter(None, groups))) 187 188 189def meid(number, separator=u' '): 190 ''' 191 Printable Mobile Equipment Identifier (MEID) number. 192 193 >>> meid(123456789012345678) 194 '1B 69B4BA 630F34 6' 195 >>> meid('1B69B4BA630F34') 196 '1B 69B4BA 630F34 6' 197 ''' 198 199 if isinstance(number, six.string_types): 200 number = re.sub(r'[\s-]', '', number) 201 202 try: 203 number = '%014X' % int(number, 16) 204 except ValueError: 205 if len(number) < 18 and number.isdigit(): 206 return meid('%014X' % int(number), separator) 207 else: 208 raise ValueError(_('Invalid MEID, size mismatch')) 209 else: 210 if len(number) not in (14, 15): 211 raise ValueError(_('Invalid MEID, size mismatch')) 212 213 elif isinstance(number, six.integer_types): 214 if number > 0xfffffffffffffff: 215 raise ValueError(_('Invalid MEID, size mismatch')) 216 return meid(('%014X' % number)[:14], separator) 217 218 else: 219 raise TypeError(_('Invalid MEID, input type invalid')) 220 221 number = number.upper() 222 region = number[:2] 223 manufacturer = number[2:8] 224 serial_number = number[8:14] 225 check_digit = number[14:] 226 227 if check_digit == '': 228 check_digit = luhn_calc(number, chars='0123456789ABCDEF') 229 230 groups = (region, manufacturer, serial_number, check_digit) 231 return separator.join(list(filter(None, groups))) 232 233 234def pesn(number, separator=u''): 235 ''' 236 Printable Pseudo Electronic Serial Number. 237 238 :param number: hexadecimal string 239 240 >>> pesn('1B69B4BA630F34E') 241 '805F9EF7' 242 ''' 243 244 number = re.sub(r'[\s-]', '', meid(number)) 245 serial = hashlib.sha1(unhexlify(number[:14])) 246 return separator.join(['80', serial.hexdigest()[-6:].upper()]) 247