// casegen/rng.js — детермінована випадковість, шифри, паролі const crypto = require('crypto'); const N = require('./names'); // ---------- RNG ---------- function hashSeed(str) { let h = 2166136261 >>> 0; for (const c of String(str)) { h ^= c.codePointAt(0); h = Math.imul(h, 16777619); } return h >>> 0; } function mulberry32(a) { return function () { a |= 0; a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } const ri = (r, a, b) => a + Math.floor(r() * (b - a + 1)); const estRange = (r, actual, lo = 3, hi = 5) => `${actual - ri(r, lo, hi)}–${actual + ri(r, lo, hi)}`; const pick = (r, arr) => arr[Math.floor(r() * arr.length)]; const chance = (r, p) => r() < p; function shuffle(r, arr) { const a = arr.slice(); for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(r() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } // ---------- Шифри ---------- const UKR = 'абвгґдеєжзиіїйклмнопрстуфхцчшщьюя'; function caesar(text, k) { return [...text].map(ch => { const lower = ch.toLowerCase(), i = UKR.indexOf(lower); if (i < 0) return ch; const out = UKR[(i + k + UKR.length) % UKR.length]; return ch === lower ? out : out.toUpperCase(); }).join(''); } const b64 = t => Buffer.from(t, 'utf8').toString('base64'); const md5 = t => crypto.createHash('md5').update(t, 'utf8').digest('hex'); // ---------- Паролі ---------- const TRANSLIT = { а:'a',б:'b',в:'v',г:'h',ґ:'g',д:'d',е:'e',є:'ie',ж:'zh',з:'z',и:'y',і:'i',ї:'i',й:'i',к:'k',л:'l',м:'m',н:'n',о:'o',п:'p',р:'r',с:'s',т:'t',у:'u',ф:'f',х:'kh',ц:'ts',ч:'ch',ш:'sh',щ:'shch',ь:'',ю:'iu',я:'ia' }; const translit = s => [...String(s).toLowerCase()].map(c => TRANSLIT[c] ?? c).join('').replace(/[^a-z0-9]/g, ''); const COMMON_PASSWORDS = ['sunshine', 'dragon', 'monkey', 'princess', 'football', 'iloveyou', 'superman', 'batman', 'master', 'hunter', 'freedom', 'trustno1', 'whatever', 'ginger', 'flower', 'summer', 'winter', 'orange', 'purple', 'silver', 'letmein', 'qwerty', 'pepper', 'cookie', 'tigger', 'jasmine', 'soccer', 'maggie', 'hannah', 'ashley', 'chelsea', 'butterfly', 'password', 'shadow', 'michael', 'jessica', 'charlie', 'daniel', 'jordan', 'matrix', 'falcon', 'phoenix', 'rainbow', 'diamond', 'banana', 'cheese', 'coffee', 'chocolate', 'midnight', 'october', 'november', 'chester', 'oliver', 'ranger', 'harley', 'buster', 'george', 'sammy', 'pumpkin', 'muffin', 'peanut', 'smokey', 'bandit', 'lucky', 'angel', 'lovely', 'sunflower', 'babygirl', 'bubbles', 'snickers', 'chicken', 'turtle', 'rabbit', 'dolphin', 'junior', 'dexter', 'guitar', 'hammer', 'winner', 'copper', 'marina', 'sierra', 'tucker', 'willow', 'zombie', 'viking', 'thunder', 'topgun', 'maverick', 'eclipse', 'scorpion', 'panther']; function genPassword(r, victim) { const yr = String(victim.birthYear), yr2 = yr.slice(2); const personal = []; if (victim.dog) personal.push( { answer: translit(victim.dog), hint: 'кличка собаки латиницею (див. досьє)' }, { answer: translit(victim.dog) + yr, hint: 'кличка собаки латиницею + повний рік народження' }); personal.push( { answer: translit(victim.first) + yr, hint: `ім'я латиницею + повний рік народження` }, { answer: translit(victim.last) + yr2, hint: 'прізвище латиницею + дві цифри року народження' }, { answer: translit(victim.first) + translit(victim.last), hint: `ім'я та прізвище латиницею разом` }); if (chance(r, 0.5)) { const p = pick(r, personal); return { ...p, style: 'personal', personal: true }; } return { answer: pick(r, COMMON_PASSWORDS), style: 'random', personal: false, hint: 'жодного зв\'язку з жертвою — тільки перебір за словником (rockyou.txt)' }; } module.exports = { hashSeed, mulberry32, ri, estRange, pick, chance, shuffle, UKR, caesar, b64, md5, translit, COMMON_PASSWORDS, genPassword };