GroupVerify/Settings.cs
Astra 1f61958099 GroupVerify 0.2.0: Steam group / override verification with an API for other plugins
Players are verified when they're in the configured Steam group (public memberslistxml,
refetched every 10 minutes, kept on failure) or have an unexpired override row in
gv_verified, written by the web panel. Settings and data live in CS2-SimpleAdmin's MySQL
database (gv_verified, gv_settings, gv_migrations), reached through its API.

- css_gv_group <groupId64|0>, css_gv_enable, css_gv_disable (@css/root): verification runs
  only when enabled with a group set.
- css_verify <target>: why a player is or isn't verified. Admin and above (by immunity from
  SimpleAdmin's groups) plus Guardian, unless admin_overrides.json says otherwise.
- groupverify:api (shared/GroupVerifyApi): IsVerified, GetStatus, IsActive and
  OnVerifiedChanged. The API object lives in the shared assembly, so it survives hot
  reloads of GroupVerify.
2026-10-02 15:37:47 +01:00

81 lines
3.7 KiB
C#

namespace GroupVerify;
// The plugin's settings, from gv_settings. Immutable: a reload swaps in a new one.
internal sealed record Settings(
bool Enabled,
ulong GroupId64,
int RefreshMinutes,
int MinForcedRefreshSeconds,
int DbReloadSeconds,
TimeZoneInfo TimeZone,
string VerifyMinRank,
string[] VerifyExtraRanks)
{
// Written to gv_settings when missing. The rank defaults are our real SimpleAdmin groups (see
// the fork's Helper.StaffRanks and the web panel's site config). Only the names are set here:
// "Admin or higher" is decided by immunity read from sa_groups.
public static readonly (string Name, string Value)[] Defaults =
[
("enabled", "1"), // 0 turns verification off (css_gv_enable/css_gv_disable)
("group_id64", "0"), // the Steam group's 64-bit ID (103582791...); 0 turns verification off (css_gv_group)
("refresh_minutes", "10"), // how often the group's member list is fetched
("min_forced_refresh_seconds", "60"), // css_verify refetches/reloads only when older than this
("db_reload_seconds", "60"), // how often gv_verified and gv_settings are reread
("timezone", "UTC"), // must match CS2-SimpleAdmin's Timezone
("verify_min_rank", "#rank/admin"), // css_verify: this group's immunity or higher
("verify_extra_ranks", "#rank/guardian") // css_verify: these groups too, comma-separated
];
public static readonly Settings Default = Parse(new Dictionary<string, string>(), _ => { });
public static Settings Parse(IReadOnlyDictionary<string, string> values, Action<string> warn)
{
string Get(string name) =>
values.TryGetValue(name, out var value) ? value.Trim() : Defaults.First(d => d.Name == name).Value;
int GetInt(string name, int min)
{
if (int.TryParse(Get(name), out var value) && value >= min)
return value;
var fallback = int.Parse(Defaults.First(d => d.Name == name).Value);
warn($"gv_settings {name} must be a number of at least {min}; using {fallback}");
return fallback;
}
ulong groupId = 0;
var groupText = Get("group_id64");
if (groupText.Length > 0 && !ulong.TryParse(groupText, out groupId))
warn($"gv_settings group_id64 \"{groupText}\" isn't a 64-bit group ID; verification is off");
var timeZone = TimeZoneInfo.Utc;
var zoneName = Get("timezone");
try
{
timeZone = TimeZoneInfo.FindSystemTimeZoneById(zoneName);
}
catch (Exception ex) when (ex is TimeZoneNotFoundException or InvalidTimeZoneException)
{
warn($"gv_settings timezone \"{zoneName}\" isn't a known time zone; using UTC");
}
return new Settings(
Get("enabled") != "0",
groupId,
GetInt("refresh_minutes", 1),
GetInt("min_forced_refresh_seconds", 0),
GetInt("db_reload_seconds", 10),
timeZone,
Get("verify_min_rank"),
Get("verify_extra_ranks").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries));
}
// Verification only runs when enabled with a group set; otherwise nobody is verified.
public bool Active => Enabled && GroupId64 != 0;
// Now as a wall-clock time in the database's time zone, to the second (MySQL rounds fractions).
public DateTime Now()
{
var now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZone);
return DateTime.SpecifyKind(now.AddTicks(-(now.Ticks % TimeSpan.TicksPerSecond)), DateTimeKind.Unspecified);
}
}