GroupVerify 0.3.0: check players with the Steam Web API as they join, clan tag for other plugins

Steam rate-limits its community member lists by IP (429), so the whole-list download is gone.
Each player is checked as they join with ISteamUser/GetUserGroupList: a member's 'group' row is
added or refreshed, a non-member's deleted, and when Steam won't say (private profile, bad key,
outage) the database stands. A player is asked about at most once per
min_forced_refresh_seconds; css_verify rechecks its targets, offline SteamIDs too. Changing the
group clears the old group's rows. refresh_minutes is gone.

The Web API key goes in configs/plugins/GroupVerify/GroupVerify.json (SteamWebApiKey); every
other setting stays in the database.

New clan_tag setting and css_gv_clantag <tag|-> (@css/root): the scoreboard tag for verified
players without a rank, applied by CS2-Tags. The API gains ClanTag and OnClanTagChanged, so
shared/GroupVerifyApi changes: needs a server restart.
This commit is contained in:
Astra 2026-10-02 18:42:41 +01:00
parent 1f61958099
commit 10e5fe9f3c
11 changed files with 436 additions and 204 deletions

View file

@ -12,6 +12,9 @@ public interface IGroupVerifyBackend
/// <summary>See <see cref="IGroupVerifyApi.GetStatus"/>.</summary>
VerifyStatus GetStatus(ulong steamId64);
/// <summary>The configured clan tag; the bridge blanks it while inactive.</summary>
string ClanTag { get; }
}
/// <summary>
@ -34,9 +37,15 @@ public sealed class GroupVerifyBridge : IGroupVerifyApi
/// <inheritdoc />
public event Action<CCSPlayerController, VerifyStatus>? OnVerifiedChanged;
/// <inheritdoc />
public event Action? OnClanTagChanged;
/// <inheritdoc />
public bool IsActive => _backend?.IsActive ?? false;
/// <inheritdoc />
public string ClanTag => _backend is { IsActive: true } backend ? backend.ClanTag : "";
/// <inheritdoc />
public bool IsVerified(CCSPlayerController player)
{
@ -56,11 +65,36 @@ public sealed class GroupVerifyBridge : IGroupVerifyApi
/// <summary>Called by GroupVerify on load.</summary>
public void Attach(IGroupVerifyBackend backend) => _backend = backend;
/// <summary>Called by GroupVerify on unload; ignored if a newer backend has attached since.</summary>
/// <summary>
/// Called by GroupVerify on unload; ignored if a newer backend has attached since. Tells
/// subscribers the clan tag is gone, if there was one.
/// </summary>
public void Detach(IGroupVerifyBackend backend)
{
if (ReferenceEquals(_backend, backend))
_backend = null;
if (!ReferenceEquals(_backend, backend))
return;
var had = ClanTag;
_backend = null;
if (had.Length > 0)
RaiseClanTagChanged();
}
/// <summary>Called by GroupVerify on the game thread; returns handlers' exceptions, like RaiseVerifiedChanged.</summary>
public List<Exception> RaiseClanTagChanged()
{
var errors = new List<Exception>();
foreach (var handler in OnClanTagChanged?.GetInvocationList() ?? [])
{
try
{
((Action)handler)();
}
catch (Exception ex)
{
errors.Add(ex);
}
}
return errors;
}
/// <summary>

View file

@ -43,6 +43,19 @@ public interface IGroupVerifyApi
/// so handlers should be idempotent.
/// </summary>
event Action<CCSPlayerController, VerifyStatus>? OnVerifiedChanged;
/// <summary>
/// The scoreboard clan tag for verified players who have no rank tag of their own (gv_settings
/// clan_tag, set with css_gv_clantag or the web panel), up to 12 characters, without brackets.
/// Empty when none is set, and while <see cref="IsActive"/> is false.
/// </summary>
string ClanTag { get; }
/// <summary>
/// Fired on the game thread when <see cref="ClanTag"/> changes, including to or from empty as
/// GroupVerify is turned on or off, loaded or unloaded. Re-tag everyone when it fires.
/// </summary>
event Action? OnClanTagChanged;
}
/// <summary>A player's verification, and why.</summary>