@@ -456,36 +456,59 @@ def read_analytics_file(package_status_id, package_id, link, session):
456456
457457 namelist = zip .namelist ()
458458 servers_root = find_servers_root (namelist )
459- if not servers_root :
459+ # Resolve server_path defensively: only assign a path that actually
460+ # exists in the zip. Don't fall through to the canonical literal
461+ # 'Servers/index.json' and let zip.open KeyError — that's the
462+ # UNKNOWN_ERROR users hit when they un-tick "Servers" in Discord's
463+ # data-request form.
464+ if servers_root :
465+ server_path = f'{ servers_root } /index.json'
466+ elif 'Servers/index.json' in namelist :
460467 server_path = 'Servers/index.json'
461- if server_path not in namelist and 'servers/index.json' in namelist :
462- server_path = 'servers/index.json'
468+ elif 'servers/index.json' in namelist :
469+ server_path = 'servers/index.json'
463470 else :
464- server_path = f'{ servers_root } /index.json'
465-
466- server_content = zip .open (server_path )
467- server_json = orjson .loads (server_content .read ())
468- for guild_id in server_json :
469- guilds .append ({
470- 'id' : guild_id ,
471- 'name' : server_json [guild_id ]
472- })
471+ server_path = None
472+
473+ if server_path :
474+ server_content = zip .open (server_path )
475+ server_json = orjson .loads (server_content .read ())
476+ for guild_id in server_json :
477+ guilds .append ({
478+ 'id' : guild_id ,
479+ 'name' : server_json [guild_id ]
480+ })
481+ else :
482+ # Server data missing — DMs, friends, payments, profile still
483+ # process. Mark partial so the frontend can render server-scoped
484+ # stats as N/A (issue dumpus-app/dumpus-app#232 covers the banner).
485+ print ('No Servers/index.json found — skipping server data; package is partial.' )
486+ is_partial = True
473487
474488 '''
475489 Read Channels Data.
476490 This will be used later to get the channel name from the channel_id (or to check whether it is a DM or a Guild Channel).
477491 '''
478492
479493 messages_root = find_messages_root (namelist )
480- if not messages_root :
494+ # Same defensive resolution as servers above.
495+ if messages_root :
496+ message_index_path = f'{ messages_root } /index.json'
497+ elif 'Messages/index.json' in namelist :
481498 message_index_path = 'Messages/index.json'
482- if message_index_path not in namelist and 'messages/index.json' in namelist :
483- message_index_path = 'messages/index.json'
499+ elif 'messages/index.json' in namelist :
500+ message_index_path = 'messages/index.json'
484501 else :
485- message_index_path = f'{ messages_root } /index.json'
486-
487- message_index_content = zip .open (message_index_path )
488- message_index_json = orjson .loads (message_index_content .read ())
502+ message_index_path = None
503+
504+ if message_index_path :
505+ message_index_content = zip .open (message_index_path )
506+ message_index_json = orjson .loads (message_index_content .read ())
507+ else :
508+ print ('No Messages/index.json found — skipping channel data; package is partial.' )
509+ is_partial = True
510+ message_index_json = {}
511+
489512 for channel_id in message_index_json :
490513 full_name = message_index_json [channel_id ]
491514 if full_name is None :
0 commit comments