Files
mue/src/features/background/api/photoPackAPI.js

214 lines
6.2 KiB
JavaScript

import variables from 'config/variables';
/**
* Fetch photos from MUE API based on pack settings
* @param {string} packId - The ID of the photo pack
* @param {object} settings - Pack-specific settings
* @returns {Promise<object|null>} Photo object or null on error
*/
export async function fetchFromMUE(packId, settings) {
const { quality, categories } = settings;
const params = new URLSearchParams({
quality: quality || 'high',
categories: (categories || []).join(','),
});
try {
const response = await fetch(`${variables.constants.API_URL}/images/random?${params}`);
if (!response.ok) throw new Error(`API error: ${response.status}`);
const data = await response.json();
return {
photographer: data.photographer,
location: data.location?.name || 'Unknown',
url: { default: data.file },
blur_hash: data.blur_hash,
colour: data.colour,
category: data.category,
camera: data.camera,
};
} catch (error) {
console.error('MUE API fetch failed:', error);
return null;
}
}
/**
* Fetch photos from Unsplash API
* @param {string} packId - The ID of the photo pack
* @param {object} settings - Pack-specific settings (must include api_key)
* @returns {Promise<object|null>} Photo object or null on error
*/
export async function fetchFromUnsplash(packId, settings) {
const { api_key, collections } = settings;
if (!api_key) {
console.warn('Unsplash API key not configured');
return null;
}
// Deobfuscate API key
const decodedKey = atob(api_key);
const params = new URLSearchParams({
orientation: 'landscape',
});
if (collections) {
params.append('collections', collections);
}
try {
const response = await fetch(`https://api.unsplash.com/photos/random?${params}`, {
headers: {
Authorization: `Client-ID ${decodedKey}`,
},
});
if (!response.ok) {
if (response.status === 429) {
console.warn('Unsplash rate limit hit');
}
throw new Error(`Unsplash API error: ${response.status}`);
}
const data = await response.json();
return {
photographer: data.user.name,
location: data.location?.title || 'Unknown',
url: { default: data.urls.regular },
blur_hash: data.blur_hash,
colour: data.color,
unsplash_url: data.links.html,
photographer_url: data.user.links.html,
};
} catch (error) {
console.error('Unsplash API fetch failed:', error);
return null;
}
}
/**
* Fetch multiple photos for a pack and update cache
* @param {string} packId - The ID of the photo pack to refresh
* @returns {Promise<boolean>} Success status
*/
export async function refreshAPIPackCache(packId) {
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
const pack = installed.find((p) => p.id === packId);
if (!pack || !pack.api_enabled) return false;
const settings = JSON.parse(localStorage.getItem(`photopack_settings_${packId}`) || '{}');
const fetchFunction = pack.api_provider === 'mue' ? fetchFromMUE : fetchFromUnsplash;
// Fetch 8 photos
const promises = Array.from({ length: 8 }, () => fetchFunction(packId, settings));
const results = await Promise.all(promises);
const validPhotos = results.filter(Boolean);
if (validPhotos.length === 0) {
console.warn(`No photos fetched for pack ${packId}`);
return false;
}
// Update cache
const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}');
apiPackCache[packId] = {
photos: validPhotos,
last_fetched: Date.now(),
last_refresh_attempt: Date.now(),
};
try {
localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache));
return true;
} catch (error) {
if (error.name === 'QuotaExceededError') {
// Keep only 5 most recent photos
apiPackCache[packId].photos = validPhotos.slice(0, 5);
localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache));
}
return false;
}
}
/**
* Check all API packs and refresh if needed
* Hardcoded 1 hour refresh interval (separate from global background change frequency)
*/
export async function checkAndRefreshAPIPacks() {
const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]');
const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}');
// Hardcoded 1 hour refresh interval (separate from global background change frequency)
const CACHE_REFRESH_INTERVAL = 3600 * 1000; // 1 hour in milliseconds
for (const packId of apiPacksReady) {
const cached = apiPackCache[packId];
const needsRefresh =
!cached ||
Date.now() - cached.last_fetched > CACHE_REFRESH_INTERVAL ||
cached.photos.length < 3;
if (needsRefresh) {
// Don't block - refresh in background
refreshAPIPackCache(packId).catch((error) => {
console.error(`Failed to refresh ${packId}:`, error);
});
}
}
}
/**
* Build combined photo pool from static and API packs
* @returns {Array} Combined array of photos from all sources
*/
export function buildPhotoPool() {
const pool = [];
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]');
const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}');
installed.forEach((pack) => {
if (pack.type !== 'photos') return;
if (pack.api_enabled) {
// API pack - check if configured and ready
if (apiPacksReady.includes(pack.id)) {
const cached = apiPackCache[pack.id];
if (cached && cached.photos.length > 0) {
// Add cached photos with source metadata
cached.photos.forEach((photo) => {
pool.push({
...photo,
source: `api:${pack.api_provider}`,
pack_id: pack.id,
});
});
}
}
} else {
// Static pack - add all photos
pack.photos.forEach((photo) => {
pool.push({
photographer: photo.photographer,
location: photo.location,
url: photo.url,
blur_hash: photo.blur_hash,
source: 'static',
pack_id: pack.id,
});
});
}
});
return pool;
}