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.
This commit is contained in:
commit
1f61958099
13 changed files with 1711 additions and 0 deletions
60
SteamGroup.cs
Normal file
60
SteamGroup.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using System.Xml.Linq;
|
||||
|
||||
namespace GroupVerify;
|
||||
|
||||
// Reads a Steam group's public member list (memberslistxml), runs off the game thread.
|
||||
internal static class SteamGroup
|
||||
{
|
||||
// 1000 members a page, so this allows groups of up to 100,000.
|
||||
private const int MaxPages = 100;
|
||||
// A pause between pages, to stay clear of Steam's rate limit.
|
||||
private static readonly TimeSpan PageDelay = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
private static readonly HttpClient Http = CreateClient();
|
||||
|
||||
private static HttpClient CreateClient()
|
||||
{
|
||||
var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("GroupVerify/1.0 (CounterStrikeSharp)");
|
||||
return client;
|
||||
}
|
||||
|
||||
// Every member's SteamID64, following nextPageLink. Throws if any page fails, so a partial list
|
||||
// never replaces a whole one.
|
||||
public static async Task<HashSet<ulong>> FetchMembersAsync(ulong groupId64, CancellationToken cancel)
|
||||
{
|
||||
var members = new HashSet<ulong>();
|
||||
Uri? url = new($"https://steamcommunity.com/gid/{groupId64}/memberslistxml/?xml=1");
|
||||
|
||||
for (var page = 1; url != null; page++)
|
||||
{
|
||||
if (page > MaxPages)
|
||||
throw new InvalidDataException($"the member list has more than {MaxPages} pages");
|
||||
if (page > 1)
|
||||
await Task.Delay(PageDelay, cancel);
|
||||
|
||||
var root = XDocument.Parse(await Http.GetStringAsync(url, cancel)).Root;
|
||||
if (root?.Name.LocalName != "memberList")
|
||||
throw new InvalidDataException($"page {page} isn't a member list (<{root?.Name.LocalName}>)");
|
||||
|
||||
foreach (var id in root.Element("members")?.Elements("steamID64") ?? [])
|
||||
{
|
||||
if (ulong.TryParse(id.Value.Trim(), out var steamId))
|
||||
members.Add(steamId);
|
||||
}
|
||||
|
||||
url = NextPage(root.Element("nextPageLink")?.Value);
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
// Only ever follows links back to steamcommunity.com, over https.
|
||||
private static Uri? NextPage(string? link)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(link) || !Uri.TryCreate(link.Trim(), UriKind.Absolute, out var uri))
|
||||
return null;
|
||||
if (uri.Host != "steamcommunity.com" || (uri.Scheme != Uri.UriSchemeHttps && uri.Scheme != Uri.UriSchemeHttp))
|
||||
throw new InvalidDataException($"unexpected nextPageLink {uri}");
|
||||
return new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps, Port = -1 }.Uri;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue