mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-27 07:58:45 -07:00
Enhance global friends functionality: suppress auto-generated placeholder accounts in synthesized friend lists and update documentation for clarity.
This commit is contained in:
@@ -100,12 +100,13 @@ public class RelationshipsController(DorkNetDbContext db, NotificationService no
|
|||||||
|
|
||||||
var others = await db.Players
|
var others = await db.Players
|
||||||
.Where(p => p.Id != me && p.Id != RelationshipQueries.SystemAccountId)
|
.Where(p => p.Id != me && p.Id != RelationshipQueries.SystemAccountId)
|
||||||
.Select(p => p.Id)
|
.Select(p => new { p.Id, p.Username })
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var wire = new List<Dictionary<string, object>>(others.Count);
|
var wire = new List<Dictionary<string, object>>(others.Count);
|
||||||
foreach (var otherId in others)
|
foreach (var other in others)
|
||||||
{
|
{
|
||||||
|
var otherId = other.Id;
|
||||||
if (realByOther.TryGetValue(otherId, out var row))
|
if (realByOther.TryGetValue(otherId, out var row))
|
||||||
{
|
{
|
||||||
// A real block wins — that pair is genuinely not friends.
|
// A real block wins — that pair is genuinely not friends.
|
||||||
@@ -114,6 +115,9 @@ public class RelationshipsController(DorkNetDbContext db, NotificationService no
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Hide auto-generated Player_NNN placeholders from the
|
||||||
|
// synthesized list (they never set a real username).
|
||||||
|
if (RelationshipQueries.IsPlaceholderUsername(other.Username)) continue;
|
||||||
wire.Add(SynthFriend(otherId, null, me));
|
wire.Add(SynthFriend(otherId, null, me));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,11 +152,17 @@ public class RelationshipsController(DorkNetDbContext db, NotificationService no
|
|||||||
var row = await FindAsync(me, playerId);
|
var row = await FindAsync(me, playerId);
|
||||||
|
|
||||||
// Global-friends mode: report Friend for any real account that isn't
|
// Global-friends mode: report Friend for any real account that isn't
|
||||||
// the system account and isn't explicitly blocked.
|
// the system account and isn't explicitly blocked. Placeholder
|
||||||
|
// Player_NNN accounts are hidden unless there's a genuine row.
|
||||||
if (playerId != RelationshipQueries.SystemAccountId
|
if (playerId != RelationshipQueries.SystemAccountId
|
||||||
&& (row is null || row.Status != EntityStatus.Blocked)
|
&& (row is null || row.Status != EntityStatus.Blocked)
|
||||||
&& await settings.IsGlobalFriendsEnabledAsync())
|
&& await settings.IsGlobalFriendsEnabledAsync())
|
||||||
return Ok(SynthFriend(playerId, row, me));
|
{
|
||||||
|
var isPlaceholder = row is null && RelationshipQueries.IsPlaceholderUsername(
|
||||||
|
await db.Players.Where(p => p.Id == playerId)
|
||||||
|
.Select(p => p.Username).FirstOrDefaultAsync());
|
||||||
|
if (!isPlaceholder) return Ok(SynthFriend(playerId, row, me));
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(row is null ? EmptyRelationship(playerId) : BuildRelationship(row, me));
|
return Ok(row is null ? EmptyRelationship(playerId) : BuildRelationship(row, me));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,30 @@ public static class RelationshipQueries
|
|||||||
/// friend in anyone's list.</summary>
|
/// friend in anyone's list.</summary>
|
||||||
public const long SystemAccountId = 1;
|
public const long SystemAccountId = 1;
|
||||||
|
|
||||||
|
/// <summary>True when <paramref name="username"/> is still an
|
||||||
|
/// auto-generated placeholder name (<c>Player_</c> followed by digits
|
||||||
|
/// only — see <c>PlayerService.GenerateUsername</c>). These are accounts
|
||||||
|
/// that never set a real username; the global-friends toggle hides them
|
||||||
|
/// from the synthesized "everyone" list so they don't clutter it. A
|
||||||
|
/// genuine Friend row still surfaces them — only the synthetic pairing is
|
||||||
|
/// suppressed.</summary>
|
||||||
|
public static bool IsPlaceholderUsername(string? username)
|
||||||
|
{
|
||||||
|
const string prefix = "Player_";
|
||||||
|
if (string.IsNullOrEmpty(username) ||
|
||||||
|
!username.StartsWith(prefix, StringComparison.Ordinal)) return false;
|
||||||
|
var suffix = username.AsSpan(prefix.Length);
|
||||||
|
if (suffix.IsEmpty) return false;
|
||||||
|
foreach (var c in suffix)
|
||||||
|
if (!char.IsAsciiDigit(c)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>The effective friend ids for <paramref name="me"/>. With the
|
/// <summary>The effective friend ids for <paramref name="me"/>. With the
|
||||||
/// global-friends toggle off this is the real Friend graph; with it on
|
/// global-friends toggle off this is the real Friend graph; with it on
|
||||||
/// it's every other (non-system, non-blocked) account.</summary>
|
/// it's every other (non-system, non-blocked) account, excluding
|
||||||
|
/// placeholder <c>Player_NNN</c> accounts that aren't already real
|
||||||
|
/// friends.</summary>
|
||||||
public static async Task<List<long>> EffectiveFriendIdsAsync(
|
public static async Task<List<long>> EffectiveFriendIdsAsync(
|
||||||
DorkNetDbContext db, ServerSettingsService settings, long me)
|
DorkNetDbContext db, ServerSettingsService settings, long me)
|
||||||
{
|
{
|
||||||
@@ -53,10 +74,21 @@ public static class RelationshipQueries
|
|||||||
(r.RequesterId == me || r.TargetId == me))
|
(r.RequesterId == me || r.TargetId == me))
|
||||||
.Select(r => r.RequesterId == me ? r.TargetId : r.RequesterId)
|
.Select(r => r.RequesterId == me ? r.TargetId : r.RequesterId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
var realFriendSet = (await db.Relationships
|
||||||
|
.Where(r => r.Status == RelationshipStatus.Friend &&
|
||||||
|
(r.RequesterId == me || r.TargetId == me))
|
||||||
|
.Select(r => r.RequesterId == me ? r.TargetId : r.RequesterId)
|
||||||
|
.ToListAsync()).ToHashSet();
|
||||||
|
|
||||||
return await db.Players
|
var candidates = await db.Players
|
||||||
.Where(p => p.Id != me && p.Id != SystemAccountId && !blocked.Contains(p.Id))
|
.Where(p => p.Id != me && p.Id != SystemAccountId && !blocked.Contains(p.Id))
|
||||||
.Select(p => p.Id)
|
.Select(p => new { p.Id, p.Username })
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Suppress placeholder accounts unless they're an actual friend.
|
||||||
|
return candidates
|
||||||
|
.Where(p => realFriendSet.Contains(p.Id) || !IsPlaceholderUsername(p.Username))
|
||||||
|
.Select(p => p.Id)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,9 @@ rows — the friend graph is synthesized at read time
|
|||||||
(`RelationshipQueries.EffectiveFriendIdsAsync`, gated on
|
(`RelationshipQueries.EffectiveFriendIdsAsync`, gated on
|
||||||
`ServerSettings.GlobalFriendsEnabled`), so flipping it off reverts
|
`ServerSettings.GlobalFriendsEnabled`), so flipping it off reverts
|
||||||
instantly. Blocks are still honored and the system/coach account is
|
instantly. Blocks are still honored and the system/coach account is
|
||||||
excluded. Flipping it broadcasts `RelationshipsInvalid` to every connected
|
excluded, as are auto-generated `Player_NNN` placeholder accounts that
|
||||||
|
never set a real username (they only appear if there's a genuine friend
|
||||||
|
row). Flipping it broadcasts `RelationshipsInvalid` to every connected
|
||||||
watch, which calls `Relationships.RefreshList` and re-pulls
|
watch, which calls `Relationships.RefreshList` and re-pulls
|
||||||
`api/relationships/v2/get` — so players see everyone appear (or disappear)
|
`api/relationships/v2/get` — so players see everyone appear (or disappear)
|
||||||
**without relogging**. It covers the friends list, the friends-online HUD,
|
**without relogging**. It covers the friends list, the friends-online HUD,
|
||||||
|
|||||||
Reference in New Issue
Block a user