Hash the 40k hidden-players list and drop the old PayPal block
Deploy 40k-rankings / build-and-deploy (push) Successful in 4s
Deploy 40k-rankings / build-and-deploy (push) Successful in 4s
Hidden players are now matched by a hash of their full name or nickname (src/hide.js) so the names aren't readable in the source or bundle. Also removes the commented-out PayPal section from the Kingmaker sign-up page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,8 +53,11 @@ Everything you'd normally change is in the `CONFIG` block at the top of
|
|||||||
- `teamId`: the BCP team whose members are shown
|
- `teamId`: the BCP team whose members are shown
|
||||||
- `leagueId` / `regionId`: which ITC league and region to rank against
|
- `leagueId` / `regionId`: which ITC league and region to rank against
|
||||||
- `lmcLeagueId`: the Lord Marshal Conference league used for the LMC columns
|
- `lmcLeagueId`: the Lord Marshal Conference league used for the LMC columns
|
||||||
- `blacklistNames`: players to hide from the board (full name or nickname,
|
- `hiddenNameHashes`: players to hide from the board, stored as hashes of
|
||||||
case-insensitive)
|
their full name or nickname so the names aren't readable in the source. The
|
||||||
|
comment at the top of `src/hide.js` has a one-line command to hash a name.
|
||||||
|
This only obfuscates: the roster is public, so anyone could hash every
|
||||||
|
member's name to find who's hidden.
|
||||||
- `memberIds`: fallback roster, used if the team endpoint ever stops returning
|
- `memberIds`: fallback roster, used if the team endpoint ever stops returning
|
||||||
members without auth
|
members without auth
|
||||||
- `useSample`: set to `true` to render placeholder rows with no network calls
|
- `useSample`: set to `true` to render placeholder rows with no network calls
|
||||||
@@ -62,6 +65,7 @@ Everything you'd normally change is in the `CONFIG` block at the top of
|
|||||||
## Files
|
## Files
|
||||||
|
|
||||||
- `index.html`, `src/main.js`, `src/style.css`: the page
|
- `index.html`, `src/main.js`, `src/style.css`: the page
|
||||||
|
- `src/hide.js`: name hashing for `hiddenNameHashes`
|
||||||
- `nav.html`: this site's header nav entries (see the root README)
|
- `nav.html`: this site's header nav entries (see the root README)
|
||||||
- `public/elo-data.json`: generated ELO data. Don't edit it by hand; the
|
- `public/elo-data.json`: generated ELO data. Don't edit it by hand; the
|
||||||
weekly sync overwrites it.
|
weekly sync overwrites it.
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* Hashing for CONFIG.hiddenNameHashes, so hidden players' names aren't
|
||||||
|
readable in the source or the deployed bundle. This is obfuscation, not
|
||||||
|
secrecy: the team roster is public, so anyone could hash every member's
|
||||||
|
name and find a match.
|
||||||
|
|
||||||
|
Get the hash for a name (full name or nickname) from sites/40k-rankings:
|
||||||
|
node --input-type=module -e "import { nameHash } from './src/hide.js'; console.log(nameHash('First Last'))"
|
||||||
|
*/
|
||||||
|
|
||||||
|
// cyrb53: small, fast, synchronous 53-bit string hash.
|
||||||
|
function cyrb53(str, seed = 0) {
|
||||||
|
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const ch = str.charCodeAt(i);
|
||||||
|
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||||
|
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||||
|
}
|
||||||
|
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||||
|
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||||
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case- and whitespace-insensitive, so "jane DOE " matches "Jane Doe".
|
||||||
|
export function nameHash(name) {
|
||||||
|
const norm = String(name || "").trim().replace(/\s+/g, " ").toLowerCase();
|
||||||
|
return norm ? cyrb53(norm).toString(16) : "";
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { nameHash } from './hide.js';
|
||||||
|
|
||||||
/* ---- Shared: header / footer ---- */
|
/* ---- Shared: header / footer ---- */
|
||||||
function loadHTML(file, elementId, callback) {
|
function loadHTML(file, elementId, callback) {
|
||||||
fetch(file)
|
fetch(file)
|
||||||
@@ -48,19 +50,20 @@ const CONFIG = {
|
|||||||
// Fallback roster if the team endpoint requires auth.
|
// Fallback roster if the team endpoint requires auth.
|
||||||
memberIds: [],
|
memberIds: [],
|
||||||
|
|
||||||
// Names to hide from the leaderboard entirely (case-insensitive).
|
// Players to hide from the leaderboard entirely, as nameHash() values of
|
||||||
// Match on full name ("First Last") or nickname, whichever the player goes by.
|
// their full name ("First Last") or nickname — see src/hide.js for how to
|
||||||
blacklistNames: ["Hidden Player"],
|
// generate one. Hashed so the names aren't readable in the source.
|
||||||
|
hiddenNameHashes: ["5b64057a31512"],
|
||||||
|
|
||||||
// Flip to true to preview the layout with placeholder rows (no network).
|
// Flip to true to preview the layout with placeholder rows (no network).
|
||||||
useSample: false,
|
useSample: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const BLACKLIST = new Set(CONFIG.blacklistNames.map(n => n.trim().toLowerCase()));
|
const HIDDEN = new Set(CONFIG.hiddenNameHashes);
|
||||||
function isBlacklisted(u = {}) {
|
function isHidden(u = {}) {
|
||||||
const full = fullName(u).toLowerCase();
|
const full = nameHash(fullName(u));
|
||||||
const nick = String(u.nickname || "").trim().toLowerCase();
|
const nick = nameHash(u.nickname);
|
||||||
return (full && BLACKLIST.has(full)) || (nick && BLACKLIST.has(nick));
|
return (full && HIDDEN.has(full)) || (nick && HIDDEN.has(nick));
|
||||||
}
|
}
|
||||||
|
|
||||||
const API = "https://newprod-api.bestcoastpairings.com/v1";
|
const API = "https://newprod-api.bestcoastpairings.com/v1";
|
||||||
@@ -290,7 +293,7 @@ function build(rows, ids) {
|
|||||||
const kept = [];
|
const kept = [];
|
||||||
for (const rec of rows) {
|
for (const rec of rows) {
|
||||||
const uid = rec.userId || (rec.user && rec.user.id);
|
const uid = rec.userId || (rec.user && rec.user.id);
|
||||||
if (uid && ids.has(uid) && !isBlacklisted(rec.user || {})) kept.push(rec);
|
if (uid && ids.has(uid) && !isHidden(rec.user || {})) kept.push(rec);
|
||||||
}
|
}
|
||||||
kept.sort((a, b) => Number(b.ITCPoints || 0) - Number(a.ITCPoints || 0));
|
kept.sort((a, b) => Number(b.ITCPoints || 0) - Number(a.ITCPoints || 0));
|
||||||
return kept;
|
return kept;
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ different form or sheet, replace the iframe `src` in the relevant HTML file.
|
|||||||
## Updating each season
|
## Updating each season
|
||||||
|
|
||||||
Season name, dates and sign-up status are hardcoded in `index.html`. The entry
|
Season name, dates and sign-up status are hardcoded in `index.html`. The entry
|
||||||
fee note is in `signups.html`. There's also a commented-out PayPal block there
|
fee note is in `signups.html`.
|
||||||
that can be re-enabled if fees move back to PayPal.
|
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
|
|||||||
@@ -18,19 +18,6 @@
|
|||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<!-- commenting out paypal information
|
|
||||||
<section class="mb-8">
|
|
||||||
<h2 class="text-2xl font-semibold mb-3">League Entry Fee</h2>
|
|
||||||
<p class="mb-4">In addition to signing-up below, please pay your league entry fee of $## via PayPal to secure your spot.</p>
|
|
||||||
<a href="https://paypal.me/REDACTED" target="_blank" rel="noopener noreferrer" class="btn btn-primary">
|
|
||||||
Pay Entry Fee via PayPal
|
|
||||||
</a>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Google Form embed — replace the src URL with your form's embed link -->
|
<!-- Google Form embed — replace the src URL with your form's embed link -->
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<iframe
|
<iframe
|
||||||
|
|||||||
Reference in New Issue
Block a user