diff --git a/app/controllers/Emojis.php b/app/controllers/Emojis.php index e69de29..1091469 100644 --- a/app/controllers/Emojis.php +++ b/app/controllers/Emojis.php @@ -0,0 +1,178 @@ +'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', 50)); + +$bucket = bucket_id($key); +$ymd = day_key(); +$usage = usage_load($bucket, $ymd); +if ($limitDaily !== null) $usage['limit'] = $limitDaily; + +$signature = sig_query($q, $cat, $sub); +$sigKey = $signature.'|p1'; // only page 1 increments +$atCap = false; + +if ($limitDaily !== null && $page === 1 && !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 ($page > $maxPages) { + echo json_encode(['items'=>[],'page'=>$page,'limit'=>$limit,'total'=>0,'plan'=>$planName,'message'=>'Page limit reached for your plan.']); + exit; +} + +// Soft cap +if ($atCap) { + header('X-Dewemoji-Plan: '.$planName); + if ($limitDaily === null) { + header('X-RateLimit-Limit: unlimited'); header('X-RateLimit-Remaining: unlimited'); + } else { + header('X-RateLimit-Limit: '.$usage['limit']); header('X-RateLimit-Remaining: 0'); + } + header('X-RateLimit-Reset: '.strtotime('tomorrow 00:00:00 UTC')); + echo json_encode([ + 'items'=>[],'page'=>$page,'limit'=>$limit,'total'=>0,'plan'=>$planName, + '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' + ], + 'message'=>'Daily free limit reached. Upgrade to Pro for unlimited usage.' + ]); + 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) { http_response_code(500); echo json_encode(['error'=>'data_not_found']); exit; } +$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; } + +// Rate-limit headers +header('X-Dewemoji-Plan: '.$planName); +if ($limitDaily === null) { + header('X-RateLimit-Limit: unlimited'); header('X-RateLimit-Remaining: unlimited'); +} else { + 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 +echo json_encode([ + 'items'=>$items, + 'page'=>$page, + 'limit'=>$limit, + 'total'=>$total, + 'plan'=>$isPro ? 'pro' : 'free', + '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_UNESCAPED_UNICODE); \ No newline at end of file diff --git a/config/env.php b/config/env.php index e69de29..21ac796 100644 --- a/config/env.php +++ b/config/env.php @@ -0,0 +1,25 @@ + true, + + 'db' => [ + 'dsn' => 'mysql:host=127.0.0.1;dbname=dewepw_emojiapi;charset=utf8mb4', + 'user' => 'dewepw_emojiapi_admin', + 'pass' => 'o$NUF(.4AHuV{RzT', + ], + + // SPA + extension origins + 'allowed_origins' => [ + 'https://emoji.dewe.pw', + 'chrome-extension://YOUR_EXTENSION_ID', + 'http://localhost:5173', // dev + ], + + // First-party whitelist logic + 'site_host' => 'emoji.dewe.pw', + 'frontend_header' => 'web-v1', // your SPA sets: X-Dewemoji-Frontend: web-v1 + + // Free daily limit for API (Pro/Whitelist are unlimited) + 'free_daily_limit' => 50, +]; \ No newline at end of file diff --git a/public/index.php b/public/index.php index a3e73e1..e69de29 100644 --- a/public/index.php +++ b/public/index.php @@ -1,27 +0,0 @@ -true,'time'=>gmdate('c')]); - exit; -} - -http_response_code(404); -header('Content-Type: application/json; charset=utf-8'); -echo json_encode(['error'=>'not_found','path'=>$path]); \ No newline at end of file