initial import: random background api

This commit is contained in:
shumengya
2026-03-29 10:58:33 +08:00
commit f68e19a3bd
62 changed files with 985 additions and 0 deletions

22
functions/api/list.js Normal file
View File

@@ -0,0 +1,22 @@
import { getVariantPayload, loadManifest } from '../lib/background.js';
export async function onRequestGet(context) {
const { request } = context;
const manifest = await loadManifest(request);
return Response.json(
{
generated_at: manifest?.generated_at || null,
variants: {
mobile: summarizeVariant(manifest, 'mobile'),
desktop: summarizeVariant(manifest, 'desktop'),
},
},
{ headers: { 'Cache-Control': 'no-store, max-age=0', 'Access-Control-Allow-Origin': '*' } },
);
}
function summarizeVariant(manifest, variant) {
const { folder, images } = getVariantPayload(manifest, variant);
return { folder, count: images.length, images };
}

36
functions/api/random.js Normal file
View File

@@ -0,0 +1,36 @@
import { buildAssetUrl, detectVariant, getVariantPayload, loadManifest, pickRandom } from '../lib/background.js';
export async function onRequestGet(context) {
const { request } = context;
const url = new URL(request.url);
const format = (url.searchParams.get('format') || 'redirect').toLowerCase();
const manifest = await loadManifest(request);
const variant = detectVariant(request);
const { folder, images } = getVariantPayload(manifest, variant);
if (!images.length) {
return Response.json(
{ error: 'no images available', variant, folder },
{ status: 404, headers: { 'Cache-Control': 'no-store, max-age=0', 'Access-Control-Allow-Origin': '*' } },
);
}
const filename = pickRandom(images);
const assetUrl = buildAssetUrl(request, folder, filename);
if (format === 'json') {
return Response.json(
{ variant, folder, filename, url: assetUrl, count: images.length },
{ headers: { 'Cache-Control': 'no-store, max-age=0', 'Access-Control-Allow-Origin': '*' } },
);
}
return new Response(null, {
status: 302,
headers: {
Location: assetUrl,
'Cache-Control': 'no-store, max-age=0',
'Access-Control-Allow-Origin': '*',
},
});
}

View File

@@ -0,0 +1,52 @@
const FALLBACK_MANIFEST = {
variants: {
mobile: { folder: 'mobile-image', images: [] },
desktop: { folder: 'desketop-image', images: [] },
},
};
export async function loadManifest(request) {
try {
const response = await fetch(new URL('/manifest.json', request.url));
if (!response.ok) return FALLBACK_MANIFEST;
return await response.json();
} catch {
return FALLBACK_MANIFEST;
}
}
export function detectVariant(request) {
const url = new URL(request.url);
const params = url.searchParams;
const mode = (params.get('mode') || 'auto').toLowerCase();
const orientation = (params.get('orientation') || '').toLowerCase();
const device = (params.get('device') || '').toLowerCase();
const ua = (request.headers.get('user-agent') || '').toLowerCase();
const chMobile = request.headers.get('sec-ch-ua-mobile');
if (mode === 'mobile' || mode === 'desktop') return mode;
if (orientation === 'portrait' || orientation === 'vertical') return 'mobile';
if (orientation === 'landscape' || orientation === 'horizontal') return 'desktop';
if (device === 'mobile' || device === 'desktop') return device;
if (chMobile === '?1') return 'mobile';
if (chMobile === '?0') return 'desktop';
if (['mobi', 'android', 'iphone', 'ipad', 'ipod', 'phone'].some((token) => ua.includes(token))) return 'mobile';
return 'desktop';
}
export function getVariantPayload(manifest, variant) {
const data = manifest?.variants?.[variant] || {};
const folder = data.folder || (variant === 'mobile' ? 'mobile-image' : 'desketop-image');
const images = Array.isArray(data.images)
? data.images.filter((name) => typeof name === 'string' && name.toLowerCase().endsWith('.webp'))
: [];
return { folder, images };
}
export function buildAssetUrl(request, folder, filename) {
return new URL(`/${folder}/${filename}`, request.url).toString();
}
export function pickRandom(items) {
return items[Math.floor(Math.random() * items.length)];
}