Verification: clan tag setting, GroupVerify 0.3.0 wording

Root sets GroupVerify's clan_tag (the scoreboard tag CS2-Tags gives verified players without a
rank) next to the Steam group. Validated like the plugin (12 UTF-16 units, no quotes, ; or \),
saved to gv_settings and sent as css_gv_clantag when the plugin is loaded. Only changed fields
are sent.

GroupVerify 0.3.0 checks players with the Steam Web API as they join, so the group list is now
"Confirmed group members" with first/last confirmed dates.
This commit is contained in:
Astra 2026-10-02 18:44:44 +01:00
parent 9e4bc63201
commit 3c62874f91
6 changed files with 102 additions and 17 deletions

View file

@ -380,7 +380,7 @@ function verify() {
<section class="panel" style="margin-top:18px">
<div class="toolbar">
${searchBox("Search by name or SteamID64", state.query)}
${segmented("verifyView", state.verifyView, [["overrides", "Overrides"], ["group", "Steam group members"]])}
${segmented("verifyView", state.verifyView, [["overrides", "Overrides"], ["group", "Confirmed group members"]])}
</div>
<div class="table-wrap" id="results">${loading()}</div>
</section>`;
@ -415,16 +415,21 @@ function verifyStatus(st) {
<dl class="kv">
<dt>Plugin</dt><dd>${PLUGIN_STATE[st.plugin](st.version)}</dd>
<dt>Steam group</dt><dd>${group}</dd>
<dt>Group members</dt><dd>${st.groupMembers}${st.groupFetched ? ` <span class="muted">list saved ${fmtAgo(st.groupFetched)}</span>` : ' <span class="faint">never fetched</span>'}</dd>
<dt>Clan tag</dt><dd>${st.clanTag ? `<b>${esc(st.clanTag)}</b> <span class="muted">for verified players without a rank; unverified ones get none</span>` : '<span class="faint">None: players keep their own tags</span>'}</dd>
<dt>Group members</dt><dd>${st.groupMembers}${st.groupFetched ? ` <span class="muted">last confirmed with Steam ${fmtAgo(st.groupFetched)}</span>` : ' <span class="faint">nobody confirmed yet</span>'}</dd>
<dt>Overrides</dt><dd>${st.overrides}</dd>
<dt>Changes apply</dt><dd>Within ${st.reloadSeconds} seconds <span class="muted">(times in ${esc(st.timezone)})</span></dd>
</dl>
${st.canConfigure ? `<form id="gv-form" style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end;margin-top:16px">
<label class="field" style="flex:1;min-width:240px"><span>Steam group ID64</span>
<input class="input" name="group" value="${esc(st.groupId)}" inputmode="numeric" pattern="[0-9]{0,20}" placeholder="103582791…, blank or 0 turns verification off">
<label class="field" style="flex:2;min-width:240px"><span>Steam group ID64</span>
<input class="input" name="group" value="${esc(st.groupId)}" data-was="${esc(st.groupId)}" inputmode="numeric" pattern="[0-9]{0,20}" placeholder="103582791…, blank or 0 turns verification off">
<small>&lt;groupID64&gt; on https://steamcommunity.com/groups/&lt;name&gt;/memberslistxml/?xml=1</small>
</label>
<button class="btn" type="submit">Save group</button>
<label class="field" style="flex:1;min-width:160px"><span>Clan tag</span>
<input class="input" name="clantag" value="${esc(st.clanTag)}" data-was="${esc(st.clanTag)}" maxlength="12" pattern="[^&quot;;\\\\]*" placeholder="none">
<small>Scoreboard tag for verified players without a rank. Blank: none.</small>
</label>
<button class="btn" type="submit">Save</button>
<button class="btn ${st.enabled ? "danger" : "primary"}" type="button" data-act="gv-toggle" data-on="${st.enabled ? "0" : "1"}">${st.enabled ? "Turn off" : "Turn on"}</button>
</form>` : '<p class="muted" style="margin:16px 0 0">Only Root can change the group or turn verification on and off.</p>'}
</div>
@ -437,13 +442,13 @@ function verifyResults(d) {
const groupView = state.verifyView === "group";
if (!list.items.length) {
if (state.query) return `<div class="empty">Nobody matches "${esc(state.query)}".</div>`;
return `<div class="empty">${groupView ? "No group members saved yet. GroupVerify saves the list each time it fetches it." : "No overrides."}</div>`;
return `<div class="empty">${groupView ? "Nobody confirmed yet. GroupVerify checks players with Steam as they join." : "No overrides."}</div>`;
}
const mayEdit = (sid) => d.status.canOverride && (sid !== state.me.steamid || can("@css/root"));
const who = (e) => `<td><div class="who"><div><b><a href="#" data-player="${esc(e.steamid)}">${e.name ? esc(e.name) : '<span class="faint">Never joined</span>'}</a></b><span class="sid">${esc(e.steamid)}</span></div></div></td>`;
if (groupView) {
return `<table>
<thead><tr><th>Player</th><th>In the group since</th><th class="hide-sm">Last seen in the list</th></tr></thead>
<thead><tr><th>Player</th><th>First confirmed</th><th class="hide-sm">Last confirmed</th></tr></thead>
<tbody>${list.items.map((e) => `<tr>${who(e)}<td>${fmtDate(e.created)}</td><td class="hide-sm muted">${fmtAgo(e.seen)}</td></tr>`).join("")}</tbody>
</table>${pager(list)}`;
}
@ -473,7 +478,7 @@ function verifySection(v, sid, name) {
<h3>Verification</h3>
<p style="margin:0 0 10px">${badge}</p>
<dl class="kv">
<dt>Steam group</dt><dd>${v.inGroup ? `Member <span class="muted">seen in the list ${fmtAgo(v.groupSeen)}</span>` : '<span class="faint">Not a member</span>'}</dd>
<dt>Steam group</dt><dd>${v.inGroup ? `Member <span class="muted">confirmed with Steam ${fmtAgo(v.groupSeen)}</span>` : '<span class="faint">Not confirmed as a member</span>'}</dd>
<dt>Override</dt><dd>${o ? `${o.expires ? `Until ${fmtDate(o.expires)}` : "No expiry"}${o.addedBy ? ` <span class="muted">from ${esc(o.addedBy)}</span>` : ""}` : '<span class="faint">None</span>'}</dd>
</dl>
${v.canOverride ? `<div style="display:flex;gap:8px;margin-top:12px">
@ -940,7 +945,12 @@ document.addEventListener("submit", async (e) => {
const f = form.elements;
if (form.id === "gv-form") {
await act("/api/admin/verify/settings", "POST", { groupId: f.group.value.trim() });
// Only what changed, so saving the tag doesn't resend the group (and the other way round).
const body = {};
if (f.group.value.trim() !== f.group.dataset.was) body.groupId = f.group.value.trim();
if (f.clantag.value.trim() !== f.clantag.dataset.was) body.clanTag = f.clantag.value.trim();
if (!Object.keys(body).length) { toast("Nothing changed."); return; }
await act("/api/admin/verify/settings", "POST", body);
return;
}