1+ const normalizeNameKey = ( value ) =>
2+ ( value ?? "" ) . toString ( ) . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] + / g, "" ) . trim ( ) ;
3+
4+ const buildNameCandidates = ( name ) => {
5+ const trimmed = ( name ?? "" ) . toString ( ) . trim ( ) ;
6+ if ( ! trimmed ) return [ ] ;
7+
8+ const candidates = new Set ( [ trimmed ] ) ;
9+ const withoutParens = trimmed . replace ( / \s * \( [ ^ ) ] * \) \s * $ / , "" ) . trim ( ) ;
10+ const withoutBrackets = trimmed . replace ( / \s * \[ [ ^ \] ] * \] \s * $ / , "" ) . trim ( ) ;
11+ const withoutDashSuffix = trimmed . replace ( / \s * [ - – ] \s * [ ^ - – ] + $ / , "" ) . trim ( ) ;
12+ const withoutRarityToken = trimmed . replace (
13+ / \s * \b ( U T R | U R | S R | C R | G R | Q C R | P C R | P G R | S P | S E | P R | H R | R | C | S C R | S N R | G C R | G U R | G S R ) \b \s * $ / i,
14+ ""
15+ ) . trim ( ) ;
16+
17+ [ withoutParens , withoutBrackets , withoutDashSuffix , withoutRarityToken ]
18+ . filter ( Boolean )
19+ . forEach ( ( candidate ) => candidates . add ( candidate ) ) ;
20+
21+ return Array . from ( candidates ) ;
22+ } ;
23+
24+ const fetchExactName = async ( name ) => {
25+ const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?name=${ encodeURIComponent ( name ) } &tcgplayer_data=true` ;
26+ const response = await fetch ( url ) ;
27+ const data = await response . json ( ) ;
28+ if ( ! Array . isArray ( data ?. data ) || data . data . length === 0 ) {
29+ return null ;
30+ }
31+ return data . data [ 0 ] ;
32+ } ;
33+
34+ const fetchFuzzyName = async ( name ) => {
35+ const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?fname=${ encodeURIComponent ( name ) } &tcgplayer_data=true` ;
36+ const response = await fetch ( url ) ;
37+ const data = await response . json ( ) ;
38+ return Array . isArray ( data ?. data ) ? data . data : [ ] ;
39+ } ;
40+
41+ const pickBestMatch = ( matches , candidates ) => {
42+ if ( ! Array . isArray ( matches ) || matches . length === 0 ) {
43+ return null ;
44+ }
45+
46+ const candidateKeys = new Set ( candidates . map ( normalizeNameKey ) . filter ( Boolean ) ) ;
47+ if ( candidateKeys . size === 0 ) {
48+ return matches [ 0 ] ;
49+ }
50+
51+ const exact = matches . find ( ( card ) => candidateKeys . has ( normalizeNameKey ( card ?. name ) ) ) ;
52+ return exact || matches [ 0 ] ;
53+ } ;
54+
55+ const formatCard = ( card ) => ( {
56+ id : card ?. id ,
57+ name : card ?. name ,
58+ type : card ?. type ,
59+ desc : card ?. desc ,
60+ frameType : card ?. frameType ,
61+ race : card ?. race ,
62+ archetype : card ?. archetype ,
63+ ygoprodeck_url : card ?. ygoprodeck_url ,
64+ card_images : card ?. card_images ?. map ( ( img ) => ( {
65+ id : img ?. id ,
66+ image_url : img ?. image_url ,
67+ image_url_small : img ?. image_url_small ,
68+ } ) ) || [ ] ,
69+ card_sets : card ?. card_sets ?. map ( ( set ) => ( {
70+ set_name : set . set_name ,
71+ set_code : set . set_code ,
72+ set_rarity : set . set_rarity ,
73+ rarity_code : set . set_rarity_code ,
74+ set_edition : set . set_edition || "Unknown Edition" ,
75+ set_price : set . set_price || "0.00" ,
76+ } ) ) || [ ] ,
77+ card_prices : card ?. card_prices ?. map ( ( price ) => ( {
78+ tcgplayer_price : price . tcgplayer_price || "0.00" ,
79+ ebay_price : price . ebay_price || "0.00" ,
80+ amazon_price : price . amazon_price || "0.00" ,
81+ } ) ) || [ ] ,
82+ } ) ;
83+
184export default async function handler ( req , res ) {
285 const { name } = req . query ;
386 const normalizedName = Array . isArray ( name ) ? name [ 0 ] : name ;
@@ -6,42 +89,32 @@ export default async function handler( req, res ) {
689 return res . status ( 400 ) . json ( { error : "Missing card name" } ) ;
790 }
891
92+ const candidates = buildNameCandidates ( normalizedName ) ;
93+ if ( candidates . length === 0 ) {
94+ return res . status ( 400 ) . json ( { error : "Missing card name" } ) ;
95+ }
96+
997 try {
10- const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?name=${ encodeURIComponent ( normalizedName ) } &tcgplayer_data=true` ;
11- const response = await fetch ( url ) ;
12- const data = await response . json ( ) ;
98+ let card = null ;
99+
100+ for ( const candidate of candidates ) {
101+ card = await fetchExactName ( candidate ) ;
102+ if ( card ) {
103+ break ;
104+ }
105+ }
106+
107+ if ( ! card ) {
108+ const fallbackName = candidates [ candidates . length - 1 ] ;
109+ const fuzzyMatches = await fetchFuzzyName ( fallbackName ) ;
110+ card = pickBestMatch ( fuzzyMatches , candidates ) ;
111+ }
13112
14- if ( ! Array . isArray ( data ?. data ) || data . data . length === 0 ) {
113+ if ( ! card ) {
15114 return res . status ( 404 ) . json ( { error : "Card not found" } ) ;
16115 }
17116
18- const card = data . data ?. [ 0 ] ;
19-
20- const formattedCard = {
21- id : card ?. id ,
22- name : card ?. name ,
23- type : card ?. type ,
24- desc : card ?. desc ,
25- frameType : card ?. frameType ,
26- race : card ?. race ,
27- archetype : card ?. archetype ,
28- ygoprodeck_url : card ?. ygoprodeck_url ,
29- card_sets : card ?. card_sets ?. map ( ( set ) => ( {
30- set_name : set . set_name ,
31- set_code : set . set_code ,
32- set_rarity : set . set_rarity ,
33- rarity_code : set . set_rarity_code ,
34- set_edition : set . set_edition || "Unknown Edition" ,
35- set_price : set . set_price || "0.00" ,
36- } ) ) || [ ] ,
37- card_prices : card ?. card_prices ?. map ( ( price ) => ( {
38- tcgplayer_price : price . tcgplayer_price || "0.00" ,
39- ebay_price : price . ebay_price || "0.00" ,
40- amazon_price : price . amazon_price || "0.00" ,
41- } ) ) || [ ] ,
42- } ;
43-
44- res . status ( 200 ) . json ( formattedCard ) ;
117+ res . status ( 200 ) . json ( formatCard ( card ) ) ;
45118 } catch ( error ) {
46119 console . error ( "Card lookup failed:" , error ) ;
47120 res . status ( 500 ) . json ( { error : "Internal Server Error" } ) ;
0 commit comments