From fcb9c28099a4b7288657d668c75f9cb188b9f6bf Mon Sep 17 00:00:00 2001 From: Androz2091 Date: Thu, 7 May 2026 07:40:14 -0700 Subject: [PATCH] fix(worker): graceful skip when Servers or Messages data is absent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user reported UNKNOWN_ERROR for package 4aa23a2a6ea2f52ba9016a723143b792. Traceback: File "/app/tasks.py", line 466, in read_analytics_file server_content = zip.open(server_path) KeyError: "There is no item named 'Servers/index.json' in the archive" Same fallback bug that PR #82 fixed for the analytics path: when the discovered root is None, the old code blindly assigned the canonical literal 'Servers/index.json' (resp. 'Messages/index.json') and let zip.open KeyError if that file isn't actually in the archive. That happens when the user un-ticks 'Servers' (or 'Messages') in Discord's data-request form. Fix: only assign a path that actually exists in zip.namelist(); if nothing matches, set it to None and skip the read block, marking is_partial=True. The rest of the worker copes with empty servers / empty channels lists (the message-CSV walker filters by namelist anyway, so it just processes 0 channels). After this: - No Servers folder → no guild-scoped stats but DMs/friends/payments OK - No Messages folder → no channel-scoped stats but server list/payments OK - Both missing → minimal package, but no crash --- src/tasks.py | 61 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/tasks.py b/src/tasks.py index 798b7e1..bd73a3a 100644 --- a/src/tasks.py +++ b/src/tasks.py @@ -456,20 +456,34 @@ def read_analytics_file(package_status_id, package_id, link, session): namelist = zip.namelist() servers_root = find_servers_root(namelist) - if not servers_root: + # Resolve server_path defensively: only assign a path that actually + # exists in the zip. Don't fall through to the canonical literal + # 'Servers/index.json' and let zip.open KeyError — that's the + # UNKNOWN_ERROR users hit when they un-tick "Servers" in Discord's + # data-request form. + if servers_root: + server_path = f'{servers_root}/index.json' + elif 'Servers/index.json' in namelist: server_path = 'Servers/index.json' - if server_path not in namelist and 'servers/index.json' in namelist: - server_path = 'servers/index.json' + elif 'servers/index.json' in namelist: + server_path = 'servers/index.json' else: - server_path = f'{servers_root}/index.json' - - server_content = zip.open(server_path) - server_json = orjson.loads(server_content.read()) - for guild_id in server_json: - guilds.append({ - 'id': guild_id, - 'name': server_json[guild_id] - }) + server_path = None + + if server_path: + server_content = zip.open(server_path) + server_json = orjson.loads(server_content.read()) + for guild_id in server_json: + guilds.append({ + 'id': guild_id, + 'name': server_json[guild_id] + }) + else: + # Server data missing — DMs, friends, payments, profile still + # process. Mark partial so the frontend can render server-scoped + # stats as N/A (issue dumpus-app/dumpus-app#232 covers the banner). + print('No Servers/index.json found — skipping server data; package is partial.') + is_partial = True ''' Read Channels Data. @@ -477,15 +491,24 @@ def read_analytics_file(package_status_id, package_id, link, session): ''' messages_root = find_messages_root(namelist) - if not messages_root: + # Same defensive resolution as servers above. + if messages_root: + message_index_path = f'{messages_root}/index.json' + elif 'Messages/index.json' in namelist: message_index_path = 'Messages/index.json' - if message_index_path not in namelist and 'messages/index.json' in namelist: - message_index_path = 'messages/index.json' + elif 'messages/index.json' in namelist: + message_index_path = 'messages/index.json' else: - message_index_path = f'{messages_root}/index.json' - - message_index_content = zip.open(message_index_path) - message_index_json = orjson.loads(message_index_content.read()) + message_index_path = None + + if message_index_path: + message_index_content = zip.open(message_index_path) + message_index_json = orjson.loads(message_index_content.read()) + else: + print('No Messages/index.json found — skipping channel data; package is partial.') + is_partial = True + message_index_json = {} + for channel_id in message_index_json: full_name = message_index_json[channel_id] if full_name is None: