@@ -209,14 +209,22 @@ def read_analytics_file(package_status_id, package_id, link, session):
209209 All this data will be useful to parse more complex things later.
210210 '''
211211
212- user_path = find_user_root (zip .namelist ())
213- if not user_path :
214- # Fallback to traditional paths
212+ user_root = find_user_root (zip .namelist ())
213+ if user_root :
214+ user_path = f'{ user_root } /user.json'
215+ elif 'Account/user.json' in zip .namelist ():
215216 user_path = 'Account/user.json'
216- if user_path not in zip . namelist () and 'account/user.json' in zip .namelist ():
217- user_path = 'account/user.json'
217+ elif 'account/user.json' in zip .namelist ():
218+ user_path = 'account/user.json'
218219 else :
219- user_path = f'{ user_path } /user.json'
220+ # Discord exports drop user.json (and the entire Account folder)
221+ # entirely when the user un-ticks the "User data" option in the
222+ # request form. Without it the package has no owner identity to
223+ # anchor the analysis on, so there's nothing useful to compute.
224+ # Surface this as a distinct code so the UI can prompt them to
225+ # re-export with that option enabled instead of bouncing them
226+ # with the generic UNKNOWN_ERROR.
227+ raise Exception ('MISSING_USER_DATA' )
220228 user_content = zip .open (user_path )
221229 user_json = orjson .loads (user_content .read ())
222230 user_data = {
@@ -993,7 +1001,7 @@ def process_package(package_status_id, package_id, link, worker_name='regular_pr
9931001 # just the string 'EXPIRED_LINK' (Python tuples need a comma);
9941002 # the `in` check then accidentally did substring match on a
9951003 # single code, hiding INVALID_LINK behind UNKNOWN_ERROR.
996- EXPECTED_ERROR_CODES = ('EXPIRED_LINK' , 'INVALID_LINK' )
1004+ EXPECTED_ERROR_CODES = ('EXPIRED_LINK' , 'INVALID_LINK' , 'MISSING_USER_DATA' )
9971005 current = str (e )
9981006 e_traceback = None
9991007 if current not in EXPECTED_ERROR_CODES :
0 commit comments