Encontré un código muy antiguo en GitHub. Con algunas modificaciones, funciona. Lo probé en algunas aplicaciones de la tienda de aplicaciones que tengo, y tuve éxito al obtener los appIds de todas ellas.
Se necesita tener instalado python3 + el módulo asn1
pip install asn1
luego guardar como appstore-url.py
y ejecutar por ejemplo:
$ appstore-url.py /Applications/Keynote.app
https://apps.apple.com/app/id409183694
código
#!/usr/bin/env python3
"""
Analiza _MASReceipt/receipt y muestra una URL de App Store
requiere el módulo asn1:
pip install asn1
original:
https://github.com/pudquick/pyMASreceipt
referencias:
https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
"""
import asn1
import os
import sys
from collections import namedtuple
import argparse
MASAttr = namedtuple('MASAttr', 'type version value')
MAS_TYPES = {
1: 'ID de Producto',
2: 'Identificador del Paquete',
3: 'Versión de la Aplicación',
4: 'Valor Opaco',
5: 'Hash SHA-1',
8: 'Fecha de Compra',
10: 'Clasificación de Contenido Parental',
16: 'ID de Versión de Instalador de la App Store',
17: 'Recibo de Compra en la App'
}
def unwind(input):
ret_val = []
while not input.eof():
tag = input.peek()
if tag[1] == 0x00:
tag, value = input.read()
ret_val.append((tag[2], tag[0], value))
elif tag[1] == 0x20:
ret_val.append((tag[2], tag[0]))
input.enter()
ret_val.append(unwind(input))
input.leave()
return ret_val
def extract_data(asn1_seq):
ret_val = []
for i, x in enumerate(asn1_seq):
if isinstance(x, tuple):
if len(x) == 3 and x[2] == '1.2.840.113549.1.7.1':
ret_val.append(asn1_seq[i + 2][0][2])
elif isinstance(x, list):
sub_val = extract_data(x)
if isinstance(sub_val, list):
ret_val.extend(sub_val)
else:
ret_val.append(sub_val)
return ret_val[0] if len(ret_val) == 1 else ret_val
def parse_receipt(rec):
ret_val = []
for y in [z for z in rec if isinstance(z, list)]:
try:
dec = asn1.Decoder()
dec.start(y[2][2])
ret_val.append(MASAttr(MAS_TYPES.get(y[0][2], f'0x{y[0][2]:02X}'), y[1][2], unwind(dec)[0][-1]))
except asn1.Error as e:
#print(f"Error al decodificar ASN.1: {e}", file=sys.stderr)
ret_val.append(MASAttr(MAS_TYPES.get(y[0][2], f'0x{y[0][2]:02X}'), y[1][2], y[2][2]))
return ret_val
def get_app_receipt(path_to_MAS_app):
receipt_path = os.path.join(path_to_MAS_app, "Contents/_MASReceipt/receipt")
if not os.path.isfile(receipt_path):
raise FileNotFoundError(f"Archivo de recibo no encontrado: {receipt_path}")
with open(receipt_path, 'rb') as f:
dec1 = asn1.Decoder()
dec1.start(f.read())
payload = extract_data(unwind(dec1))
dec2 = asn1.Decoder()
dec2.start(payload)
return parse_receipt(unwind(dec2)[1])
def main():
parser = argparse.ArgumentParser(description="Extrae el ID de Producto y devuelve una URL de AppStore analizando el MASReceipt.")
parser.add_argument("path", help="Ruta de la app MAS")
args = parser.parse_args()
try:
decoded_receipt = get_app_receipt(args.path)
for p in decoded_receipt:
if p.type == 'ID de Producto':
print(f'https://apps.apple.com/app/id{p.value}')
except FileNotFoundError as e:
print(e, file=sys.stderr)
except Exception as e:
print(f"error: {e}", file=sys.stderr)
if __name__ == "__main__":
main()