UNPKG

maplestorysea-mcp-server

Version:

NEXON MapleStory SEA API MCP Server for Claude Desktop - Complete character info, union details, guild data, rankings optimized for SEA servers

873 lines 40.3 kB
"use strict"; /** * Character Information Tools for MCP Maple * Provides MCP tools for retrieving MapleStory SEA character data */ Object.defineProperty(exports, "__esModule", { value: true }); exports.FindCharacterRankingTool = exports.GetJobClassInfoTool = exports.GetCharacterAnalysisTool = exports.GetCharacterFullInfoTool = exports.GetCharacterEquipmentTool = exports.GetCharacterStatsTool = exports.GetCharacterBasicInfoTool = void 0; const base_tool_1 = require("./base-tool"); const job_utils_1 = require("../utils/job-utils"); const constants_1 = require("../api/constants"); const server_utils_1 = require("../utils/server-utils"); /** * Format equipment option values for SEA region display */ function formatEquipmentOption(option) { if (!option || typeof option !== 'object') { return option; } const formatted = { ...option }; // Format numeric stat values in equipment options Object.keys(formatted).forEach((key) => { const value = formatted[key]; if (typeof value === 'string') { const numValue = parseInt(value); if (!isNaN(numValue) && numValue > 0) { // Check if it's likely a percentage value if (key.includes('확률') || key.includes('rate') || key.includes('데미지') || key.includes('damage') || key.includes('무시') || key.includes('ignore') || key.includes('율') || key.includes('스탠스') || key.includes('stance')) { const percentValue = parseFloat(value); if (!isNaN(percentValue)) { formatted[key] = (0, server_utils_1.formatSEAPercentage)(percentValue); } } else { formatted[key] = (0, server_utils_1.formatSEANumber)(numValue); } } } }); return formatted; } /** * Tool for getting basic character information */ class GetCharacterBasicInfoTool extends base_tool_1.EnhancedBaseTool { name = 'get_character_basic_info'; description = 'Retrieve basic information about a MapleStory SEA character including level, job, world, and guild'; inputSchema = { type: 'object', properties: { characterName: { type: 'string', description: 'The name of the character to look up', minLength: 1, maxLength: 12, pattern: '^[a-zA-Z0-9]+$', }, date: { type: 'string', description: 'Date for character info in YYYY-MM-DD format (optional, defaults to yesterday)', pattern: '^\\d{4}-\\d{2}-\\d{2}$', }, }, required: ['characterName'], additionalProperties: false, }; metadata = { category: base_tool_1.ToolCategory.CHARACTER, tags: ['character', 'basic', 'info', 'level', 'job'], examples: [ { description: 'Get basic info for character', arguments: { characterName: 'AquilaHero' }, }, { description: 'Get basic info for specific date', arguments: { characterName: 'AquilaHero', date: '2024-01-15' }, }, ], }; async executeImpl(args, context) { const characterName = this.getRequiredString(args, 'characterName'); const date = this.getOptionalString(args, 'date'); try { const startTime = Date.now(); // Get character OCID first context.logger.info('Looking up character OCID', { characterName }); const ocidResult = await context.nexonClient.getCharacterOcid(characterName); const ocid = ocidResult.ocid; // Get basic character info context.logger.info('Fetching character basic info', { characterName, ocid }); const basicInfo = await context.nexonClient.getCharacterBasic(ocid, date); const executionTime = Date.now() - startTime; context.logger.info('Character basic info retrieved successfully', { characterName, level: basicInfo.character_level, job: basicInfo.character_class, executionTime, }); return this.formatResult({ characterName: basicInfo.character_name, level: (0, server_utils_1.formatSEANumber)(parseInt(basicInfo.character_level.toString())), job: basicInfo.character_class, jobDetail: basicInfo.character_class_level, exp: (0, server_utils_1.formatSEANumber)(typeof basicInfo.character_exp === 'string' ? parseInt(basicInfo.character_exp) : basicInfo.character_exp), expRate: (0, server_utils_1.formatSEAPercentage)(parseFloat(basicInfo.character_exp_rate)), guildName: basicInfo.character_guild_name || null, world: basicInfo.world_name, gender: basicInfo.character_gender, // Basic stats are not available in CharacterBasic endpoint // They need to be fetched separately from getCharacterStat date: basicInfo.date ? (0, server_utils_1.formatSEADate)(basicInfo.date) : date ? (0, server_utils_1.formatSEADate)(date) : (0, server_utils_1.getCurrentSEADate)(), }, { executionTime, cacheHit: false, apiCalls: 2, // OCID lookup + basic info }); } catch (error) { context.logger.error('Failed to get character basic info', { characterName, error: error instanceof Error ? error.message : String(error), }); return this.formatError(`Failed to get basic info for character "${characterName}": ${error instanceof Error ? error.message : String(error)}`); } } } exports.GetCharacterBasicInfoTool = GetCharacterBasicInfoTool; /** * Tool for getting detailed character statistics */ class GetCharacterStatsTool extends base_tool_1.EnhancedBaseTool { name = 'get_character_stats'; description = 'Retrieve detailed statistics for a MapleStory SEA character including damage, critical rate, and all combat stats'; inputSchema = { type: 'object', properties: { characterName: { type: 'string', description: 'The name of the character to look up', minLength: 1, maxLength: 12, pattern: '^[a-zA-Z0-9]+$', }, date: { type: 'string', description: 'Date for character stats in YYYY-MM-DD format (optional, defaults to yesterday)', pattern: '^\\d{4}-\\d{2}-\\d{2}$', }, }, required: ['characterName'], additionalProperties: false, }; metadata = { category: base_tool_1.ToolCategory.CHARACTER, tags: ['character', 'stats', 'damage', 'critical', 'combat'], examples: [ { description: 'Get detailed stats for character', arguments: { characterName: 'AquilaHero' }, }, { description: 'Get stats for specific date', arguments: { characterName: 'AquilaHero', date: '2024-01-15' }, }, ], }; async executeImpl(args, context) { const characterName = this.getRequiredString(args, 'characterName'); const date = this.getOptionalString(args, 'date'); try { const startTime = Date.now(); // Get character OCID first context.logger.info('Looking up character OCID for stats', { characterName }); const ocidResult = await context.nexonClient.getCharacterOcid(characterName); const ocid = ocidResult.ocid; // Get character stats context.logger.info('Fetching character detailed stats', { characterName, ocid }); const stats = await context.nexonClient.getCharacterStat(ocid, date); const executionTime = Date.now() - startTime; // Group stats by category for better presentation const basicStats = stats.final_stat?.filter((s) => ['STR', 'DEX', 'INT', 'LUK', 'HP', 'MP'].includes(s.stat_name)) || []; const combatStats = stats.final_stat?.filter((s) => [ // Korean stat names '공격력', '마력', '크리티컬 확률', '크리티컬 데미지', '보스 몬스터 데미지', '방어율 무시', '데미지', // English stat names 'Attack Power', 'Magic Power', 'Critical Rate', 'Critical Damage', 'Boss Monster Damage', 'Defense Rate Ignore', 'Damage', ].includes(s.stat_name)) || []; const defenseStats = stats.final_stat?.filter((s) => [ // Korean stat names '물리방어력', '마법방어력', '스탠스', '방어율', // English stat names 'Physical Defense', 'Magic Defense', 'Stance', 'Defense Rate', ].includes(s.stat_name)) || []; context.logger.info('Character stats retrieved successfully', { characterName, statCount: stats.final_stat?.length || 0, executionTime, }); return this.formatResult({ characterName, date: stats.date ? (0, server_utils_1.formatSEADate)(stats.date) : date ? (0, server_utils_1.formatSEADate)(date) : (0, server_utils_1.getCurrentSEADate)(), basicStats: basicStats.reduce((acc, stat) => { const numValue = parseInt(stat.stat_value); acc[stat.stat_name] = isNaN(numValue) ? stat.stat_value : (0, server_utils_1.formatSEANumber)(numValue); return acc; }, {}), combatStats: combatStats.reduce((acc, stat) => { const numValue = parseInt(stat.stat_value); // Check if it's a percentage stat if (stat.stat_name.includes('확률') || stat.stat_name.includes('Rate') || stat.stat_name.includes('데미지') || stat.stat_name.includes('Damage') || stat.stat_name.includes('무시') || stat.stat_name.includes('Ignore')) { const percentValue = parseFloat(stat.stat_value); acc[stat.stat_name] = isNaN(percentValue) ? stat.stat_value : (0, server_utils_1.formatSEAPercentage)(percentValue); } else { acc[stat.stat_name] = isNaN(numValue) ? stat.stat_value : (0, server_utils_1.formatSEANumber)(numValue); } return acc; }, {}), defenseStats: defenseStats.reduce((acc, stat) => { const numValue = parseInt(stat.stat_value); // Check if it's a percentage stat if (stat.stat_name.includes('율') || stat.stat_name.includes('Rate')) { const percentValue = parseFloat(stat.stat_value); acc[stat.stat_name] = isNaN(percentValue) ? stat.stat_value : (0, server_utils_1.formatSEAPercentage)(percentValue); } else { acc[stat.stat_name] = isNaN(numValue) ? stat.stat_value : (0, server_utils_1.formatSEANumber)(numValue); } return acc; }, {}), allStats: stats.final_stat?.reduce((acc, stat) => { const numValue = parseInt(stat.stat_value); // Apply appropriate formatting based on stat type if (stat.stat_name.includes('확률') || stat.stat_name.includes('Rate') || stat.stat_name.includes('데미지') || stat.stat_name.includes('Damage') || stat.stat_name.includes('무시') || stat.stat_name.includes('Ignore') || stat.stat_name.includes('율') || stat.stat_name.includes('스탠스') || stat.stat_name.includes('Stance')) { const percentValue = parseFloat(stat.stat_value); acc[stat.stat_name] = isNaN(percentValue) ? stat.stat_value : (0, server_utils_1.formatSEAPercentage)(percentValue); } else { acc[stat.stat_name] = isNaN(numValue) ? stat.stat_value : (0, server_utils_1.formatSEANumber)(numValue); } return acc; }, {}) || {}, remainAp: (0, server_utils_1.formatSEANumber)(parseInt(stats.remain_ap.toString())), }, { executionTime, cacheHit: false, apiCalls: 2, // OCID lookup + stats }); } catch (error) { context.logger.error('Failed to get character stats', { characterName, error: error instanceof Error ? error.message : String(error), }); return this.formatError(`Failed to get stats for character "${characterName}": ${error instanceof Error ? error.message : String(error)}`); } } } exports.GetCharacterStatsTool = GetCharacterStatsTool; /** * Tool for getting character equipment information */ class GetCharacterEquipmentTool extends base_tool_1.EnhancedBaseTool { name = 'get_character_equipment'; description = 'Retrieve equipment information for a MapleStory SEA character including all equipped items and their stats'; inputSchema = { type: 'object', properties: { characterName: { type: 'string', description: 'The name of the character to look up', minLength: 1, maxLength: 12, pattern: '^[a-zA-Z0-9]+$', }, date: { type: 'string', description: 'Date for character equipment in YYYY-MM-DD format (optional, defaults to yesterday)', pattern: '^\\d{4}-\\d{2}-\\d{2}$', }, }, required: ['characterName'], additionalProperties: false, }; metadata = { category: base_tool_1.ToolCategory.CHARACTER, tags: ['character', 'equipment', 'items', 'gear', 'weapon'], examples: [ { description: 'Get equipment for character', arguments: { characterName: 'AquilaHero' }, }, { description: 'Get equipment for specific date', arguments: { characterName: 'AquilaHero', date: '2024-01-15' }, }, ], }; async executeImpl(args, context) { const characterName = this.getRequiredString(args, 'characterName'); const date = this.getOptionalString(args, 'date'); try { const startTime = Date.now(); // Get character OCID first context.logger.info('Looking up character OCID for equipment', { characterName }); const ocidResult = await context.nexonClient.getCharacterOcid(characterName); const ocid = ocidResult.ocid; // Get character equipment context.logger.info('Fetching character equipment', { characterName, ocid }); const equipment = await context.nexonClient.getCharacterItemEquipment(ocid, date); const executionTime = Date.now() - startTime; // Organize equipment by category const equipmentBySlot = equipment.item_equipment?.reduce((acc, item) => { acc[item.item_equipment_slot] = { name: item.item_name, icon: item.item_icon, description: item.item_description, shapeIcon: item.item_shape_icon, shapeName: item.item_shape_name, gender: item.item_gender, totalOption: formatEquipmentOption(item.item_total_option), baseOption: formatEquipmentOption(item.item_base_option), exceptionalOption: formatEquipmentOption(item.item_exceptional_option), addOption: formatEquipmentOption(item.item_add_option), starforceOption: formatEquipmentOption(item.item_starforce_option), etcOption: formatEquipmentOption(item.item_etc_option), starforce: (0, server_utils_1.formatSEANumber)(parseInt(item.starforce.toString())), starforceScrollFlag: item.starforce_scroll_flag, cuttableCount: (0, server_utils_1.formatSEANumber)(parseInt(item.cuttable_count.toString())), goldenHammerFlag: item.golden_hammer_flag, scrollUpgrade: (0, server_utils_1.formatSEANumber)(parseInt(item.scroll_upgrade.toString())), scrollUpgradableCount: (0, server_utils_1.formatSEANumber)(parseInt(item.scroll_upgradeable_count.toString())), scrollResilienceCount: (0, server_utils_1.formatSEANumber)(parseInt(item.scroll_resilience_count.toString())), scrollUpgradeCount: (0, server_utils_1.formatSEANumber)(parseInt(item.scroll_upgradeable_count.toString())), potential: item.potential_option_grade, potentialOptions: [ item.potential_option_1, item.potential_option_2, item.potential_option_3, ].filter(Boolean), additionalPotential: item.additional_potential_option_grade, additionalPotentialOptions: [ item.additional_potential_option_1, item.additional_potential_option_2, item.additional_potential_option_3, ].filter(Boolean), }; return acc; }, {}) || {}; context.logger.info('Character equipment retrieved successfully', { characterName, equipmentCount: equipment.item_equipment?.length || 0, executionTime, }); return this.formatResult({ characterName, date: equipment.date ? (0, server_utils_1.formatSEADate)(equipment.date) : date ? (0, server_utils_1.formatSEADate)(date) : (0, server_utils_1.getCurrentSEADate)(), equipment: equipmentBySlot, equipmentList: equipment.item_equipment || [], presetNo: equipment.preset_no, title: equipment.title, }, { executionTime, cacheHit: false, apiCalls: 2, // OCID lookup + equipment }); } catch (error) { context.logger.error('Failed to get character equipment', { characterName, error: error instanceof Error ? error.message : String(error), }); return this.formatError(`Failed to get equipment for character "${characterName}": ${error instanceof Error ? error.message : String(error)}`); } } } exports.GetCharacterEquipmentTool = GetCharacterEquipmentTool; /** * Tool for getting comprehensive character information (combines basic, stats, and equipment) */ class GetCharacterFullInfoTool extends base_tool_1.EnhancedBaseTool { name = 'get_character_full_info'; description = 'Retrieve comprehensive character information including basic info, stats, and equipment in a single request'; inputSchema = { type: 'object', properties: { characterName: { type: 'string', description: 'The name of the character to look up', minLength: 1, maxLength: 12, pattern: '^[a-zA-Z0-9]+$', }, date: { type: 'string', description: 'Date for character info in YYYY-MM-DD format (optional, defaults to yesterday)', pattern: '^\\d{4}-\\d{2}-\\d{2}$', }, includeEquipment: { type: 'boolean', description: 'Whether to include equipment information (defaults to true)', default: true, }, }, required: ['characterName'], additionalProperties: false, }; metadata = { category: base_tool_1.ToolCategory.CHARACTER, tags: ['character', 'comprehensive', 'full', 'complete', 'all'], examples: [ { description: 'Get full character information', arguments: { characterName: 'AquilaHero' }, }, { description: 'Get full info without equipment', arguments: { characterName: 'AquilaHero', includeEquipment: false }, }, { description: 'Get full info for specific date', arguments: { characterName: 'AquilaHero', date: '2024-01-15' }, }, ], }; async executeImpl(args, context) { const characterName = this.getRequiredString(args, 'characterName'); const date = this.getOptionalString(args, 'date'); const includeEquipment = this.getOptionalBoolean(args, 'includeEquipment', true); try { const startTime = Date.now(); // Get character OCID first context.logger.info('Looking up character OCID for full info', { characterName }); const ocidResult = await context.nexonClient.getCharacterOcid(characterName); const ocid = ocidResult.ocid; // Get all character data in parallel context.logger.info('Fetching comprehensive character data', { characterName, ocid, includeEquipment, }); const [basicInfo, stats, equipment] = await Promise.all([ context.nexonClient.getCharacterBasic(ocid, date), context.nexonClient.getCharacterStat(ocid, date), includeEquipment ? context.nexonClient.getCharacterItemEquipment(ocid, date) : null, ]); const executionTime = Date.now() - startTime; // Get job class insights const jobClass = basicInfo.character_class; const jobCategory = (0, job_utils_1.getJobCategory)(jobClass); const primaryStat = (0, job_utils_1.getJobPrimaryStat)(jobClass); const jobDescription = (0, job_utils_1.getJobDescription)(jobClass); const recommendedBuild = (0, job_utils_1.getRecommendedBuild)(jobClass); const result = { characterName: basicInfo.character_name, basicInfo: { level: (0, server_utils_1.formatSEANumber)(parseInt(basicInfo.character_level.toString())), job: basicInfo.character_class, jobDetail: basicInfo.character_class_level, exp: (0, server_utils_1.formatSEANumber)(typeof basicInfo.character_exp === 'string' ? parseInt(basicInfo.character_exp) : basicInfo.character_exp), expRate: (0, server_utils_1.formatSEAPercentage)(parseFloat(basicInfo.character_exp_rate)), guildName: basicInfo.character_guild_name || null, world: basicInfo.world_name, gender: basicInfo.character_gender, date: basicInfo.date ? (0, server_utils_1.formatSEADate)(basicInfo.date) : date ? (0, server_utils_1.formatSEADate)(date) : (0, server_utils_1.getCurrentSEADate)(), jobInsights: { category: jobCategory, primaryStat: primaryStat, description: jobDescription, recommendedBuild: recommendedBuild, }, }, stats: { basicStats: stats.final_stat ?.filter((s) => ['STR', 'DEX', 'INT', 'LUK', 'HP', 'MP'].includes(s.stat_name)) .reduce((acc, stat) => { const numValue = parseInt(stat.stat_value); acc[stat.stat_name] = isNaN(numValue) ? stat.stat_value : (0, server_utils_1.formatSEANumber)(numValue); return acc; }, {}) || {}, combatStats: stats.final_stat ?.filter((s) => [ // Korean stat names '공격력', '마력', '크리티컬 확률', '크리티컬 데미지', '보스 몬스터 데미지', '방어율 무시', '데미지', // English stat names 'Attack Power', 'Magic Power', 'Critical Rate', 'Critical Damage', 'Boss Monster Damage', 'Defense Rate Ignore', 'Damage', ].includes(s.stat_name)) .reduce((acc, stat) => { const numValue = parseInt(stat.stat_value); // Check if it's a percentage stat if (stat.stat_name.includes('확률') || stat.stat_name.includes('Rate') || stat.stat_name.includes('데미지') || stat.stat_name.includes('Damage') || stat.stat_name.includes('무시') || stat.stat_name.includes('Ignore')) { const percentValue = parseFloat(stat.stat_value); acc[stat.stat_name] = isNaN(percentValue) ? stat.stat_value : (0, server_utils_1.formatSEAPercentage)(percentValue); } else { acc[stat.stat_name] = isNaN(numValue) ? stat.stat_value : (0, server_utils_1.formatSEANumber)(numValue); } return acc; }, {}) || {}, remainAp: (0, server_utils_1.formatSEANumber)(parseInt(stats.remain_ap.toString())), }, ...(includeEquipment && equipment && { equipment: { presetNo: equipment.preset_no, title: equipment.title, items: equipment.item_equipment?.reduce((acc, item) => { acc[item.item_equipment_slot] = { name: item.item_name, icon: item.item_icon, starforce: (0, server_utils_1.formatSEANumber)(parseInt(item.starforce.toString())), potential: item.potential_option_grade, potentialOptions: [ item.potential_option_1, item.potential_option_2, item.potential_option_3, ].filter(Boolean), }; return acc; }, {}) || {}, }, }), date: date ? (0, server_utils_1.formatSEADate)(date) : (0, server_utils_1.getCurrentSEADate)(), }; context.logger.info('Character full info retrieved successfully', { characterName, level: basicInfo.character_level, job: basicInfo.character_class, jobCategory: jobCategory, primaryStat: primaryStat, statsCount: stats.final_stat?.length || 0, equipmentCount: equipment?.item_equipment?.length || 0, executionTime, }); return this.formatResult(result, { executionTime, cacheHit: false, apiCalls: includeEquipment ? 4 : 3, // OCID + basic + stats + equipment (optional) }); } catch (error) { context.logger.error('Failed to get character full info', { characterName, error: error instanceof Error ? error.message : String(error), }); return this.formatError(`Failed to get full information for character "${characterName}": ${error instanceof Error ? error.message : String(error)}`); } } } exports.GetCharacterFullInfoTool = GetCharacterFullInfoTool; /** * Tool for getting comprehensive character analysis */ class GetCharacterAnalysisTool extends base_tool_1.EnhancedBaseTool { name = 'get_character_analysis'; description = 'Get comprehensive character analysis including equipment scoring, set effects, and improvement recommendations'; inputSchema = { type: 'object', properties: { characterName: { type: 'string', description: 'The name of the character to analyze', minLength: 1, maxLength: 12, pattern: '^[a-zA-Z0-9]+$', }, date: { type: 'string', description: 'Date for analysis in YYYY-MM-DD format (optional)', pattern: '^\\d{4}-\\d{2}-\\d{2}$', }, }, required: ['characterName'], additionalProperties: false, }; metadata = { category: base_tool_1.ToolCategory.CHARACTER, tags: ['character', 'analysis', 'equipment', 'recommendations', 'scoring'], examples: [ { description: 'Analyze character equipment and stats', arguments: { characterName: 'AquilaHero' }, }, ], }; async executeImpl(args, context) { const characterName = this.getRequiredString(args, 'characterName'); const date = this.getOptionalString(args, 'date'); try { const startTime = Date.now(); const analysis = await context.nexonClient.getCharacterAnalysis(characterName, date); const executionTime = Date.now() - startTime; context.logger.info('Character analysis completed', { characterName, characterScore: analysis.analysis?.characterScore, equipmentCount: analysis.equipment?.item_equipment?.length || 0, setEffectsCount: analysis.analysis?.equipment?.setEffects?.length || 0, executionTime, }); return this.formatResult(analysis, { executionTime, cacheHit: false, apiCalls: 8, // Multiple API calls for comprehensive analysis }); } catch (error) { context.logger.error('Failed to analyze character', { characterName, error: error instanceof Error ? error.message : String(error), }); return this.formatError(`Failed to analyze character "${characterName}": ${error instanceof Error ? error.message : String(error)}`); } } } exports.GetCharacterAnalysisTool = GetCharacterAnalysisTool; /** * Tool for getting SEA job class information */ class GetJobClassInfoTool extends base_tool_1.EnhancedBaseTool { name = 'get_job_class_info'; description = 'Get detailed information about a specific job class in MapleStory SEA including category, stats, and advancement paths'; inputSchema = { type: 'object', properties: { jobClass: { type: 'string', description: 'The job class name to get information about', enum: [...constants_1.JOB_CLASSES], }, }, required: ['jobClass'], additionalProperties: false, }; metadata = { category: base_tool_1.ToolCategory.CHARACTER, tags: ['job', 'class', 'info', 'stats', 'advancement'], examples: [ { description: 'Get info about Hero job class', arguments: { jobClass: 'Hero' }, }, { description: 'Get info about Arch Mage (Fire, Poison)', arguments: { jobClass: 'Arch Mage (Fire, Poison)' }, }, ], }; async executeImpl(args, context) { const jobClass = this.getRequiredString(args, 'jobClass'); try { const startTime = Date.now(); // Validate job class if (!(0, job_utils_1.validateJobClass)(jobClass)) { return this.formatError(`Invalid job class: ${jobClass}`); } // Get job information const category = (0, job_utils_1.getJobCategory)(jobClass); const primaryStat = (0, job_utils_1.getJobPrimaryStat)(jobClass); const description = (0, job_utils_1.getJobDescription)(jobClass); const formattedName = (0, job_utils_1.formatJobClassName)(jobClass); const isBeginner = (0, job_utils_1.isBeginnerJob)(jobClass); const recommendedBuild = (0, job_utils_1.getRecommendedBuild)(jobClass); const executionTime = Date.now() - startTime; const result = { jobClass: formattedName, category: category, primaryStat: primaryStat, description: description, isBeginner: isBeginner, recommendedBuild: recommendedBuild, availableInSEA: true, // All jobs in our enum are available in SEA }; context.logger.info('Job class information retrieved', { jobClass, category, primaryStat, executionTime, }); return this.formatResult(result, { executionTime, cacheHit: false, apiCalls: 0, // No API calls needed }); } catch (error) { context.logger.error('Failed to get job class info', { jobClass, error: error instanceof Error ? error.message : String(error), }); return this.formatError(`Failed to get information for job class "${jobClass}": ${error instanceof Error ? error.message : String(error)}`); } } } exports.GetJobClassInfoTool = GetJobClassInfoTool; /** * Tool for finding character ranking position */ class FindCharacterRankingTool extends base_tool_1.EnhancedBaseTool { name = 'find_character_ranking'; description = "Find a character's position in the overall ranking system across multiple pages"; inputSchema = { type: 'object', properties: { characterName: { type: 'string', description: 'The name of the character to find in rankings', minLength: 1, maxLength: 12, pattern: '^[a-zA-Z0-9]+$', }, worldName: { type: 'string', description: 'World/server name to search in (optional)', enum: ['Aquila', 'Bootes', 'Cassiopeia', 'Draco'], }, className: { type: 'string', description: 'Character class to filter by (optional)', enum: [...constants_1.JOB_CLASSES], }, maxPages: { type: 'number', description: 'Maximum pages to search (default: 10)', minimum: 1, maximum: 20, }, }, required: ['characterName'], additionalProperties: false, }; metadata = { category: base_tool_1.ToolCategory.CHARACTER, tags: ['character', 'ranking', 'position', 'search'], examples: [ { description: 'Find character ranking position', arguments: { characterName: 'AquilaHero' }, }, { description: 'Find ranking in specific world', arguments: { characterName: 'AquilaHero', worldName: 'Aquila' }, }, ], }; async executeImpl(args, context) { const characterName = this.getRequiredString(args, 'characterName'); const worldName = this.getOptionalString(args, 'worldName'); const className = this.getOptionalString(args, 'className'); const maxPages = this.getOptionalNumber(args, 'maxPages', 10); try { const startTime = Date.now(); const result = await context.nexonClient.findCharacterRankingPosition(characterName, worldName, className, maxPages); const executionTime = Date.now() - startTime; context.logger.info('Character ranking search completed', { characterName, found: result.found, position: result.position, searchedPages: result.searchedPages, executionTime, }); // Format the position number for SEA display const formattedResult = { ...result, position: result.position ? (0, server_utils_1.formatSEANumber)(result.position) : undefined, searchedPages: (0, server_utils_1.formatSEANumber)(result.searchedPages), }; return this.formatResult(formattedResult, { executionTime, cacheHit: false, apiCalls: result.searchedPages, }); } catch (error) { context.logger.error('Failed to find character ranking', { characterName, error: error instanceof Error ? error.message : String(error), }); return this.formatError(`Failed to find ranking for character "${characterName}": ${error instanceof Error ? error.message : String(error)}`); } } } exports.FindCharacterRankingTool = FindCharacterRankingTool; //# sourceMappingURL=character-tools.js.map