import json import os import shutil import bcrypt from datetime import datetime DATA_DIR = "user_data" def _ensure_dirs(): os.makedirs(DATA_DIR, exist_ok=True) def _user_dir(username: str) -> str: path = os.path.join(DATA_DIR, username) os.makedirs(path, exist_ok=True) return path def _voices_dir(username: str) -> str: path = os.path.join(_user_dir(username), "voices") os.makedirs(path, exist_ok=True) return path def _files_dir(username: str) -> str: path = os.path.join(_user_dir(username), "files") os.makedirs(path, exist_ok=True) return path def _users_file() -> str: _ensure_dirs() return os.path.join(DATA_DIR, "users.json") def _load_users() -> dict: path = _users_file() if os.path.exists(path): with open(path, "r") as f: return json.load(f) return {} def _save_users(users: dict): with open(_users_file(), "w") as f: json.dump(users, f, indent=2) def register_user(username: str, password: str) -> tuple[bool, str]: if not username or not password: return False, "Username and password required" if len(username) < 3: return False, "Username must be at least 3 characters" if len(password) < 6: return False, "Password must be at least 6 characters" users = _load_users() if username.lower() in users: return False, "user_exists" hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() users[username.lower()] = { "password_hash": hashed, "created": datetime.now().isoformat(), "settings": {"app_language": "en", "tts_language": "en", "speed": 1.0}, } _save_users(users) _user_dir(username.lower()) _voices_dir(username.lower()) _files_dir(username.lower()) return True, "register_success" def verify_user(username: str, password: str) -> bool: users = _load_users() user = users.get(username.lower()) if not user: return False return bcrypt.checkpw(password.encode(), user["password_hash"].encode()) def delete_user(username: str, password: str) -> bool: if not verify_user(username, password): return False users = _load_users() username = username.lower() if username in users: del users[username] _save_users(users) user_path = os.path.join(DATA_DIR, username) if os.path.exists(user_path): shutil.rmtree(user_path) return True def get_user_settings(username: str) -> dict: users = _load_users() user = users.get(username.lower(), {}) return user.get("settings", {"app_language": "en", "tts_language": "en", "speed": 1.0}) def update_user_settings(username: str, settings: dict): users = _load_users() username = username.lower() if username in users: users[username]["settings"] = settings _save_users(users) def save_user_file(username: str, filename: str, content: str) -> str: files_dir = _files_dir(username.lower()) meta_path = os.path.join(files_dir, "files_meta.json") if os.path.exists(meta_path): with open(meta_path, "r") as f: meta = json.load(f) else: meta = {} safe_name = "".join(c for c in filename if c.isalnum() or c in "._- ").strip() file_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{safe_name}" content_path = os.path.join(files_dir, f"{file_id}.txt") with open(content_path, "w", encoding="utf-8") as f: f.write(content) meta[file_id] = { "original_name": filename, "saved_at": datetime.now().isoformat(), "char_count": len(content), } with open(meta_path, "w") as f: json.dump(meta, f, indent=2) return file_id def get_user_files(username: str) -> dict: files_dir = _files_dir(username.lower()) meta_path = os.path.join(files_dir, "files_meta.json") if os.path.exists(meta_path): with open(meta_path, "r") as f: return json.load(f) return {} def load_file_content(username: str, file_id: str) -> str: files_dir = _files_dir(username.lower()) content_path = os.path.join(files_dir, f"{file_id}.txt") if os.path.exists(content_path): with open(content_path, "r", encoding="utf-8") as f: return f.read() return "" def delete_user_file(username: str, file_id: str) -> bool: files_dir = _files_dir(username.lower()) meta_path = os.path.join(files_dir, "files_meta.json") content_path = os.path.join(files_dir, f"{file_id}.txt") if os.path.exists(meta_path): with open(meta_path, "r") as f: meta = json.load(f) if file_id in meta: del meta[file_id] with open(meta_path, "w") as f: json.dump(meta, f, indent=2) if os.path.exists(content_path): os.remove(content_path) return True return False def save_cloned_voice(username: str, voice_name: str, audio_path: str) -> str: voices_dir = _voices_dir(username.lower()) meta_path = os.path.join(voices_dir, "voices_meta.json") if os.path.exists(meta_path): with open(meta_path, "r") as f: meta = json.load(f) else: meta = {} voice_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{voice_name}" ext = os.path.splitext(audio_path)[1] voice_file = os.path.join(voices_dir, f"{voice_id}{ext}") shutil.copy2(audio_path, voice_file) meta[voice_id] = { "name": voice_name, "file": f"{voice_id}{ext}", "created": datetime.now().isoformat(), } with open(meta_path, "w") as f: json.dump(meta, f, indent=2) return voice_id def get_user_voices(username: str) -> dict: voices_dir = _voices_dir(username.lower()) meta_path = os.path.join(voices_dir, "voices_meta.json") if os.path.exists(meta_path): with open(meta_path, "r") as f: return json.load(f) return {} def get_voice_path(username: str, voice_id: str) -> str: voices = get_user_voices(username.lower()) if voice_id in voices: voices_dir = _voices_dir(username.lower()) return os.path.join(voices_dir, voices[voice_id]["file"]) return "" def delete_user_voice(username: str, voice_id: str) -> bool: voices_dir = _voices_dir(username.lower()) meta_path = os.path.join(voices_dir, "voices_meta.json") if os.path.exists(meta_path): with open(meta_path, "r") as f: meta = json.load(f) if voice_id in meta: voice_file = os.path.join(voices_dir, meta[voice_id]["file"]) if os.path.exists(voice_file): os.remove(voice_file) del meta[voice_id] with open(meta_path, "w") as f: json.dump(meta, f, indent=2) return True return False