import { authService, state } from '../state.js'; import { buildArweaveAvatarValue, parseArweaveAvatarValue, validateArweaveTxId, validateSha256Hex, } from './arweave-file-service.js'; export const profileFieldDefs = [ { key: 'first_name', readKeys: ['first_name'], label: 'Имя', placeholder: 'Введите имя' }, { key: 'last_name', readKeys: ['last_name'], label: 'Фамилия', placeholder: 'Введите фамилию' }, { key: 'address', readKeys: ['address'], label: 'Адрес', placeholder: 'Город, улица, дом' }, { key: 'web', readKeys: ['web'], label: 'Links', placeholder: 'Сайт, профиль или другая ссылка' }, { key: 'phone', readKeys: ['phone'], label: 'Телефон', placeholder: '+7 ...' }, { key: 'about', readKeys: ['about'], label: 'О себе', placeholder: 'Коротко расскажите о себе', maxLength: 160, multiline: true }, { key: 'spiritual_path', readKeys: ['spiritual_path'], label: 'Духовный путь', placeholder: 'Расскажите о своём духовном пути, опыте, практиках и взглядах', maxLength: 5000, multiline: true }, ]; export const profileToggleDefs = []; // legacy boolean toggles are no longer used by the new UI. export const PROFILE_ACCOUNT_ROLE_PRIMARY = 'primary'; export const PROFILE_ACCOUNT_ROLE_NON_VOTING = 'non_voting'; export const PROFILE_SHINE_SHINING = 'shining'; export const PROFILE_SHINE_UNKNOWN = 'unknown'; export const PROFILE_SHINE_NOT_INTERESTED = 'not_interested'; export const PROFILE_GENDER_MALE = 'male'; export const PROFILE_GENDER_FEMALE = 'female'; export const PROFILE_GENDER_UNKNOWN = 'unknown'; export const PROFILE_GENDER_VALUES = Object.freeze([ PROFILE_GENDER_MALE, PROFILE_GENDER_FEMALE, PROFILE_GENDER_UNKNOWN, ]); function normalizeItem(param, payload) { if (!param) return null; if (payload && typeof payload === 'object') { const value = String(payload?.value || payload?.param_value || ''); const timeMs = Number(payload?.time_ms || payload?.timeMs || 0); if (!value && !timeMs) return null; return { param, value, timeMs }; } return null; } function parseToggleValue(value) { const normalized = String(value || '').trim().toLowerCase(); return normalized === 'true' || normalized === 'yes' || normalized === '1'; } function normalizeGenderValue(value) { const normalized = String(value || '').trim().toLowerCase(); if (normalized === PROFILE_GENDER_MALE) return PROFILE_GENDER_MALE; if (normalized === PROFILE_GENDER_FEMALE) return PROFILE_GENDER_FEMALE; return PROFILE_GENDER_UNKNOWN; } async function getStoragePwd() { const storagePwd = state.session.storagePwdInMemory; if (!storagePwd) { throw new Error('Нет storagePwd в памяти сессии. Выполните вход заново.'); } return storagePwd; } function normalizeListItems(payload) { const rows = Array.isArray(payload?.params) ? payload.params : []; const normalized = []; for (let i = 0; i < rows.length; i += 1) { const row = rows[i]; if (!row || typeof row !== 'object') continue; const param = String(row.param || '').trim(); if (!param) continue; const item = normalizeItem(param, row); if (item) normalized.push(item); } return normalized; } function loadLatestByAliasesFromItems(items, aliases) { if (!Array.isArray(items) || !items.length || !Array.isArray(aliases) || !aliases.length) return null; const aliasSet = new Set(aliases.map((alias) => String(alias || '').trim().toLowerCase()).filter(Boolean)); if (!aliasSet.size) return null; let latest = null; for (let i = 0; i < items.length; i += 1) { const item = items[i]; const itemParam = String(item?.param || '').trim().toLowerCase(); if (!itemParam || !aliasSet.has(itemParam)) continue; if (!latest || Number(item.timeMs || 0) > Number(latest.timeMs || 0)) { latest = item; } } return latest; } export async function loadProfileSnapshot(login) { const payload = await authService.listUserParams(login); const items = normalizeListItems(payload); const fields = []; for (let i = 0; i < profileFieldDefs.length; i += 1) { const field = profileFieldDefs[i]; const latest = loadLatestByAliasesFromItems(items, field.readKeys); fields.push({ key: field.key, label: field.label, placeholder: field.placeholder, maxLength: Number(field.maxLength || 300), multiline: Boolean(field.multiline), value: latest?.value || '', timeMs: latest?.timeMs || 0, }); } const latestAccountRole = loadLatestByAliasesFromItems(items, ['account_role']); const rawAccountRole = String(latestAccountRole?.value || '').trim().toLowerCase(); const accountRole = rawAccountRole === PROFILE_ACCOUNT_ROLE_PRIMARY || rawAccountRole === PROFILE_ACCOUNT_ROLE_NON_VOTING ? rawAccountRole : ''; const latestShine = loadLatestByAliasesFromItems(items, ['shine']); const rawShine = String(latestShine?.value || '').trim().toLowerCase(); let shineStatus = ''; if (rawShine === PROFILE_SHINE_SHINING || rawShine === 'yes') shineStatus = PROFILE_SHINE_SHINING; else if (rawShine === PROFILE_SHINE_UNKNOWN) shineStatus = PROFILE_SHINE_UNKNOWN; else if (rawShine === PROFILE_SHINE_NOT_INTERESTED) shineStatus = PROFILE_SHINE_NOT_INTERESTED; // Legacy shine=no is intentionally ignored. Legacy official=yes/no is not read at all. const toggles = [ { key: 'account_role', label: 'Роль аккаунта', enabled: accountRole === PROFILE_ACCOUNT_ROLE_PRIMARY, rawValue: accountRole, timeMs: latestAccountRole?.timeMs || 0 }, { key: 'shine', label: 'Сияние', enabled: shineStatus === PROFILE_SHINE_SHINING, rawValue: shineStatus, timeMs: latestShine?.timeMs || 0 }, ]; const latestGender = loadLatestByAliasesFromItems(items, ['gender']); const gender = normalizeGenderValue(latestGender?.value || PROFILE_GENDER_UNKNOWN); const latestAvatar = loadLatestByAliasesFromItems(items, ['ava']); const parsedAvatar = parseArweaveAvatarValue(latestAvatar?.value || ''); const avatar = parsedAvatar.ok ? { value: buildArweaveAvatarValue(parsedAvatar.txId, parsedAvatar.sha256Hex), source: 'arweave', txId: parsedAvatar.txId, sha256Hex: parsedAvatar.sha256Hex || '', timeMs: latestAvatar?.timeMs || 0, } : { value: '', source: '', txId: '', sha256Hex: '', timeMs: latestAvatar?.timeMs || 0, }; return { fields, toggles, accountRole, accountRoleTimeMs: latestAccountRole?.timeMs || 0, shineStatus, shineTimeMs: latestShine?.timeMs || 0, gender, genderTimeMs: latestGender?.timeMs || 0, avatar, }; } export async function saveProfileParamBlock(login, key, value) { const storagePwd = await getStoragePwd(); await authService.addBlockUserParam({ login, param: key, value: String(value ?? '').trim(), storagePwd, }); } export async function saveProfileStatus(login, key, value) { const cleanKey = String(key || '').trim(); const cleanValue = String(value || '').trim().toLowerCase(); if (cleanKey === 'account_role' && ![PROFILE_ACCOUNT_ROLE_PRIMARY, PROFILE_ACCOUNT_ROLE_NON_VOTING].includes(cleanValue)) { throw new Error('Некорректное значение account_role'); } if (cleanKey === 'shine' && ![PROFILE_SHINE_SHINING, PROFILE_SHINE_UNKNOWN, PROFILE_SHINE_NOT_INTERESTED].includes(cleanValue)) { throw new Error('Некорректное значение shine'); } const storagePwd = await getStoragePwd(); await authService.addBlockUserParam({ login, param: cleanKey, value: cleanValue, storagePwd }); } // Kept only for old callers outside the current UI. New code must use saveProfileStatus(). export async function saveProfileToggle(login, key, enabled) { if (key === 'shine') return saveProfileStatus(login, 'shine', enabled ? PROFILE_SHINE_SHINING : PROFILE_SHINE_UNKNOWN); if (key === 'official') throw new Error('Параметр official устарел. Используйте account_role.'); throw new Error(`Неизвестный legacy toggle: ${key}`); } export async function saveProfileGender(login, gender) { const normalized = normalizeGenderValue(gender); const storagePwd = await getStoragePwd(); await authService.addBlockUserParam({ login, param: 'gender', value: normalized, storagePwd, }); } export async function saveProfileAvatarArweave(login, txId, sha256Hex) { const cleanTxId = String(txId || '').trim(); const cleanSha = String(sha256Hex || '').trim().toLowerCase(); if (!validateArweaveTxId(cleanTxId)) { throw new Error('Некорректный Transaction ID Arweave'); } if (!validateSha256Hex(cleanSha)) { throw new Error('Некорректный SHA256 хэш аватара'); } await saveProfileParamBlock(login, 'ava', buildArweaveAvatarValue(cleanTxId, cleanSha)); }