← back to Animate Museum Posts

src/fetchFromMet.js

141 lines

import axios from 'axios';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const MET_API_BASE = 'https://collectionapi.metmuseum.org/public/collection/v1';

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Cache of highlight object IDs (public domain with images)
let cachedObjectIds = null;

async function getHighlightObjectIds() {
  if (cachedObjectIds && cachedObjectIds.length > 0) {
    return cachedObjectIds;
  }

  try {
    // Search for highlighted public domain artworks with images
    const response = await axios.get(`${MET_API_BASE}/search`, {
      params: {
        isHighlight: true,
        hasImages: true,
        q: '*'  // All artworks
      },
      timeout: 30000
    });

    if (response.data && response.data.objectIDs) {
      cachedObjectIds = response.data.objectIDs;
      console.log(`Met Museum: Found ${cachedObjectIds.length} highlighted artworks`);
      return cachedObjectIds;
    }
  } catch (error) {
    console.error('Met Museum search failed:', error.message);
  }

  return [];
}

async function getObjectDetails(objectId) {
  try {
    const response = await axios.get(`${MET_API_BASE}/objects/${objectId}`, {
      timeout: 15000
    });
    return response.data;
  } catch (error) {
    console.error(`Failed to fetch object ${objectId}:`, error.message);
    return null;
  }
}

export async function fetchFromMet() {
  try {
    console.log('Fetching from Metropolitan Museum of Art...');

    const objectIds = await getHighlightObjectIds();
    if (objectIds.length === 0) {
      throw new Error('No artworks found from Met Museum');
    }

    // Try up to 10 random artworks to find one with valid public domain image
    for (let attempt = 0; attempt < 10; attempt++) {
      const randomId = objectIds[Math.floor(Math.random() * objectIds.length)];
      const artwork = await getObjectDetails(randomId);

      // Accept public domain OR CC0 licensed images
      const hasValidLicense = artwork?.isPublicDomain ||
        artwork?.rightsAndReproduction?.toLowerCase().includes('cc0') ||
        artwork?.rightsAndReproduction?.toLowerCase().includes('public domain');

      if (!artwork || !hasValidLicense || !artwork.primaryImage) {
        if (attempt < 9) await sleep(500); // Small delay between attempts
        continue;
      }

      // Download the image
      console.log(`Downloading: ${artwork.title}`);
      const imageResponse = await axios.get(artwork.primaryImage, {
        responseType: 'arraybuffer',
        timeout: 30000,
        headers: {
          'User-Agent': 'Mozilla/5.0 (compatible; MuseumBot/1.0)'
        }
      });

      if (!imageResponse.data || imageResponse.data.length < 1000) {
        continue;
      }

      // Save to output directory
      const outputDir = path.join(__dirname, '..', 'output');
      if (!fs.existsSync(outputDir)) {
        fs.mkdirSync(outputDir, { recursive: true });
      }

      const timestamp = Date.now();
      const fileName = `met_${timestamp}.jpg`;
      const filePath = path.join(outputDir, fileName);
      fs.writeFileSync(filePath, imageResponse.data);

      // Return normalized metadata
      const metadata = {
        title: artwork.title || 'Untitled',
        artist: artwork.artistDisplayName || 'Unknown Artist',
        year: artwork.objectDate || 'Date Unknown',
        description: artwork.medium || '',
        objectNumber: `MET-${artwork.objectID}`,
        imageUrl: artwork.primaryImage,
        localPath: filePath,
        fileName: fileName,
        museum: 'The Metropolitan Museum of Art',
        creditLine: artwork.creditLine || 'The Metropolitan Museum of Art (Public Domain)',
        dimensions: artwork.dimensions ? [artwork.dimensions] : [],
        department: artwork.department,
        culture: artwork.culture,
        period: artwork.period
      };

      console.log(`Met Museum: Fetched "${metadata.title}" by ${metadata.artist}`);
      return metadata;
    }

    throw new Error('Could not find valid public domain artwork after 5 attempts');

  } catch (error) {
    console.error('Met Museum fetch failed:', error.message);
    throw error;
  }
}

// Test if run directly
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  fetchFromMet()
    .then(m => console.log('Success:', JSON.stringify(m, null, 2)))
    .catch(e => console.error('Failed:', e.message));
}