| 584 | * @returns {string | null} AICC path | 584 | * @returns {string | null} AICC path |
| 585 | */ | 585 | */ |
| 586 | function parseAICC(url) { | 586 | function parseAICC(url) { |
| 587 | const pattern = /^https?:\/\/aicharactercards\.com\/character-cards\/([^/]+)\/([^/]+)\/?$|([^/]+)\/([^/]+)$/; | 587 | try { |
| 588 | const match = url.match(pattern); | 588 | if (isValidUrl(url)) { |
| 589 | if (match) { | 589 | const urlObj = new URL(url); |
| 590 | // Match group 1 & 2 for full URL, 3 & 4 for relative path | 590 | // Split the path and remove empty strings caused by trailing slashes |
| 591 | return match[1] && match[2] ? `${match[1]}/${match[2]}` : `${match[3]}/${match[4]}`; | 591 | const parts = urlObj.pathname.split('/').filter(Boolean); |
| | 592 | if (parts.length >= 2) { |
| | 593 | // Always grab the last two segments (author/character) |
| | 594 | return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`; |
| | 595 | } |
| | 596 | } else { |
| | 597 | // Fallback for relative paths or raw "author/character" strings |
| | 598 | const parts = url.split('/').filter(Boolean); |
| | 599 | if (parts.length >= 2) { |
| | 600 | return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`; |
| | 601 | } |
| | 602 | } |
| | 603 | } catch (e) { |
| | 604 | console.error('Error parsing AICC URL:', e); |
| 592 | } | 605 | } |
| 593 | return null; | 606 | return null; |
| 594 | } | 607 | } |