"""Alias management for NotebookLM CLI.""" import json from typing import Any from notebooklm_tools.utils.config import get_config_dir class AliasEntry: """Represents an alias with its value and type.""" def __init__(self, value: str, alias_type: str = "unknown") -> None: self.value = value self.type = alias_type def to_dict(self) -> dict[str, str]: return {"value": self.value, "type": self.type} @classmethod def from_dict(cls, data: dict[str, Any] | str) -> "AliasEntry": """Create from dict or legacy string format.""" if isinstance(data, str): # Legacy format: just the value return cls(value=data, alias_type="unknown") return cls(value=data.get("value", ""), alias_type=data.get("type", "unknown")) class AliasManager: """Manages user-defined aliases for IDs.""" def __init__(self) -> None: self.config_dir = get_config_dir() self.aliases_file = self.config_dir / "aliases.json" self._aliases: dict[str, AliasEntry] = {} self._load() def _load(self) -> None: """Load aliases from disk.""" if not self.aliases_file.exists(): return try: content = self.aliases_file.read_text(encoding="utf-8") if content: raw_data = json.loads(content) # Convert to AliasEntry objects (handles legacy format) self._aliases = { name: AliasEntry.from_dict(data) for name, data in raw_data.items() } except Exception: # On error, start with empty map self._aliases = {} def _save(self) -> None: """Save aliases to disk.""" self.config_dir.mkdir(parents=True, exist_ok=True) data = {name: entry.to_dict() for name, entry in self._aliases.items()} self.aliases_file.write_text( json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8" ) def set_alias(self, name: str, value: str, alias_type: str = "unknown") -> None: """Set an alias with optional type.""" self._aliases[name] = AliasEntry(value=value, alias_type=alias_type) self._save() def get_alias(self, name: str) -> str | None: """Get an alias value.""" entry = self._aliases.get(name) return entry.value if entry else None def get_entry(self, name: str) -> AliasEntry | None: """Get the full alias entry including type.""" return self._aliases.get(name) def delete_alias(self, name: str) -> bool: """Delete an alias. Returns True if deleted.""" if name in self._aliases: del self._aliases[name] self._save() return True return False def list_aliases(self) -> dict[str, AliasEntry]: """List all aliases with their types.""" return self._aliases.copy() def resolve(self, id_or_alias: str) -> str: """ Resolve an ID or alias to its value. If the input matches a known alias, return the aliased value. Otherwise return the input as-is. """ entry = self._aliases.get(id_or_alias) return entry.value if entry else id_or_alias # Global instance _alias_manager: AliasManager | None = None def get_alias_manager() -> AliasManager: """Get the global alias manager instance.""" global _alias_manager if _alias_manager is None: _alias_manager = AliasManager() return _alias_manager def detect_id_type(value: str, profile: str | None = None) -> str: """ Detect the type of an ID by trying API calls. Returns: "notebook", "source", or "unknown" """ from notebooklm_tools.cli.utils import get_client from notebooklm_tools.core.exceptions import NLMError from notebooklm_tools.services.errors import ServiceError from notebooklm_tools.services.sources import get_source_content try: with get_client(profile) as client: # Try as notebook ID first (most common) try: notebook = client.get_notebook(value) if notebook: return "notebook" except NLMError: pass # Try as source ID try: # Reuse the supported source-content path instead of calling a # non-existent legacy client method. content = get_source_content(client, value) if content: return "source" except (NLMError, ServiceError, AttributeError): pass except NLMError: pass return "unknown"