@@ -39,15 +39,134 @@ def _extract_json_data_attribute(html_text: str, attribute_name: str) -> dict:
3939 ) from exc
4040
4141 @staticmethod
42- def _ensure_ajax_success (response : dict , action : str ) -> dict :
42+ def _normalize_ajax_error_message (message : object ) -> str :
43+ return html .unescape (
44+ str (message or "" )
45+ .replace ("<br />" , "\n " )
46+ .replace ("<br \\ />" , "\n " )
47+ .replace ("<br/>" , "\n " )
48+ ).strip ()
49+
50+ @classmethod
51+ def _ensure_ajax_success (cls , response : dict , action : str ) -> dict :
4352 success = response .get ("success" )
44- error_message = html . unescape ( str ( response .get ("errmsg" , "" )). replace ( "<br />" , " " ). strip ( ))
53+ error_message = cls . _normalize_ajax_error_message ( response .get ("errmsg" ))
4554 if success == 1 :
4655 return response
4756 if error_message :
48- raise SteamResponseError ("{0} failed: {1}" .format (action , error_message ))
57+ raise SteamResponseError (
58+ "{0} failed: {1}" .format (action , " " .join (error_message .splitlines ()))
59+ )
4960 raise SteamResponseError ("{0} failed." .format (action ))
5061
62+ @staticmethod
63+ def _profile_edit_state_value (profile_edit_state : dict , field_name : str ):
64+ if field_name == "personaName" :
65+ return profile_edit_state .get ("strPersonaName" , "" )
66+ if field_name == "real_name" :
67+ return profile_edit_state .get ("strRealName" , "" )
68+ if field_name == "summary" :
69+ return profile_edit_state .get ("strSummary" , "" )
70+ if field_name == "customURL" :
71+ return profile_edit_state .get ("strCustomURL" , "" )
72+ if field_name == "country" :
73+ return (profile_edit_state .get ("LocationData" ) or {}).get ("locCountryCode" , "" )
74+ if field_name == "state" :
75+ return (profile_edit_state .get ("LocationData" ) or {}).get ("locStateCode" , "" )
76+ if field_name == "city" :
77+ return (profile_edit_state .get ("LocationData" ) or {}).get ("locCityCode" , "" )
78+ if field_name == "hide_profile_awards" :
79+ return int (bool ((profile_edit_state .get ("ProfilePreferences" ) or {}).get ("hide_profile_awards" , 0 )))
80+ return None
81+
82+ @staticmethod
83+ def _normalize_profile_edit_value (field_name : str , value ):
84+ if field_name == "hide_profile_awards" :
85+ return int (bool (value ))
86+ if value is None :
87+ return ""
88+ return str (value )
89+
90+ def _verify_profile_edit_updates (
91+ self ,
92+ steam_id : str ,
93+ requested_updates : Dict [str , object ],
94+ ) -> dict :
95+ profile_edit_state = self .get_profile_edit_state (steam_id )
96+ mismatches = {}
97+ for field_name , expected_value in requested_updates .items ():
98+ actual_value = self ._profile_edit_state_value (profile_edit_state , field_name )
99+ if self ._normalize_profile_edit_value (
100+ field_name ,
101+ actual_value ,
102+ ) != self ._normalize_profile_edit_value (field_name , expected_value ):
103+ mismatches [field_name ] = {
104+ "expected" : expected_value ,
105+ "actual" : actual_value ,
106+ }
107+ return {
108+ "profile_edit_state" : profile_edit_state ,
109+ "mismatches" : mismatches ,
110+ }
111+
112+ def _finalize_profile_edit_response (
113+ self ,
114+ steam_id : str ,
115+ response : dict ,
116+ requested_updates : Dict [str , object ],
117+ ) -> dict :
118+ success = response .get ("success" )
119+ if success == 1 :
120+ return response
121+ error_message = self ._normalize_ajax_error_message (response .get ("errmsg" ))
122+ if success != 2 :
123+ if error_message :
124+ raise SteamResponseError (
125+ "Profile update failed: {0}" .format (" " .join (error_message .splitlines ()))
126+ )
127+ raise SteamResponseError ("Profile update failed." )
128+
129+ try :
130+ verification = self ._verify_profile_edit_updates (steam_id , requested_updates )
131+ except Exception as exc :
132+ if error_message :
133+ raise SteamResponseError (
134+ "Profile update returned an ambiguous Steam response: {0}" .format (
135+ " " .join (error_message .splitlines ())
136+ )
137+ ) from exc
138+ raise SteamResponseError ("Profile update returned an ambiguous Steam response." ) from exc
139+
140+ if verification ["mismatches" ]:
141+ mismatch_details = ", " .join (
142+ "{0} expected {1!r} but Steam now reports {2!r}" .format (
143+ field_name ,
144+ details ["expected" ],
145+ details ["actual" ],
146+ )
147+ for field_name , details in verification ["mismatches" ].items ()
148+ )
149+ if error_message :
150+ raise SteamResponseError (
151+ "Profile update failed: {0} ({1})" .format (
152+ " " .join (error_message .splitlines ()),
153+ mismatch_details ,
154+ )
155+ )
156+ raise SteamResponseError (
157+ "Profile update failed: {0}" .format (mismatch_details )
158+ )
159+
160+ finalized = dict (response )
161+ finalized ["verified" ] = True
162+ finalized ["verified_fields" ] = sorted (requested_updates .keys ())
163+ finalized ["warnings" ] = [
164+ line .strip ()
165+ for line in error_message .splitlines ()
166+ if line .strip ()
167+ ]
168+ return finalized
169+
51170 def _resolved_steam_id (self , steam_id = None ) -> str :
52171 if steam_id is None :
53172 return self .transport .require_community_credentials ().steam_id
@@ -322,41 +441,44 @@ def edit_profile(
322441 country : Optional [str ] = None ,
323442 state : Optional [str ] = None ,
324443 city : Optional [Union [int , str ]] = None ,
325- hide_profile_awards : bool = False ,
444+ hide_profile_awards : Optional [ bool ] = None ,
326445 ) -> dict :
327446 credentials = self .transport .require_community_credentials ()
328447 normalized_steam_id = self ._resolved_steam_id (steam_id )
329448 data = {
330449 "sessionID" : credentials .session_id ,
331450 "type" : "profileSave" ,
332- "hide_profile_awards" : int (hide_profile_awards ),
333451 "json" : 1 ,
334452 }
453+ requested_updates = {}
335454 if persona_name is not None :
336- data ["personaName" ] = ensure_not_blank (persona_name , "persona_name" )
455+ normalized_persona_name = ensure_not_blank (persona_name , "persona_name" )
456+ data ["personaName" ] = normalized_persona_name
457+ requested_updates ["personaName" ] = normalized_persona_name
337458 if real_name is not None :
338459 data ["real_name" ] = real_name
460+ requested_updates ["real_name" ] = real_name
339461 if summary is not None :
340462 data ["summary" ] = summary
463+ requested_updates ["summary" ] = summary
341464 if custom_url is not None :
342465 data ["customURL" ] = custom_url
466+ requested_updates ["customURL" ] = custom_url
343467 if country is not None :
344468 data ["country" ] = country
469+ requested_updates ["country" ] = country
345470 if state is not None :
346471 data ["state" ] = state
472+ requested_updates ["state" ] = state
347473 if city is not None :
348474 data ["city" ] = str (city )
475+ requested_updates ["city" ] = str (city )
476+ if hide_profile_awards is not None :
477+ normalized_hide_profile_awards = int (bool (hide_profile_awards ))
478+ data ["hide_profile_awards" ] = normalized_hide_profile_awards
479+ requested_updates ["hide_profile_awards" ] = normalized_hide_profile_awards
349480
350- editable_fields = {
351- "personaName" ,
352- "real_name" ,
353- "summary" ,
354- "customURL" ,
355- "country" ,
356- "state" ,
357- "city" ,
358- }
359- if not any (field in data for field in editable_fields ):
481+ if not requested_updates :
360482 raise SteamValidationError ("edit_profile requires at least one editable field." )
361483
362484 response = self .transport .request (
@@ -366,7 +488,11 @@ def edit_profile(
366488 headers = self ._headers (f"{ COMMUNITY_BASE_URL } /profiles/{ normalized_steam_id } /edit/" ),
367489 cookies = self ._community_cookies (),
368490 )
369- return self ._ensure_ajax_success (response , "Profile update" )
491+ return self ._finalize_profile_edit_response (
492+ normalized_steam_id ,
493+ response ,
494+ requested_updates ,
495+ )
370496
371497 def upload_avatar (self , image_path : Union [str , Path ], steam_id = None ) -> dict :
372498 credentials = self .transport .require_community_credentials ()
0 commit comments