Genlogins / index.html
enotkrutoy's picture
Update index.html
2c03d36 verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Red Team Username/Email Generator</title>
<style>
:root {
--bg: #0f1115;
--panel: #12151d;
--muted: #99a1b3;
--text: #e6eaf2;
--accent: #6ae3ff;
--accent2: #a8ff78;
--bad: #ff6b6b;
--ok: #51cf66;
--chip: #1b2030;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
background: linear-gradient(180deg, #0f1115, #0b0d12);
font-family: Inter, system-ui, Segoe UI, Roboto, Arial, sans-serif;
color: var(--text);
-webkit-font-smoothing: antialiased;
line-height: 1.5;
}
header {
padding: 24px 20px 10px;
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
}
header h1 {
font-size: 20px;
margin: 0;
font-weight: 700;
letter-spacing: .2px;
}
header .badge {
font-size: 12px;
padding: 6px 10px;
border-radius: 999px;
background: linear-gradient(90deg, var(--accent), var(--accent2));
color: #071018;
font-weight: 700;
}
main {
display: grid;
grid-template-columns: 360px 1fr;
gap: 18px;
padding: 12px 18px 24px;
}
@media (max-width: 980px) {
main { grid-template-columns: 1fr; }
}
.card {
background: var(--panel);
border: 1px solid #1e2333;
border-radius: 14px;
box-shadow: 0 5px 30px rgba(0,0,0,.35);
}
.card h2 {
margin: 0 0 10px;
font-size: 14px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .12em;
}
.section {
padding: 16px 16px 8px;
border-top: 1px solid #1a1f2d;
}
.section:first-child { border-top: 0; }
label {
display: block;
font-size: 13px;
color: var(--muted);
margin: 10px 0 6px;
}
input[type="text"], input[type="number"], textarea, select {
width: 100%;
padding: 10px 12px;
background: #0c0f17;
border: 1px solid #1f2433;
border-radius: 10px;
color: var(--text);
outline: none;
font-family: inherit;
}
textarea { min-height: 70px; resize: vertical; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.chip {
padding: 6px 10px;
border-radius: 999px;
background: var(--chip);
border: 1px solid #21273a;
color: #cdd6f4;
font-size: 12px;
}
.opts {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.opt {
background: #0c0f17;
border: 1px solid #1f2433;
border-radius: 10px;
padding: 10px;
display: flex;
gap: 8px;
align-items: flex-start;
}
.opt input { margin-top: 2px; }
.btns {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
button {
padding: 10px 14px;
border-radius: 10px;
border: 1px solid #23304b;
background: #131b2b;
color: var(--text);
cursor: pointer;
font-weight: 700;
font-family: inherit;
transition: opacity 0.2s;
}
button.primary {
background: linear-gradient(90deg, #1e88ff, #6ae3ff);
color: #071018;
border: 0;
}
button.good {
background: linear-gradient(90deg, #5ef38c, #a8ff78);
color: #03130a;
border: 0;
}
button.bad {
background: linear-gradient(90deg, #ff5f6d, #ffc371);
color: #2a0808;
border: 0;
}
button:hover { opacity: 0.9; }
button:active { opacity: 0.8; }
button:disabled { opacity: .5; cursor: not-allowed; }
.summary {
display: flex;
gap: 14px;
flex-wrap: wrap;
align-items: center;
}
.stat {
background: #0c0f17;
border: 1px solid #1f2433;
border-radius: 12px;
padding: 10px 12px;
}
.stat b { font-size: 16px; }
.results {
padding: 12px;
max-height: 68vh;
overflow: auto;
}
.res-head {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 4px 8px 8px;
position: sticky;
top: 0;
background: linear-gradient(180deg, #12151d 70%, #12151dd9);
backdrop-filter: blur(6px);
z-index: 1;
border-bottom: 1px solid #1f2433;
}
.search { flex: 1; max-width: 420px; }
.list { margin: 8px 0 0; display: grid; grid-template-columns: 1fr; gap: 6px; }
.line {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 10px;
align-items: center;
padding: 8px 10px;
border: 1px solid #1f2433;
border-radius: 10px;
background: #0c0f17;
transition: background-color 0.2s;
}
.line:hover { background-color: #141a29; }
.line input[type="checkbox"] { transform: translateY(1px); cursor: pointer; }
.email { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.muted { color: var(--muted); }
.sep { height: 1px; background: #1a1f2d; margin: 10px 0; }
.small { font-size: 12px; color: var(--muted); }
footer { padding: 10px 18px 24px; color: #8b93a7; }
.linklike { cursor: pointer; text-decoration: underline; }
/* Toast notification */
.toast {
position: fixed;
right: 18px;
bottom: 18px;
background: linear-gradient(90deg, #1e88ff, #6ae3ff);
color: #071018;
padding: 12px 16px;
border-radius: 10px;
font-weight: 700;
box-shadow: 0 10px 30px rgba(0,0,0,.35);
z-index: 1000;
animation: slideIn 0.3s ease, fadeOut 0.5s ease 1.5s forwards;
}
@keyframes slideIn {
from { transform: translateX(100px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
/* Loading indicator */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: var(--accent);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<header>
<h1>Red Team Username/Email Generator</h1>
<span class="badge">онлайн артефакт</span>
</header>
<main>
<!-- LEFT: Controls -->
<div class="card">
<div class="section">
<h2>Входные данные</h2>
<label>Имена (через запятую)</label>
<input id="firstNames" type="text" placeholder="например: claudia, daniel, kevin, anna">
<label>Фамилии (через запятую)</label>
<input id="lastNames" type="text" placeholder="например: berghoffer, pirker, rohrer, sykora">
<div class="row">
<div>
<label>Ники / слова (опц., через запятую)</label>
<input id="nickSeeds" type="text" placeholder="shadow, ranger, chaos, sugarless, underworld, schatz">
</div>
<div>
<label>Домен(ы)</label>
<input id="domains" type="text" value="gmx.at" placeholder="gmx.at или несколько: gmx.at,example.com">
</div>
</div>
<div class="row">
<div>
<label>Год рождения (опц.)</label>
<input id="birthYear" type="number" min="1900" max="2099" placeholder="1984">
</div>
<div>
<label>Числовые суффиксы (опц.)</label>
<input id="numSuffixes" type="text" placeholder="1,12,69,70,71,84,85,87,97,123,166,2005">
</div>
</div>
<div class="small">Совет: можно оставить поля пустыми, генератор подставит реалистичные значения по умолчанию.</div>
</div>
<div class="section">
<h2>Паттерны</h2>
<div class="opts">
<label class="opt"><input type="checkbox" id="pName" checked> <span>Имя + фамилия (firstname.lastname / f.lastname / firstname.l / firstnamelastname …)</span></label>
<label class="opt"><input type="checkbox" id="pReverse" checked> <span>Обратные (lastname.firstname / lastnamef …)</span></label>
<label class="opt"><input type="checkbox" id="pSeparators" checked> <span>Разделители . _ - и без разделителя</span></label>
<label class="opt"><input type="checkbox" id="pInitials" checked> <span>Инициалы (f.l / fl / lf / f / l)</span></label>
<label class="opt"><input type="checkbox" id="pNick" checked> <span>Ники / слова (first.nick, nickYY, nickNN …)</span></label>
<label class="opt"><input type="checkbox" id="pNumbers" checked> <span>Числа (YY, YYYY, популярные суффиксы)</span></label>
<label class="opt"><input type="checkbox" id="pNormalize" checked> <span>Нормализация (ä→ae, ö→oe, ü→ue, ß→ss, диакритики)</span></label>
<label class="opt"><input type="checkbox" id="pCompact" checked> <span>Компактные (без точки: flastname, firstnamel)</span></label>
</div>
<div class="btns">
<button class="primary" id="btnGen">Сгенерировать</button>
<button id="btnClear">Очистить</button>
</div>
<div class="chips">
<div class="chip">Реалистичные шаблоны</div>
<div class="chip">Дедупликация</div>
<div class="chip">Массовые домены</div>
<div class="chip">Экспорт .txt</div>
</div>
</div>
<div class="section">
<h2>Экспорт</h2>
<div class="btns">
<button class="good" id="btnCopy" disabled>Скопировать выбранные</button>
<button class="good" id="btnCopyAll" disabled>Скопировать всё</button>
<button class="bad" id="btnSave" disabled>Скачать .txt</button>
</div>
<div class="small">Поддерживается фильтр по тексту, выделение чекбоксами, быстрая копипаста и сохранение.</div>
</div>
</div>
<!-- RIGHT: Results -->
<div class="card">
<div class="results">
<div class="res-head">
<div class="summary">
<div class="stat"><span class="muted small">Сгенерировано</span><br><b><span id="count">0</span></b></div>
<div class="stat"><span class="muted small">Выбрано</span><br><b><span id="selCount">0</span></b></div>
<div class="stat"><span class="muted small">Доменов</span><br><b><span id="domCount">1</span></b></div>
</div>
<input id="filter" class="search" type="text" placeholder="Фильтр (например: gmx.at, anna, 2005)…">
</div>
<div id="list" class="list"></div>
</div>
</div>
</main>
<footer>
<div class="small">Демонстрационные ники по умолчанию: shadow, ranger, chaos, sugarless, noizegate, facenorth, underworld, tender, big_balls, nike_maus, schatz, ravenation, linchen, dasher.</div>
</footer>
<script>
/* ---------- Helpers ---------- */
const $ = sel => document.querySelector(sel);
const $$ = sel => [...document.querySelectorAll(sel)];
// Кэширование элементов DOM
const elements = {
firstNames: $('#firstNames'),
lastNames: $('#lastNames'),
nickSeeds: $('#nickSeeds'),
domains: $('#domains'),
birthYear: $('#birthYear'),
numSuffixes: $('#numSuffixes'),
pName: $('#pName'),
pReverse: $('#pReverse'),
pSeparators: $('#pSeparators'),
pInitials: $('#pInitials'),
pNick: $('#pNick'),
pNumbers: $('#pNumbers'),
pNormalize: $('#pNormalize'),
pCompact: $('#pCompact'),
btnGen: $('#btnGen'),
btnClear: $('#btnClear'),
btnCopy: $('#btnCopy'),
btnCopyAll: $('#btnCopyAll'),
btnSave: $('#btnSave'),
count: $('#count'),
selCount: $('#selCount'),
domCount: $('#domCount'),
filter: $('#filter'),
list: $('#list')
};
// Состояние приложения
let state = {
generatedItems: [],
currentFilter: ''
};
function splitCSV(s, def = []) {
if (!s) return def;
return s.split(/[,\n;]/).map(x => x.trim()).filter(Boolean);
}
function normalizeName(s, enable = true) {
if (!enable) return s.toLowerCase();
const diacriticsMap = {
'ä': 'ae', 'ö': 'oe', 'ü': 'ue', 'ß': 'ss',
'á': 'a', 'à': 'a', 'â': 'a', 'ã': 'a', 'å': 'a', 'ă': 'a', 'ą': 'a',
'č': 'c', 'ć': 'c', 'ç': 'c',
'ď': 'd', 'ḑ': 'd',
'é': 'e', 'è': 'e', 'ê': 'e', 'ë': 'e', 'ě': 'e', 'ę': 'e',
'í': 'i', 'ì': 'i', 'î': 'i', 'ï': 'i',
'ľ': 'l', 'ł': 'l',
'ń': 'n', 'ñ': 'n',
'ó': 'o', 'ò': 'o', 'ô': 'o', 'õ': 'o', 'ő': 'o', 'ø': 'o',
'ř': 'r', 'ŕ': 'r',
'š': 's', 'ś': 's',
'ť': 't',
'ú': 'u', 'ù': 'u', 'û': 'u', 'ü': 'u', 'ů': 'u', 'ű': 'u',
'ý': 'y', 'ÿ': 'y',
'ž': 'z', 'ź': 'z', 'ż': 'z'
};
return s.toLowerCase()
.replace(/[^\u0000-\u007E]/g, ch => diacriticsMap[ch] || ch)
.replace(/[^\w.-]/g, '');
}
function uniq(arr) {
return [...new Set(arr)];
}
function pairs(firsts, lasts) {
const out = [];
for (const f of firsts) {
for (const l of lasts) {
out.push([f, l]);
}
}
return out.length ? out : [['', '']];
}
function twoDigit(y) {
y = String(y);
return y.length === 4 ? y.slice(2) : y.padStart(2, '0').slice(-2);
}
function commonSuffixesFromYear(y) {
const base = ['1', '12', '69', '70', '71', '84', '85', '87', '97', '123', '166', '2005'];
if (!y) return base;
const yy = twoDigit(y);
const yyyy = String(y);
return uniq(base.concat([yy, yyyy]));
}
function withDomains(localParts, domains) {
if (!domains.length) return localParts;
const res = [];
for (const lp of localParts) {
for (const d of domains) {
res.push(`${lp}@${d}`);
}
}
return res;
}
/* ---------- Generator ---------- */
function generate() {
// Показать индикатор загрузки
const originalText = elements.btnGen.textContent;
elements.btnGen.innerHTML = '<span class="loading"></span> Генерация...';
elements.btnGen.disabled = true;
// Используем setTimeout чтобы дать интерфейсу обновиться перед тяжелой операцией
setTimeout(() => {
try {
const opt = {
pName: elements.pName.checked,
pReverse: elements.pReverse.checked,
pSeparators: elements.pSeparators.checked,
pInitials: elements.pInitials.checked,
pNick: elements.pNick.checked,
pNumbers: elements.pNumbers.checked,
pNormalize: elements.pNormalize.checked,
pCompact: elements.pCompact.checked
};
// Inputs
let firsts = splitCSV(elements.firstNames.value);
let lasts = splitCSV(elements.lastNames.value);
let nicks = splitCSV(elements.nickSeeds.value, []);
let domains = splitCSV(elements.domains.value, ['gmx.at']);
const birthYear = elements.birthYear.value ? parseInt(elements.birthYear.value, 10) : null;
// Defaults if empty (for a good demo OOTB)
if (!firsts.length) firsts = ['claudia', 'daniel', 'anna', 'kevin', 'alexander', 'christiane', 'veronika', 'karina', 'norbert', 'hannes'];
if (!lasts.length) lasts = ['berghoffer', 'pirker', 'rohrer', 'sykora', 'hermann', 'huetter', 'semlitsch', 'steger', 'plettenbacher', 'gfoehler'];
if (!nicks.length) nicks = ['shadow', 'ranger', 'chaos', 'sugarless', 'noizegate', 'facenorth', 'underworld', 'tender', 'big_balls', 'nike_maus', 'schatz', 'ravenation', 'linchen', 'dasher'];
// Normalize
firsts = uniq(firsts.map(s => normalizeName(s, opt.pNormalize))).filter(Boolean);
lasts = uniq(lasts.map(s => normalizeName(s, opt.pNormalize))).filter(Boolean);
nicks = uniq(nicks.map(s => normalizeName(s, opt.pNormalize))).filter(Boolean);
domains = uniq(domains.map(s => s.toLowerCase()).filter(Boolean));
const sepSet = opt.pSeparators ? ['.', '_', '-', ''] : ['.'];
// Suffixes
let manualSuffixes = splitCSV(elements.numSuffixes.value, []);
manualSuffixes = manualSuffixes.filter(s => /^\d{1,4}$/.test(s));
const popular = commonSuffixesFromYear(birthYear);
const suffixes = opt.pNumbers ? uniq(popular.concat(manualSuffixes)) : [];
const locals = new Set();
// Name-based patterns
if (opt.pName || opt.pReverse || opt.pInitials || opt.pCompact) {
for (const [f, l] of pairs(firsts, lasts)) {
const fi = f ? f[0] : '';
const li = l ? l[0] : '';
// base combos
const bases = new Set();
if (opt.pName) {
for (const s of sepSet) {
if (f && l) { bases.add(`${f}${s}${l}`); }
if (opt.pCompact && f && l) bases.add(`${f}${l}`);
if (f && l && s && opt.pInitials) bases.add(`${fi}${s}${l}`);
if (f && l && s && opt.pInitials) bases.add(`${f}${s}${li}`);
}
}
if (opt.pReverse) {
for (const s of sepSet) {
if (f && l) { bases.add(`${l}${s}${f}`); }
if (opt.pCompact && f && l) bases.add(`${l}${f}`);
if (opt.pInitials && f && l && s) bases.add(`${l}${s}${fi}`);
}
}
if (opt.pInitials) {
if (f && l) bases.add(`${fi}${l}`);
if (f && l) bases.add(`${l}${fi}`);
if (f) bases.add(`${fi}`);
if (l) bases.add(`${l}`);
}
// add bases
bases.forEach(b => locals.add(b));
// numeric suffixing
if (opt.pNumbers && suffixes.length) {
bases.forEach(b => {
for (const suf of suffixes) {
locals.add(`${b}${suf}`);
}
});
}
}
}
// Nick-based patterns
if (opt.pNick && nicks.length) {
const maybeYears = opt.pNumbers ? suffixes : [];
for (const n of nicks) {
locals.add(n);
if (maybeYears.length) {
for (const suf of maybeYears) { locals.add(`${n}${suf}`); }
}
// stitch with firstnames
for (const f of firsts) {
for (const s of sepSet.filter(x => x !== '')) { // nick separators look more human with visible sep
locals.add(`${f}${s}${n}`);
locals.add(`${n}${s}${f}`);
}
}
}
}
// Final list with domains
const localParts = uniq([...locals]).filter(Boolean);
const emails = withDomains(localParts, domains);
// Сохраняем сгенерированные данные в состоянии
state.generatedItems = emails;
// Render
renderList(emails, domains.length);
} catch (error) {
console.error('Generation error:', error);
showToast('Ошибка генерации: ' + error.message, 'error');
} finally {
// Восстанавливаем кнопку
elements.btnGen.textContent = originalText;
elements.btnGen.disabled = false;
}
}, 10);
}
/* ---------- Render ---------- */
function renderList(items, domCount) {
elements.list.innerHTML = '';
const filter = elements.filter.value.trim().toLowerCase();
let filtered = items;
if (filter) {
filtered = items.filter(x => x.toLowerCase().includes(filter));
}
elements.count.textContent = String(items.length);
elements.domCount.textContent = String(domCount);
// Используем DocumentFragment для оптимизации рендеринга
const frag = document.createDocumentFragment();
// Рендерим виртуализированный список (только видимые элементы)
const batchSize = 500; // Размер батча для рендеринга
const renderBatch = (startIdx) => {
const endIdx = Math.min(startIdx + batchSize, filtered.length);
for (let i = startIdx; i < endIdx; i++) {
const email = filtered[i];
const line = document.createElement('div');
line.className = 'line';
line.innerHTML = `
<input type="checkbox" class="pick">
<div class="email">${email}</div>
<div class="small muted">${i + 1}</div>
`;
frag.appendChild(line);
}
elements.list.appendChild(frag);
// Если есть еще элементы, рендерим следующий батч
if (endIdx < filtered.length) {
setTimeout(() => renderBatch(endIdx), 0);
} else {
updateSelectionCount();
const disabled = filtered.length === 0;
elements.btnCopy.disabled = disabled;
elements.btnCopyAll.disabled = disabled;
elements.btnSave.disabled = disabled;
// Attach selection handlers
$$('.pick').forEach(cb => cb.addEventListener('change', updateSelectionCount));
}
};
// Начинаем рендеринг с первого батча
renderBatch(0);
}
function updateSelectionCount() {
const picks = $$('.pick:checked');
elements.selCount.textContent = String(picks.length);
}
/* ---------- Export ---------- */
async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
showToast('Скопировано в буфер обмена');
} catch (e) {
alert('Не удалось скопировать: ' + e.message);
}
}
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
if (type === 'error') {
toast.style.background = 'linear-gradient(90deg, #ff5f6d, #ffc371)';
} else if (type === 'warning') {
toast.style.background = 'linear-gradient(90deg, #ffc371, #ffdd67)';
toast.style.color = '#2a1c08';
}
document.body.appendChild(toast);
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 2000);
}
function getVisibleEmails() {
return $$('#list .line .email').map(n => n.textContent.trim());
}
function getSelectedEmails() {
const rows = $$('#list .line');
const out = [];
rows.forEach(r => {
const cb = r.querySelector('.pick');
if (cb && cb.checked) {
out.push(r.querySelector('.email').textContent.trim());
}
});
return out;
}
/* ---------- Event Handlers ---------- */
elements.btnCopyAll.addEventListener('click', () => {
const items = getVisibleEmails();
if (!items.length) return;
copyText(items.join('\n'));
});
elements.btnCopy.addEventListener('click', () => {
const items = getSelectedEmails();
if (!items.length) {
showToast('Ничего не выбрано', 'warning');
return;
}
copyText(items.join('\n'));
});
elements.btnSave.addEventListener('click', () => {
const items = getVisibleEmails();
if (!items.length) return;
const blob = new Blob([items.join('\n')], { type: 'text/plain;charset=utf-8' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'usernames_emails.txt';
document.body.appendChild(a);
a.click();
setTimeout(() => {
URL.revokeObjectURL(a.href);
a.remove();
}, 0);
});
elements.btnGen.addEventListener('click', generate);
elements.btnClear.addEventListener('click', () => {
elements.list.innerHTML = '';
elements.count.textContent = '0';
elements.selCount.textContent = '0';
state.generatedItems = [];
});
elements.filter.addEventListener('input', () => {
const query = elements.filter.value.trim().toLowerCase();
state.currentFilter = query;
if (state.generatedItems.length === 0) return;
// Фильтруем результаты
const filtered = query ?
state.generatedItems.filter(x => x.toLowerCase().includes(query)) :
state.generatedItems;
// Обновляем счетчик
elements.count.textContent = String(state.generatedItems.length);
// Перерисовываем список
renderList(state.generatedItems, parseInt(elements.domCount.textContent));
});
/* ---------- Auto-generate on first load ---------- */
window.addEventListener('DOMContentLoaded', () => {
// Восстанавливаем значения из localStorage, если есть
const savedData = localStorage.getItem('usernameGeneratorData');
if (savedData) {
try {
const data = JSON.parse(savedData);
if (data.firstNames) elements.firstNames.value = data.firstNames;
if (data.lastNames) elements.lastNames.value = data.lastNames;
if (data.nickSeeds) elements.nickSeeds.value = data.nickSeeds;
if (data.domains) elements.domains.value = data.domains;
if (data.birthYear) elements.birthYear.value = data.birthYear;
if (data.numSuffixes) elements.numSuffixes.value = data.numSuffixes;
} catch (e) {
console.error('Error loading saved data:', e);
}
}
generate();
});
// Сохраняем данные при изменении
['firstNames', 'lastNames', 'nickSeeds', 'domains', 'birthYear', 'numSuffixes'].forEach(id => {
$(`#${id}`).addEventListener('change', () => {
const data = {
firstNames: elements.firstNames.value,
lastNames: elements.lastNames.value,
nickSeeds: elements.nickSeeds.value,
domains: elements.domains.value,
birthYear: elements.birthYear.value,
numSuffixes: elements.numSuffixes.value
};
localStorage.setItem('usernameGeneratorData', JSON.stringify(data));
});
});
</script>
</body>
</html>