Files
multipleof4andClaude Opus 5.5 029b259f05 Feat: Add free.direct-img.link and ?i= result picker (#1)
- free.direct-img.link serves only unrestricted (public domain/CC0) images
  from Openverse (license=cc0,pdm) and Wikimedia Commons
  (haslicense:unrestricted), with optional ?src=openverse|wikimedia
- ?i=1..20 picks the i-th working image on both sites; broken links are
  skipped so every index is a different image
- Openverse OAuth token is cached in KV and refreshed automatically
- Skip Flickr "photo unavailable" placeholders (served with HTTP 200)
- Free/indexed cache keys use uppercase prefixes; plain main-site keys
  are unchanged so existing cache entries stay valid
- Update README, homepage, privacy policy and terms

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 12:00:54 -07:00

38 lines
1.7 KiB
JavaScript

const API = "https://api.openverse.org/v1";
// Uppercase so it can't collide with query cache keys, which are always lowercased
const TOKEN_KEY = "OPENVERSE_TOKEN";
// Tokens expire (~12h); client ID/secret are permanent, so fetch a new token whenever the cached one is gone
async function getToken(env) {
if (!env.OPENVERSE_CLIENT_ID || !env.OPENVERSE_CLIENT_SECRET) return null;
const cached = await env.DIRECT_IMG_CACHE.get(TOKEN_KEY);
if (cached) return cached;
const res = await fetch(`${API}/auth_tokens/token/`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ grant_type: "client_credentials", client_id: env.OPENVERSE_CLIENT_ID, client_secret: env.OPENVERSE_CLIENT_SECRET }),
});
if (!res.ok) return null;
const { access_token, expires_in } = await res.json();
await env.DIRECT_IMG_CACHE.put(TOKEN_KEY, access_token, { expirationTtl: Math.max(60, expires_in - 600) });
return access_token;
}
export async function openverseImageSearch(query, env, ua) {
const url = `${API}/images/?${new URLSearchParams({ q: query, license: "cc0,pdm", extension: "jpg,png,gif,webp", page_size: 20 })}`;
const search = token => fetch(url, { headers: { "User-Agent": ua, ...(token && { Authorization: `Bearer ${token}` }) } });
try {
const token = await getToken(env).catch(() => null);
let res = await search(token);
if (res.status === 401 && token) {
await env.DIRECT_IMG_CACHE.delete(TOKEN_KEY);
res = await search();
}
if (!res.ok) return null;
const data = await res.json();
return data.results?.map(r => r.url).filter(Boolean) || null;
} catch {
return null;
}
}