|
1 | | -import type { PartnerCategory, Partners } from '#site/types/partners.js'; |
| 1 | +import crypto from 'node:crypto'; |
| 2 | + |
| 3 | +import type { |
| 4 | + RandomPartnerListConfig, |
| 5 | + Partners, |
| 6 | +} from '#site/types/partners.js'; |
2 | 7 |
|
3 | 8 | function randomPartnerList( |
4 | 9 | partners: Array<Partners>, |
5 | | - config: { |
6 | | - /** |
7 | | - * Number of partners to pick from the list. |
8 | | - * If null, all partners will be returned. |
9 | | - */ |
10 | | - pick?: number | null; |
11 | | - /** |
12 | | - * Date seed to use for the randomization. |
13 | | - * This is used to ensure that the same partners are returned for the same date. |
14 | | - */ |
15 | | - dateSeed?: number; |
16 | | - /** |
17 | | - * Category of partners to filter by. |
18 | | - * If not provided, all partners will be returned. |
19 | | - */ |
20 | | - category?: PartnerCategory; |
21 | | - /** |
22 | | - * Whether to randomize the partners or not. |
23 | | - */ |
24 | | - sort?: 'name' | 'weight' | null; |
25 | | - } |
| 10 | + config: RandomPartnerListConfig |
26 | 11 | ) { |
27 | | - const { pick = 4, dateSeed = 5, category, sort = 'weight' } = config; |
28 | | - |
29 | | - const filteredPartners = [...partners].filter(partner => { |
30 | | - return !category || partner.categories.includes(category); |
31 | | - }); |
| 12 | + const { pick = 4, dateSeed = 5, category } = config; |
32 | 13 |
|
33 | | - if (sort === null) { |
34 | | - return pick !== null ? filteredPartners.slice(0, pick) : filteredPartners; |
35 | | - } |
| 14 | + // Generate a deterministic seed based on current time that changes every X minutes |
| 15 | + const seed = Math.floor(Date.now() / (dateSeed * 60 * 1000)); |
36 | 16 |
|
37 | | - if (sort === 'name') { |
38 | | - const shuffled = [...filteredPartners].sort((a, b) => |
39 | | - a.name.localeCompare(b.name) |
40 | | - ); |
| 17 | + // Filter by category if provided |
| 18 | + const filtered = category |
| 19 | + ? partners.filter(p => p.categories.includes(category)) |
| 20 | + : partners; |
41 | 21 |
|
42 | | - return pick !== null ? shuffled.slice(0, pick) : shuffled; |
43 | | - } |
| 22 | + // Remove duplicate partners |
| 23 | + const unique = Array.from(new Set(filtered)); |
44 | 24 |
|
45 | | - const now = new Date(); |
46 | | - const minutes = Math.floor(now.getUTCMinutes() / dateSeed) * dateSeed; |
| 25 | + // Create a hash from the seed to use for consistent randomization |
| 26 | + const hash = crypto.createHash('sha256').update(String(seed)).digest(); |
47 | 27 |
|
48 | | - const fixedTime = new Date( |
49 | | - Date.UTC( |
50 | | - now.getUTCFullYear(), |
51 | | - now.getUTCMonth(), |
52 | | - now.getUTCDate(), |
53 | | - now.getUTCHours(), |
54 | | - minutes, |
55 | | - 0, |
56 | | - 0 |
57 | | - ) |
| 28 | + // Sort partners using the hash to ensure same results for the same seed |
| 29 | + const sorted = [...unique].sort( |
| 30 | + (a, b) => hash[a.name.charCodeAt(0) % 32] - hash[b.name.charCodeAt(0) % 32] |
58 | 31 | ); |
59 | 32 |
|
60 | | - // We create a seed from the rounded date (timestamp in ms) |
61 | | - const seed = fixedTime.getTime(); |
62 | | - const rng = mulberry32(seed); |
63 | | - |
64 | | - const weightedPartners = filteredPartners.flatMap(partner => { |
65 | | - const weight = partner.weight; |
66 | | - return Array(weight).fill(partner); |
67 | | - }); |
68 | | - |
69 | | - // Create a copy of the array to avoid modifying the original |
70 | | - const shuffled = [...weightedPartners].sort(() => rng() - 0.5); |
71 | | - |
72 | | - // Remove duplicates while preserving order |
73 | | - const unique = Array.from(new Set(shuffled)); |
74 | | - |
75 | | - if (pick !== null) { |
76 | | - return unique.slice(0, pick); |
77 | | - } |
78 | | - |
79 | | - return unique; |
80 | | -} |
81 | | - |
82 | | -// This function returns a random list of partners based on a fixed time seed |
83 | | -function mulberry32(seed: number) { |
84 | | - return function () { |
85 | | - let t = (seed += 0x6d2b79f5); |
86 | | - t = Math.imul(t ^ (t >>> 15), t | 1); |
87 | | - t ^= t + Math.imul(t ^ (t >>> 7), t | 61); |
88 | | - return ((t ^ (t >>> 14)) >>> 0) / 4294967296; |
89 | | - }; |
| 33 | + return sorted.slice(0, pick ?? sorted.length); |
90 | 34 | } |
91 | 35 |
|
92 | 36 | export { randomPartnerList }; |
0 commit comments