export function connectionAction({ connected, selectedTag, configExists }) { if (connected) return { type: 'stop' }; if (selectedTag) return { type: 'apply', selectedTag }; if (configExists) return { type: 'restart' }; return null; } export function formatConnectionDuration(startedAt, now = Date.now()) { const { totalHours, minutes, seconds } = connectionDurationParts(startedAt, now); return [totalHours, minutes.value, seconds.value] .map((part) => String(part).padStart(2, '0')) .join(':'); } function durationLabel(value, forms) { const mod10 = value % 10; const mod100 = value % 100; return mod10 === 1 && mod100 !== 11 ? forms[0] : mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14) ? forms[1] : forms[2]; } export function connectionDurationParts(startedAt, now = Date.now()) { const started = Date.parse(startedAt); const totalSeconds = Number.isFinite(started) ? Math.max(0, Math.floor((now - started) / 1000)) : 0; const days = Math.floor(totalSeconds / 86_400); const totalHours = Math.floor(totalSeconds / 3600); const hours = Math.floor((totalSeconds % 86_400) / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return { totalHours, days: { value: days, label: durationLabel(days, ['день', 'дня', 'дней']) }, hours: { value: hours, label: durationLabel(hours, ['час', 'часа', 'часов']) }, minutes: { value: minutes, label: durationLabel(minutes, ['минута', 'минуты', 'минут']) }, seconds: { value: seconds, label: durationLabel(seconds, ['секунда', 'секунды', 'секунд']) }, }; } export function formatConnectionDurationWords(startedAt, now = Date.now()) { const { days, hours, minutes, seconds } = connectionDurationParts(startedAt, now); return [ days.value && `${days.value} ${days.label}`, hours.value && `${hours.value} ${hours.label}`, minutes.value && `${minutes.value} ${minutes.label}`, `${seconds.value} ${seconds.label}`, ].filter(Boolean).join(' '); } export function subscriptionDomain(subscriptionHost) { const value = String(subscriptionHost || ''); try { return new URL(value).host; } catch { return value.split('/')[0]; } } export function localProxyUrls(port = 8082, host = '127.0.0.1') { const urlHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host; return { socks5: `socks5://${urlHost}:${port}`, http: `http://${urlHost}:${port}`, }; } export async function copyText(text, options = {}) { const clipboard = options.clipboard ?? globalThis.navigator?.clipboard; const documentRef = options.documentRef ?? globalThis.document; if (documentRef?.execCommand) { const textarea = documentRef.createElement('textarea'); textarea.value = text; textarea.setAttribute('readonly', ''); textarea.style.position = 'fixed'; textarea.style.opacity = '0'; documentRef.body.append(textarea); textarea.select(); const copied = documentRef.execCommand('copy'); textarea.remove(); if (copied) return; } if (!clipboard?.writeText) throw new Error('Copy failed'); await clipboard.writeText(text); } export function subscriptionUsage(userInfo = {}) { const upload = Math.max(0, Number(userInfo.upload) || 0); const download = Math.max(0, Number(userInfo.download) || 0); const total = Math.max(0, Number(userInfo.total) || 0); const used = upload + download; return { upload, download, total, used, percent: total ? Math.min(100, (used / total) * 100) : null, expiresAt: userInfo.expire ? new Date(Number(userInfo.expire) * 1000) : null, }; } export function subscriptionDaysLeft(expiresAt, now = Date.now()) { const days = Math.ceil((expiresAt?.getTime() - now) / 86_400_000); if (!Number.isFinite(days)) return ''; if (days <= 0) return 'срок истёк'; const mod10 = days % 10; const mod100 = days % 100; const unit = mod10 === 1 && mod100 !== 11 ? 'день' : mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14) ? 'дня' : 'дней'; return `${days === 1 ? 'остался' : 'осталось'} ${days} ${unit}`; }