@@ -58,7 +58,9 @@ public class FriendInfo : INotifyPropertyChanged
5858 public string SteamId
5959 {
6060 get => _steamId ;
61- set { _steamId = value ; OnPropertyChanged ( ) ; }
61+ // Only overwrite if new value is non-empty so steamid64 written first isn't erased
62+ // by a subsequent empty "steamid" field in the same JSON object.
63+ set { if ( ! string . IsNullOrWhiteSpace ( value ) ) { _steamId = value ; OnPropertyChanged ( ) ; } }
6264 }
6365
6466 [ JsonProperty ( "steamid2" ) ]
@@ -71,6 +73,16 @@ public string? SteamId2
7173 }
7274 }
7375
76+ [ JsonProperty ( "steamid64" ) ]
77+ public string ? SteamId64
78+ {
79+ set
80+ {
81+ if ( ! string . IsNullOrWhiteSpace ( value ) && string . IsNullOrWhiteSpace ( SteamId ) )
82+ SteamId = value ;
83+ }
84+ }
85+
7486 [ JsonProperty ( "username" ) ]
7587 public string Username
7688 {
@@ -164,18 +176,26 @@ public class FriendsResponse
164176 public List < FriendInfo > ? Friends { get ; set ; }
165177 }
166178
167- public interface IEddies
179+ public class CCFriendsResponse
168180 {
169- [ Headers ( "User-Agent: ClassicCounter Wauncher" ) ]
170- [ Get ( "/friendsapi.php" ) ]
171- Task < string > GetFriends ( [ AliasAs ( "steamid64" ) ] string steamId64 ) ;
181+ [ JsonProperty ( "response" ) ]
182+ public List < FriendInfo > ? Response { get ; set ; }
183+ }
184+
185+ public class CCPlayerResponse
186+ {
187+ [ JsonProperty ( "response" ) ]
188+ public FriendInfo ? Response { get ; set ; }
189+ }
172190
191+ public interface IClassicCounterProfiles
192+ {
173193 [ Headers ( "User-Agent: ClassicCounter Wauncher" ) ]
174- [ Get ( "/friendsapi.php " ) ]
175- Task < string > GetFriendsBySteamId2 ( [ AliasAs ( "steamid2 " ) ] string steamId2 ) ;
194+ [ Get ( "/friends " ) ]
195+ Task < string > GetFriends ( [ AliasAs ( "steamid64 " ) ] string steamId64 ) ;
176196
177197 [ Headers ( "User-Agent: ClassicCounter Wauncher" ) ]
178- [ Get ( "/selfinfo.php " ) ]
198+ [ Get ( "/player " ) ]
179199 Task < string > GetSelfInfo ( [ AliasAs ( "steamid64" ) ] string steamId64 ) ;
180200 }
181201
@@ -204,34 +224,36 @@ private static HttpClient TimedClient(string baseUrl, int timeoutSeconds = 12) =
204224
205225 public static IGitHub GitHub = RestService . For < IGitHub > ( TimedClient ( "https://api.github.com" , 8 ) , _settings ) ;
206226 public static IClassicCounter ClassicCounter = RestService . For < IClassicCounter > ( TimedClient ( "https://classiccounter.cc/api" ) , _settings ) ;
207- public static IEddies Eddies = RestService . For < IEddies > ( "https://eddies .cc/api" , _settings ) ;
227+ public static IClassicCounterProfiles Profiles = RestService . For < IClassicCounterProfiles > ( TimedClient ( "https://classiccounter .cc/api/profiles" ) , _settings ) ;
208228
209229 public static List < FriendInfo > ParseFriendsPayload ( string ? json )
210230 {
211231 if ( string . IsNullOrWhiteSpace ( json ) )
212232 return new List < FriendInfo > ( ) ;
213233
234+ try
235+ {
236+ var cc = JsonConvert . DeserializeObject < CCFriendsResponse > ( json ) ;
237+ if ( cc ? . Response != null && cc . Response . Count > 0 )
238+ return NormalizeFriends ( cc . Response ) ;
239+ }
240+ catch { }
241+
214242 try
215243 {
216244 var wrapped = JsonConvert . DeserializeObject < FriendsResponse > ( json ) ;
217245 if ( wrapped ? . Friends != null && wrapped . Friends . Count > 0 )
218246 return NormalizeFriends ( wrapped . Friends ) ;
219247 }
220- catch
221- {
222- // Fall through to array parse.
223- }
248+ catch { }
224249
225250 try
226251 {
227252 var flat = JsonConvert . DeserializeObject < List < FriendInfo > > ( json ) ;
228253 if ( flat != null )
229254 return NormalizeFriends ( flat ) ;
230255 }
231- catch
232- {
233- // Ignore and return empty.
234- }
256+ catch { }
235257
236258 return new List < FriendInfo > ( ) ;
237259 }
@@ -243,29 +265,47 @@ public static List<FriendInfo> ParseFriendsPayload(string? json)
243265
244266 try
245267 {
246- var parsed = JsonConvert . DeserializeObject < FriendInfo > ( json ) ;
247- if ( parsed == null )
248- return null ;
249-
250- return NormalizeFriends ( new [ ] { parsed } ) . FirstOrDefault ( ) ;
268+ var cc = JsonConvert . DeserializeObject < CCPlayerResponse > ( json ) ;
269+ if ( cc ? . Response != null )
270+ return NormalizeFriends ( new [ ] { cc . Response } ) . FirstOrDefault ( ) ;
251271 }
252- catch
272+ catch { }
273+
274+ try
253275 {
254- return null ;
276+ var parsed = JsonConvert . DeserializeObject < FriendInfo > ( json ) ;
277+ if ( parsed != null )
278+ return NormalizeFriends ( new [ ] { parsed } ) . FirstOrDefault ( ) ;
255279 }
280+ catch { }
281+
282+ return null ;
256283 }
257284
258285 private static List < FriendInfo > NormalizeFriends ( IEnumerable < FriendInfo > friends )
259286 {
260287 var normalized = new List < FriendInfo > ( ) ;
288+ var usedKeys = new HashSet < string > ( StringComparer . Ordinal ) ;
289+
261290 foreach ( var f in friends )
262291 {
263292 var username = string . IsNullOrWhiteSpace ( f . Username ) ? "Unknown" : f . Username ;
264293 var status = NormalizeStatus ( f . Status ) ;
265294
295+ // Fall back to username as identity key when the API omits steamid.
296+ // Append an index suffix to guarantee uniqueness when two friends share a username.
297+ string key = ! string . IsNullOrWhiteSpace ( f . SteamId ) ? f . SteamId : username ;
298+ if ( ! usedKeys . Add ( key ) )
299+ {
300+ int n = 1 ;
301+ string unique ;
302+ while ( ! usedKeys . Add ( unique = $ "{ key } #{ n } ") ) n ++ ;
303+ key = unique ;
304+ }
305+
266306 normalized . Add ( new FriendInfo
267307 {
268- SteamId = f . SteamId ?? string . Empty ,
308+ SteamId = key ,
269309 Username = username ,
270310 AvatarUrl = f . AvatarUrl ?? string . Empty ,
271311 Status = status
0 commit comments