Skip to main content
Glama
character_service.py10.5 kB
"""Character service for business logic encapsulation.""" import asyncio import re from typing import Any from ..core.exceptions import DataNotFoundException from ..core.exceptions import ServiceException from ..core.interfaces import CharacterServiceProtocol from ..core.logging_config import LoggerMixin from ..infrastructure.repositories import CharacterRepository from ..parsers.content_parser import StrategyBasedContentParser class CharacterService(CharacterServiceProtocol, LoggerMixin): """Service for character-related business operations.""" def __init__( self, character_repository: CharacterRepository, content_parser: StrategyBasedContentParser, markdown_service: "MarkdownService", # Forward reference ): """Initialize character service. Args: character_repository: Repository for character data access. content_parser: Content parser for processing raw data. markdown_service: Service for markdown generation. """ self.character_repository = character_repository self.content_parser = content_parser self.markdown_service = markdown_service async def get_character_info(self, character_name: str) -> str: """Get comprehensive character information including strategy. Args: character_name: Name of the character to query. Returns: Markdown formatted character information. Raises: ServiceException: If character retrieval fails. """ try: self.logger.info(f"Getting character info for: {character_name}") # Get character raw data character_raw_data = await self._get_character_data(character_name) # Extract strategy item ID for parallel processing strategy_item_id = self._extract_strategy_item_id(character_raw_data) # Parallel processing: parse profile and fetch strategy profile_task = asyncio.create_task( asyncio.to_thread(self.content_parser.parse_main_content, character_raw_data) ) strategy_task = None if strategy_item_id: strategy_task = asyncio.create_task(self._fetch_strategy_content(strategy_item_id)) # Wait for profile parsing character_profile_data = await profile_task # Generate markdown for profile character_markdown = self.markdown_service.generate_character_markdown( character_profile_data, include_strategy=False ) # Process strategy if available strategy_markdown = "" if strategy_task: try: strategy_raw_data = await strategy_task if strategy_raw_data: strategy_parsed = await asyncio.to_thread( self.content_parser.parse_strategy_content, strategy_raw_data ) strategy_markdown = self.markdown_service.generate_strategy_markdown(strategy_parsed) except Exception as e: self.logger.warning(f"Failed to process strategy content: {e}") # Combine results combined_markdown = character_markdown if strategy_markdown: combined_markdown += "\n\n" + strategy_markdown # Add strategy link if available if strategy_item_id: link_markdown = self._generate_strategy_link_markdown(strategy_item_id) combined_markdown += "\n\n" + link_markdown self.logger.info(f"Successfully generated character info for: {character_name}") return combined_markdown except DataNotFoundException: error_msg = f"Character '{character_name}' not found" self.logger.error(error_msg) return f"错误:未找到名为 '{character_name}' 的角色。" except Exception as e: self.logger.error(f"Failed to get character info for {character_name}: {e}") raise ServiceException(f"Character info retrieval failed: {e}") async def get_character_profile(self, character_name: str) -> str: """Get character profile information only. Args: character_name: Name of the character to query. Returns: Markdown formatted character profile. Raises: ServiceException: If character profile retrieval fails. """ try: self.logger.info(f"Getting character profile for: {character_name}") # Get character raw data character_raw_data = await self._get_character_data(character_name) # Parse profile content character_profile_data = await asyncio.to_thread( self.content_parser.parse_character_profile, character_raw_data ) # Generate markdown profile_markdown = self.markdown_service.generate_character_markdown( character_profile_data, include_strategy=False ) if not profile_markdown.strip(): self.logger.warning(f"Generated empty profile for: {character_name}") return f"成功获取 '{character_name}' 的档案数据,但解析后的内容无法生成有效的 Markdown。" self.logger.info(f"Successfully generated character profile for: {character_name}") return profile_markdown except DataNotFoundException: error_msg = f"Character '{character_name}' not found" self.logger.error(error_msg) return f"错误:未找到名为 '{character_name}' 的角色。" except Exception as e: self.logger.error(f"Failed to get character profile for {character_name}: {e}") raise ServiceException(f"Character profile retrieval failed: {e}") async def _get_character_data(self, character_name: str) -> dict[str, Any]: """Get character raw data from repository. Args: character_name: Name of the character. Returns: Raw character data. Raises: DataNotFoundException: If character not found. ServiceException: If data retrieval fails. """ try: character_data = await self.character_repository.find_by_name(character_name) if not character_data: raise DataNotFoundException("character", character_name) return character_data except DataNotFoundException: raise except Exception as e: self.logger.error(f"Failed to get character data for {character_name}: {e}") raise ServiceException(f"Character data retrieval failed: {e}") def _extract_strategy_item_id(self, character_raw_data: dict[str, Any]) -> str | None: """Extract strategy item ID from character data. Args: character_raw_data: Raw character data. Returns: Strategy item ID if found, None otherwise. """ try: modules = character_raw_data.get("modules", []) for module in modules: module_title = module.get("title", "") if module_title in ["角色攻略", "角色养成推荐"]: components = module.get("components", []) for component in components: content = component.get("content", "") if content: item_id = self._extract_item_id_from_html(content) if item_id: self.logger.debug(f"Extracted strategy item ID: {item_id}") return item_id self.logger.debug("No strategy item ID found") return None except Exception as e: self.logger.warning(f"Failed to extract strategy item ID: {e}") return None def _extract_item_id_from_html(self, html_content: str) -> str | None: """Extract item ID from HTML content using regex. Args: html_content: HTML content to search. Returns: Extracted item ID or None. """ if not html_content: return None pattern = r"https://wiki\.kurobbs\.com/mc/item/(\d+)" match = re.search(pattern, html_content) return match.group(1) if match else None async def _fetch_strategy_content(self, strategy_item_id: str) -> dict[str, Any] | None: """Fetch strategy content from API. Args: strategy_item_id: Strategy item ID. Returns: Strategy content data or None if failed. """ try: self.logger.info(f"Fetching strategy content for ID: {strategy_item_id}") # Use the same API client as the repository strategy_data = await self.character_repository.api_client.fetch_entry_detail(strategy_item_id) if strategy_data: self.logger.debug("Strategy content fetched successfully") else: self.logger.warning(f"No strategy content found for ID: {strategy_item_id}") return strategy_data except Exception as e: self.logger.error(f"Failed to fetch strategy content: {e}") return None def _generate_strategy_link_markdown(self, strategy_item_id: str) -> str: """Generate markdown for strategy link. Args: strategy_item_id: Strategy item ID. Returns: Markdown formatted strategy link. """ url = f"https://wiki.kurobbs.com/mc/item/{strategy_item_id}" return f"""## Character Strategy Link - Strategy Item ID: {strategy_item_id} - [View Strategy]({url})""" # Factory function for dependency injection def create_character_service( character_repository: CharacterRepository, content_parser: StrategyBasedContentParser, markdown_service: "MarkdownService", ) -> CharacterService: """Create character service. Args: character_repository: Character repository. content_parser: Content parser. markdown_service: Markdown service. Returns: CharacterService instance. """ return CharacterService(character_repository, content_parser, markdown_service)

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jacksmith3888/wuwa-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server