👁️
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix search bug, showing weird tokens

+120 -16
+1 -2
package.json
··· 18 18 "typecheck:faster": "npm run typecheck -- --skipLibCheck --incremental", 19 19 "build:typelex": "typelex compile com.deckbelcher.*", 20 20 "build:lexicons": "lex-cli generate -c ./lex.config.js", 21 - "download:scryfall": "node --experimental-strip-types scripts/download-scryfall.ts", 22 - "screenshot:wireframe": "node --experimental-strip-types scripts/screenshot-wireframe.ts" 21 + "download:scryfall": "node --experimental-strip-types scripts/download-scryfall.ts" 23 22 }, 24 23 "dependencies": { 25 24 "@atcute/bluesky": "^3.2.8",
+6
scripts/download-scryfall.ts
··· 273 273 if (aDefault && !bDefault) return -1; 274 274 if (!aDefault && bDefault) return 1; 275 275 276 + // Deprioritize memorabilia (gold border, world champs, placeholder cards) 277 + const aMemo = a.set_type === "memorabilia"; 278 + const bMemo = b.set_type === "memorabilia"; 279 + if (!aMemo && bMemo) return -1; 280 + if (aMemo && !bMemo) return 1; 281 + 276 282 // Deprioritize Arena-only (paper and MTGO are both fine) 277 283 const aArenaOnly = a.games?.length === 1 && a.games[0] === "arena"; 278 284 const bArenaOnly = b.games?.length === 1 && b.games[0] === "arena";
+90
scripts/scryfall.sh
··· 1 + #!/usr/bin/env bash 2 + # Query Scryfall API for card data 3 + # 4 + # Usage: 5 + # ./scripts/scryfall.sh "ancestral recall" 6 + # ./scripts/scryfall.sh -f set_type "sol ring" 7 + # ./scripts/scryfall.sh -q "layout:token treasure" 8 + 9 + set -euo pipefail 10 + 11 + FIELD="" 12 + RAW_QUERY=false 13 + 14 + while [[ $# -gt 0 ]]; do 15 + case $1 in 16 + -f|--field) 17 + FIELD="$2" 18 + shift 2 19 + ;; 20 + -q|--query) 21 + RAW_QUERY=true 22 + shift 23 + ;; 24 + -h|--help) 25 + echo "Usage: $0 [options] <query>" 26 + echo "" 27 + echo "Options:" 28 + echo " -f, --field <name> Show specific field (e.g., set_type, layout, legalities)" 29 + echo " -q, --query Pass query directly to Scryfall (don't wrap in name search)" 30 + echo " -h, --help Show this help" 31 + echo "" 32 + echo "Examples:" 33 + echo " $0 \"ancestral recall\" # Search by name" 34 + echo " $0 -f set_type \"sol ring\" # Show set_type for each printing" 35 + echo " $0 -q \"layout:token treasure\" # Raw Scryfall query" 36 + exit 0 37 + ;; 38 + *) 39 + QUERY="$1" 40 + shift 41 + ;; 42 + esac 43 + done 44 + 45 + if [[ -z "${QUERY:-}" ]]; then 46 + echo "Error: No query provided" >&2 47 + exit 1 48 + fi 49 + 50 + # Build the search query 51 + if [[ "$RAW_QUERY" == true ]]; then 52 + SEARCH_QUERY="$QUERY" 53 + else 54 + SEARCH_QUERY="!\"$QUERY\"" 55 + fi 56 + 57 + # URL encode the query 58 + ENCODED=$(printf '%s' "$SEARCH_QUERY" | jq -sRr @uri) 59 + 60 + # Fetch from Scryfall 61 + URL="https://api.scryfall.com/cards/search?q=${ENCODED}&unique=prints&order=released" 62 + 63 + echo "Query: $SEARCH_QUERY" >&2 64 + echo "URL: $URL" >&2 65 + echo "" >&2 66 + 67 + RESPONSE=$(curl -s "$URL") 68 + 69 + # Check for errors 70 + if echo "$RESPONSE" | jq -e '.object == "error"' > /dev/null 2>&1; then 71 + echo "$RESPONSE" | jq -r '.details' 72 + exit 1 73 + fi 74 + 75 + # Extract and display results 76 + if [[ -n "$FIELD" ]]; then 77 + echo "$RESPONSE" | jq -r ".data[] | [.set, .name, .$FIELD] | @tsv" | while IFS=$'\t' read -r set name val; do 78 + printf "%-6s %-30s → %s: %s\n" "$set" "$name" "$FIELD" "$val" 79 + done 80 + else 81 + echo "$RESPONSE" | jq -r '.data[] | [.set, .set_type, .layout, .name] | @tsv' | while IFS=$'\t' read -r set set_type layout name; do 82 + printf "%-6s set_type: %-12s layout: %-15s %s\n" "$set" "$set_type" "$layout" "$name" 83 + done 84 + fi 85 + 86 + # Show count 87 + COUNT=$(echo "$RESPONSE" | jq '.data | length') 88 + TOTAL=$(echo "$RESPONSE" | jq '.total_cards') 89 + echo "" >&2 90 + echo "Showing $COUNT of $TOTAL results" >&2
+23 -14
src/workers/cards.worker.ts
··· 51 51 bonus: 5, 52 52 }; 53 53 54 + /** 55 + * Check if a card is a "non-game" card (token, art series, memorabilia). 56 + * These are excluded from search results unless explicitly queried. 57 + * For cards with both game and non-game printings (e.g., Ancestral Recall), 58 + * the canonical printing is sorted to prefer game printings in download-scryfall.ts. 59 + */ 60 + function isNonGameCard(card: Card): boolean { 61 + return ( 62 + card.layout === "art_series" || 63 + card.layout === "token" || 64 + card.layout === "double_faced_token" || 65 + card.set_type === "token" || 66 + card.set_type === "memorabilia" 67 + ); 68 + } 69 + 54 70 // WUBRG ordering 55 71 const WUBRG_ORDER = ["W", "U", "B", "R", "G"]; 56 72 ··· 361 377 const card = this.data.cards[result.id as ScryfallId]; 362 378 if (!card) continue; 363 379 364 - // Skip tokens and art series in fuzzy search 365 - if (card.layout === "art_series" || card.layout === "token") continue; 380 + // Skip non-game cards in fuzzy search 381 + if (isNonGameCard(card)) continue; 366 382 367 383 // Apply restrictions 368 384 if (restrictions) { ··· 527 543 ): Card[] { 528 544 if (!this.data) return []; 529 545 530 - // Check if query explicitly references layout/set-type (don't filter if so) 531 - const referencesLayout = someNode( 546 + // Check if query explicitly references layout/set-type (don't filter non-game cards) 547 + const includesNonGameCards = someNode( 532 548 ast, 533 549 (n) => 534 - n.type === "FIELD" && 535 - (n.field === "settype" || 536 - n.field === "layout" || 537 - (n.field === "is" && 538 - n.value.kind === "string" && 539 - (n.value.value === "token" || n.value.value === "art_series"))), 550 + n.type === "FIELD" && (n.field === "settype" || n.field === "layout"), 540 551 ); 541 552 542 553 const restrictionCheck = this.buildRestrictionCheck(restrictions); 543 554 544 - // Filter cards, skipping art_series/tokens unless explicitly queried 555 + // Filter cards, skipping non-game cards unless explicitly queried 545 556 const allMatches: Card[] = []; 546 557 for (const card of Object.values(this.data.cards)) { 547 - if (!referencesLayout) { 548 - if (card.layout === "art_series" || card.layout === "token") continue; 549 - } 558 + if (!includesNonGameCards && isNonGameCard(card)) continue; 550 559 if (!restrictionCheck(card)) continue; 551 560 if (!match(card)) continue; 552 561 allMatches.push(card);