diff --git a/scryfall-google-sheets.js b/scryfall-google-sheets.js index 9ffa513..53cff7a 100644 --- a/scryfall-google-sheets.js +++ b/scryfall-google-sheets.js @@ -1,9 +1,14 @@ // this function is available here: // https://github.com/scryfall/google-sheets/blob/main/scryfall-google-sheets.js -// and was last updated on 2021-01-08 (probably) +// and was updated with bug fixes for API rate limits and User-Agent policies (2026-04-03). const MAX_RESULTS_ = 700; // a safe max due to Google Sheets timeout system +// ---- Configure these ---- +const SCRYFALL_USER_AGENT_ = "TLASheet-Scryfall-Integration/1.1"; +const SCRYFALL_ACCEPT_ = "application/json;q=0.9,*/*;q=0.8"; +// ------------------------- + /** * Inserts the results of a search in Scryfall into your spreadsheet * @@ -17,8 +22,8 @@ const MAX_RESULTS_ = 700; // a safe max due to Google Sheets timeout system * @customfunction */ const SCRYFALL = (query, fields = "name", num_results = 150, - order = "name", dir = "auto", unique = "cards") => { - if (query === undefined) { + order = "name", dir = "auto", unique = "cards") => { + if (query === undefined) { throw new Error("Must include a query"); } @@ -28,7 +33,9 @@ const SCRYFALL = (query, fields = "name", num_results = 150, } // the docs say fields is space separated, but allow comma separated too - fields = fields.split(/[\s,]+/); + if (typeof fields === "string") { + fields = fields.split(/[\s,]+/); + } // most people won't know the JSON field names for cards, so let's do some mapping of // what they'll try to what it should be @@ -80,7 +87,9 @@ const SCRYFALL = (query, fields = "name", num_results = 150, // a little hack to make images return an image function; note that Google // sheets doesn't currently execute it or anything - card["image"] = `=IMAGE("${card["image_uris"]["normal"]}", 4, 340, 244)`; + if (card["image_uris"] && card["image_uris"]["normal"]) { + card["image"] = `=IMAGE("${card["image_uris"]["normal"]}", 4, 340, 244)`; + } fields.forEach(field => { // grab the field from the card data @@ -99,6 +108,11 @@ const SCRYFALL = (query, fields = "name", num_results = 150, output.push(row); }); + // If no cards were found, return an error message to display in the cell gracefully + if (output.length === 0) { + return [Array(fields.length).fill("Card not found")]; + } + return output; }; @@ -106,7 +120,6 @@ const deepFind_ = (obj, path) => { return path.split(".").reduce((prev, curr) => prev && prev[curr], obj) }; - // paginated query of scryfall const scryfallSearch_ = (params, num_results = MAX_RESULTS_) => { const query_string = Object.entries(params).map(([key, val]) => `${key}=${encodeURIComponent(val)}`).join('&'); @@ -114,28 +127,60 @@ const scryfallSearch_ = (params, num_results = MAX_RESULTS_) => { let data = []; let page = 1; - let response; + let responseText; + + // Set up headers to comply with new Scryfall API requirements + const options = { + 'method': 'get', + 'headers': { + 'User-Agent': SCRYFALL_USER_AGENT_, + 'Accept': SCRYFALL_ACCEPT_ + }, + 'muteHttpExceptions': true + }; // try to get the results from scryfall try { while (true) { - response = JSON.parse(UrlFetchApp.fetch(`${scryfall_url}&page=${page}`).getContentText()); + Utilities.sleep(150); // Respect Scryfall's rate limit of 10 requests/sec - if (!response.data) { - throw new Error("No results from Scryfall"); + let response = UrlFetchApp.fetch(`${scryfall_url}&page=${page}`, options); + let responseCode = response.getResponseCode(); + responseText = response.getContentText(); + + // Handle 404 (no results found for the query) Gracefully + if (responseCode === 404) { + break; // Return empty data array } - data.push(...response.data); + // Handle Rate Limiting + if (responseCode === 429) { + Utilities.sleep(1000); // Sleep longer if we hit rate limits and retry + continue; + } - if (!response.has_more || data.length > num_results) { + // Throw error ONLY on other actual HTTP errors + if (responseCode !== 200) { + throw new Error(`Scryfall API returned ${responseCode}: ${responseText}`); + } + + let parsedResponse = JSON.parse(responseText); + + if (!parsedResponse.data) { + break; + } + + data.push(...parsedResponse.data); + + if (!parsedResponse.has_more || data.length >= num_results) { break; } page++; } } catch (error) { - throw new Error(`Unable to retrieve results from Scryfall: ${error}`); + throw new Error(`Unable to retrieve results from Scryfall: ${error.message}`); } return data; -}; +}; \ No newline at end of file