Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions tests/bonanza-pure-core.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,46 @@ const constantsSource = readSource("userscripts/giveaway/src/01-global-constants
const sponsorSource = readSource("userscripts/giveaway/src/09-sponsorship-polling.js");
const payoutSource = readSource("userscripts/giveaway/src/11-winner-selection-payouts.js");

function loadChatNumberFormatter() {
const context = makeContext();
const formatter = extractBraceBlock(utilitiesSource, "function formatChatNumbersWithSpaces");

vm.runInContext(
`${formatter}\nglobalThis.formatChatNumbersWithSpaces = formatChatNumbersWithSpaces;`,
context
);

return context.formatChatNumbersWithSpaces;
}

test("chat number formatter preserves comma-separated giveaway suggestions with digit-bearing usernames", () => {
const format = loadChatNumberFormatter();
const message =
"🚫 Sorry d\u200Bemonkadar, but S\u200Bch2021 already entered with number " +
"[color=#DC3D1D][b]61[/b][/color]! Here are some free numbers you can try: " +
"[b][color=#1DDC5D]28, 36, 52, 83, 89[/color][/b].";

assert.equal(format(message), message);
});

test("chat number formatter never mistakes comma-space list syntax for thousands grouping", () => {
const format = loadChatNumberFormatter();

assert.equal(
format("Free numbers: 28, 365, 52, 83, 89."),
"Free numbers: 28, 365, 52, 83, 89."
);
});

test("chat number formatter still normalizes genuine thousands-grouped amounts", () => {
const format = loadChatNumberFormatter();

assert.equal(
format("Amounts: 1000 | 1000000 | 1,000,000 | 1 000 000 | 1'000'000 | 1’000’000"),
"Amounts: 1 000 | 1 000 000 | 1 000 000 | 1 000 000 | 1 000 000 | 1 000 000"
);
});

function loadFinancialFunctions() {
const context = makeContext({
BONANZA: {
Expand Down
2 changes: 1 addition & 1 deletion tests/bonanza-review-hardening.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const sourcePath = new URL(
const source = readFileSync(sourcePath, "utf8");

test("review hardening invariants stay present", () => {
assert.match(source, /^\/\/ @version\s+1\.5\.12$/m);
assert.match(source, /^\/\/ @version\s+1\.5\.13$/m);
assert.doesNotMatch(source, /pollChatFallback/);
assert.doesNotMatch(source, /onlyguardians/i);
assert.match(source, /async function getLatestMainChatReplayBoundary\(\)/);
Expand Down
37 changes: 29 additions & 8 deletions userscripts/giveaway/DarkPeers_BONanza_Giveaway.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// @name DarkPeers BONanza Giveaway | Maghuro Fork
// @namespace https://darkpeers.org/users/maghuro
// @description BON giveaways on DarkPeers with an optional direct contribution to the BON Pool
// @version 1.5.12
// @version 1.5.13
// @author 🤖 T.R.A.V.I.S., Maghuro & M.A.E.S.T.R.O.
// @homepageURL https://darkpeers.org/users/maghuro
// @updateURL https://gist.githubusercontent.com/maghuro/da2dbfec94951990cbc54e75a9aee318/raw/DarkPeers_BONanza_Giveaway.meta.js
Expand Down Expand Up @@ -236,6 +236,10 @@
// per-notification CSRF-protected PATCH forms, never mass-marks unrelated
// notifications, and runs fire-and-forget so cleanup failure cannot block or
// roll back giveaway settlement.
// - v1.5.13 fixes chat number formatting so comma-separated giveaway values are
// never collapsed into a fake thousands-grouped number. Usernames containing
// digits (for example Sch2021) no longer trigger unrelated formatting, while
// genuine 1,000 / 1 000 / 1'000-style amounts remain normalized with spaces.
//// DarkPeers BONanza fork created and maintained by T.R.A.V.I.S. for the DarkPeers staff.
// Further development and maintenance by Maghuro & M.A.E.S.T.R.O.

Expand Down Expand Up @@ -11873,8 +11877,23 @@ body.host-panel-dragging * {
if (!str) return str;

const raw = String(str);
// Quick bailout: nothing that looks like a 4+ digit number or grouped digits.
if (!/\d{4}/.test(raw) && !/\d{1,3}[,\s'’]\d{3}/.test(raw)) return raw;

// Only treat one lexical numeric token at a time as a thousands-formatted
// number. In particular, comma+space is intentionally NOT a thousands
// separator because giveaway lists use it between independent values:
// "28, 36, 52, 83, 89" must never become "28, 365 283, 89".
//
// Supported forms:
// 1000 / 1000000
// 1,000 / 1,000,000
// 1 000 / 1 000 000 (including NBSP/narrow NBSP)
// 1'000 / 1’000’000
const formattableNumberRe =
/\b(?:\d{4,}|\d{1,3}(?:,\d{3})+|\d{1,3}(?:[ \u00A0\u202F]\d{3})+|\d{1,3}(?:['’]\d{3})+)\b/;

// Quick bailout also avoids usernames such as "Sch2021" waking the
// formatter up merely because they contain four consecutive digits.
if (!formattableNumberRe.test(raw)) return raw;

// Protect URL segments and BBCode tags/attributes from numeric formatting.
// This keeps [tag=...], [url=...], and color hexes untouched.
Expand All @@ -11884,11 +11903,13 @@ body.host-panel-dragging * {
return `__BG_PROTECTED_${idx}__`;
});

const formatted = protectedText.replace(/\b\d[\d,\s'’]*\d\b/g, (match) => {
const digits = match.replace(/[^\d]/g, "");
if (digits.length < 4) return match;
return digits.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
});
const formatted = protectedText.replace(
/\b(?:\d{4,}|\d{1,3}(?:,\d{3})+|\d{1,3}(?:[ \u00A0\u202F]\d{3})+|\d{1,3}(?:['’]\d{3})+)\b/g,
(match) => {
const digits = match.replace(/[^\d]/g, "");
return digits.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}
);

return formatted.replace(/__BG_PROTECTED_(\d+)__/g, (_, i) => protectedParts[Number(i)] ?? "");
} catch {
Expand Down
6 changes: 5 additions & 1 deletion userscripts/giveaway/src/00-preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// @name DarkPeers BONanza Giveaway | Maghuro Fork
// @namespace https://darkpeers.org/users/maghuro
// @description BON giveaways on DarkPeers with an optional direct contribution to the BON Pool
// @version 1.5.12
// @version 1.5.13
// @author 🤖 T.R.A.V.I.S., Maghuro & M.A.E.S.T.R.O.
// @homepageURL https://darkpeers.org/users/maghuro
// @updateURL https://gist.githubusercontent.com/maghuro/da2dbfec94951990cbc54e75a9aee318/raw/DarkPeers_BONanza_Giveaway.meta.js
Expand Down Expand Up @@ -236,6 +236,10 @@
// per-notification CSRF-protected PATCH forms, never mass-marks unrelated
// notifications, and runs fire-and-forget so cleanup failure cannot block or
// roll back giveaway settlement.
// - v1.5.13 fixes chat number formatting so comma-separated giveaway values are
// never collapsed into a fake thousands-grouped number. Usernames containing
// digits (for example Sch2021) no longer trigger unrelated formatting, while
// genuine 1,000 / 1 000 / 1'000-style amounts remain normalized with spaces.
//// DarkPeers BONanza fork created and maintained by T.R.A.V.I.S. for the DarkPeers staff.
// Further development and maintenance by Maghuro & M.A.E.S.T.R.O.

Expand Down
31 changes: 24 additions & 7 deletions userscripts/giveaway/src/12-utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -1453,8 +1453,23 @@
if (!str) return str;

const raw = String(str);
// Quick bailout: nothing that looks like a 4+ digit number or grouped digits.
if (!/\d{4}/.test(raw) && !/\d{1,3}[,\s'’]\d{3}/.test(raw)) return raw;

// Only treat one lexical numeric token at a time as a thousands-formatted
// number. In particular, comma+space is intentionally NOT a thousands
// separator because giveaway lists use it between independent values:
// "28, 36, 52, 83, 89" must never become "28, 365 283, 89".
//
// Supported forms:
// 1000 / 1000000
// 1,000 / 1,000,000
// 1 000 / 1 000 000 (including NBSP/narrow NBSP)
// 1'000 / 1’000’000
const formattableNumberRe =
/\b(?:\d{4,}|\d{1,3}(?:,\d{3})+|\d{1,3}(?:[ \u00A0\u202F]\d{3})+|\d{1,3}(?:['’]\d{3})+)\b/;

// Quick bailout also avoids usernames such as "Sch2021" waking the
// formatter up merely because they contain four consecutive digits.
if (!formattableNumberRe.test(raw)) return raw;

// Protect URL segments and BBCode tags/attributes from numeric formatting.
// This keeps [tag=...], [url=...], and color hexes untouched.
Expand All @@ -1464,11 +1479,13 @@
return `__BG_PROTECTED_${idx}__`;
});

const formatted = protectedText.replace(/\b\d[\d,\s'’]*\d\b/g, (match) => {
const digits = match.replace(/[^\d]/g, "");
if (digits.length < 4) return match;
return digits.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
});
const formatted = protectedText.replace(
/\b(?:\d{4,}|\d{1,3}(?:,\d{3})+|\d{1,3}(?:[ \u00A0\u202F]\d{3})+|\d{1,3}(?:['’]\d{3})+)\b/g,
(match) => {
const digits = match.replace(/[^\d]/g, "");
return digits.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}
);

return formatted.replace(/__BG_PROTECTED_(\d+)__/g, (_, i) => protectedParts[Number(i)] ?? "");
} catch {
Expand Down
Loading