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
|
||||
- `leagueId` / `regionId`: which ITC league and region to rank against
|
||||
- `lmcLeagueId`: the Lord Marshal Conference league used for the LMC columns
|
||||
- `blacklistNames`: players to hide from the board (full name or nickname,
|
||||
case-insensitive)
|
||||
- `hiddenNameHashes`: players to hide from the board, stored as hashes of
|
||||
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
|
||||
members without auth
|
||||
- `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
|
||||
|
||||
- `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)
|
||||
- `public/elo-data.json`: generated ELO data. Don't edit it by hand; the
|
||||
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 ---- */
|
||||
function loadHTML(file, elementId, callback) {
|
||||
fetch(file)
|
||||
@@ -48,19 +50,20 @@ const CONFIG = {
|
||||
// Fallback roster if the team endpoint requires auth.
|
||||
memberIds: [],
|
||||
|
||||
// Names to hide from the leaderboard entirely (case-insensitive).
|
||||
// Match on full name ("First Last") or nickname, whichever the player goes by.
|
||||
blacklistNames: ["Hidden Player"],
|
||||
// Players to hide from the leaderboard entirely, as nameHash() values of
|
||||
// their full name ("First Last") or nickname — see src/hide.js for how to
|
||||
// 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).
|
||||
useSample: false,
|
||||
};
|
||||
|
||||
const BLACKLIST = new Set(CONFIG.blacklistNames.map(n => n.trim().toLowerCase()));
|
||||
function isBlacklisted(u = {}) {
|
||||
const full = fullName(u).toLowerCase();
|
||||
const nick = String(u.nickname || "").trim().toLowerCase();
|
||||
return (full && BLACKLIST.has(full)) || (nick && BLACKLIST.has(nick));
|
||||
const HIDDEN = new Set(CONFIG.hiddenNameHashes);
|
||||
function isHidden(u = {}) {
|
||||
const full = nameHash(fullName(u));
|
||||
const nick = nameHash(u.nickname);
|
||||
return (full && HIDDEN.has(full)) || (nick && HIDDEN.has(nick));
|
||||
}
|
||||
|
||||
const API = "https://newprod-api.bestcoastpairings.com/v1";
|
||||
@@ -290,7 +293,7 @@ function build(rows, ids) {
|
||||
const kept = [];
|
||||
for (const rec of rows) {
|
||||
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));
|
||||
return kept;
|
||||
|
||||
@@ -29,8 +29,7 @@ different form or sheet, replace the iframe `src` in the relevant HTML file.
|
||||
## Updating each season
|
||||
|
||||
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
|
||||
that can be re-enabled if fees move back to PayPal.
|
||||
fee note is in `signups.html`.
|
||||
|
||||
## Files
|
||||
|
||||
|
||||
@@ -18,19 +18,6 @@
|
||||
|
||||
<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 -->
|
||||
<div class="w-full">
|
||||
<iframe
|
||||
|
||||
Reference in New Issue
Block a user