'all','smileys'=>'Smileys & Emotion','people'=>'People & Body','animals'=>'Animals & Nature','food'=>'Food & Drink','travel'=>'Travel & Places','activities'=>'Activities','objects'=>'Objects','symbols'=>'Symbols','flags'=>'Flags' ]; $catLabel = ''; if ($catParam !== '' && strtolower($catParam) !== 'all') { $catLabel = $CATEGORY_MAP[strtolower($catParam)] ?? $catParam; } $subSlug = $subParam !== '' ? dw_slugify($subParam) : ''; $q = $qParam; $cat = $catLabel; $sub = $subSlug; // Usage (Free counted, Pro/WL unlimited) $limitDaily = $isWl ? null : ($isPro ? null : (int)cfg('free_daily_limit', 30)); $bucket = bucket_id($key); $ymd = day_key(); $usage = usage_load($bucket, $ymd); if ($limitDaily !== null) $usage['limit'] = $limitDaily; $atCap = false; if ($page > $maxPages) { $resp = ['items'=>[],'page'=>$page,'limit'=>$limit,'total'=>0,'message'=>'Page limit reached for your plan.']; if (!$isWl && !$isPro) { $resp['plan'] = $planName; } echo json_encode($resp); exit; } // Load data $DATA_PATHS = [ __DIR__.'/../data/emojis.json', __DIR__.'/../../data/emojis.json', // fallback if you keep old path ]; $json = null; foreach ($DATA_PATHS as $p) { if (is_file($p)) { $json = file_get_contents($p); break; } } if ($json === null) { json_error(500, 'data_not_found'); } $data = json_decode($json, true) ?: []; $items = $data['emojis'] ?? []; // Filter $items = array_values(array_filter($items, function($e) use ($q,$cat,$sub) { if ($cat !== '') { if (dw_slugify($e['category'] ?? '') !== dw_slugify($cat)) return false; } if ($sub !== '') { if (dw_slugify($e['subcategory'] ?? '') !== $sub) return false; } if ($q !== '') { $hay = strtolower(implode(' ', [ $e['emoji'] ?? '', $e['name'] ?? '', $e['slug'] ?? '', $e['category'] ?? '', $e['subcategory'] ?? '', implode(' ', $e['keywords_en'] ?? []), implode(' ', $e['keywords_id'] ?? []), implode(' ', $e['aliases'] ?? []), implode(' ', $e['shortcodes'] ?? []), ])); foreach (preg_split('/\s+/', strtolower($q)) as $tok) { if ($tok !== '' && strpos($hay, $tok) === false) return false; } } return true; })); $total = count($items); $offset = ($page - 1) * $limit; $items = array_slice($items, $offset, $limit); // Shape + tone $items = array_map(function($raw) use ($planName, $isPro) { $e = filterEmoji($raw, $planName, true); $e['supports_skin_tone'] = dw_supports_skin_tone($raw) || dw_supports_skin_tone($e); $e['skin_tone_modifiers'] = dw_skin_tone_modifiers(); if ($e['supports_skin_tone'] && !empty($e['emoji'])) { $base = dw_strip_tone($e['emoji']); if ($base !== '') { $e['emoji_base'] = $base; if ($isPro) { $e['variants'] = dw_build_tone_variants($base); } } } return $e; }, $items); // ETag (exclude dynamic usage) $etag = '"'.sha1(json_encode([$page,$limit,$q,$cat,$sub,$planName,$total,$items])).'"'; header('ETag: '.$etag); header('Cache-Control: public, max-age=120'); if (($_SERVER['HTTP_IF_NONE_MATCH'] ?? '') === $etag) { http_response_code(304); exit; } // Only count usage now if Free, page 1, and not cached $signature = sig_query($q, $cat, $sub); $sigKey = $signature . '|p1'; if ($limitDaily !== null && $page === 1 && ($_SERVER['HTTP_IF_NONE_MATCH'] ?? '') !== $etag) { if (!isset($usage['seen'][$sigKey])) { if ($usage['used'] >= $limitDaily) { $atCap = true; } else { $usage['used'] = (int)$usage['used'] + 1; $usage['seen'][$sigKey] = 1; usage_save($bucket, $ymd, $usage); } } } // If we just detected cap on this request, return a capped response now if ($atCap) { // Only Free responses expose plan/usage headers if (!$isWl && !$isPro) { header('X-Dewemoji-Plan: '.$planName); header('X-RateLimit-Limit: '.$usage['limit']); header('X-RateLimit-Remaining: 0'); header('X-RateLimit-Reset: '.strtotime('tomorrow 00:00:00 UTC')); } $extra = []; if (!$isWl && !$isPro) { $extra['plan'] = $planName; $extra['usage'] = [ 'used'=>(int)$usage['used'], 'limit'=>$limitDaily, 'remaining'=> $limitDaily===null ? null : max(0, $limitDaily - (int)$usage['used']), 'window'=>'daily', 'window_ends_at'=>gmdate('c', strtotime('tomorrow 00:00:00 UTC')), 'count_basis'=>'distinct_query' ]; } json_error(429, 'Daily free limit reached. Upgrade to Pro for unlimited usage.', $extra); } // Rate-limit headers (only for Free; omit for Whitelisted and Pro) if (!$isWl && !$isPro) { header('X-Dewemoji-Plan: '.$planName); header('X-RateLimit-Limit: '.$usage['limit']); header('X-RateLimit-Remaining: '.max(0, $usage['limit'] - (int)$usage['used'])); header('X-RateLimit-Reset: '.strtotime('tomorrow 00:00:00 UTC')); } // Response $resp = [ 'items'=>$items, 'page'=>$page, 'limit'=>$limit, 'total'=>$total, ]; if (!$isWl && !$isPro) { $resp['plan'] = $planName; $resp['usage'] = [ 'used'=>(int)$usage['used'], 'limit'=>$limitDaily, 'remaining'=> $limitDaily===null ? null : max(0, $limitDaily - (int)$usage['used']), 'window'=>'daily', 'window_ends_at'=>gmdate('c', strtotime('tomorrow 00:00:00 UTC')), 'count_basis'=>'distinct_query' ]; } echo json_encode($resp, JSON_UNESCAPED_UNICODE);