hasPerk('extended_comments')) ? 1000 : 500; $request->validate([ 'commentable_type' => 'required|in:episode,anime', 'commentable_id' => 'required|integer', 'content' => 'nullable|string|max:' . $maxLength, 'gif_url' => 'nullable|url|max:500', 'parent_id' => 'nullable|integer|exists:comments,id', ]); if (empty(trim($request->content ?? '')) && empty($request->gif_url)) { return response()->json(['error' => 'Yorum boş olamaz.'], 422); } if (!empty($request->gif_url) && (!$user || !$user->hasPerk('comment_gif'))) { return response()->json(['error' => 'GIF eklemek için premium üyelik gerekiyor.'], 403); } $commentsEnabled = Setting::get('comments_enabled', '1') === '1'; if (!$commentsEnabled) { return response()->json(['error' => 'Yorumlar şu an kapalı.'], 403); } $content = trim($request->content ?? ''); // AI moderasyon (sadece metin içeren yorumlar için, GIF yorumları direkt onaylanır) $aiService = new DeepSeekService(); $pendingReason = null; $status = 'approved'; if (!empty($content) && $aiService->isConfigured()) { $mod = $aiService->moderateComment($content); if ($mod['is_rude']) { $status = 'pending'; $pendingReason = 'rude'; } elseif ($mod['is_spoiler']) { $status = 'pending'; $pendingReason = 'spoiler'; } } elseif (Setting::get('comments_require_approval', '0') === '1') { $status = 'pending'; $pendingReason = 'manual'; } $comment = Comment::create([ 'user_id' => Auth::id(), 'commentable_type' => $request->commentable_type, 'commentable_id' => $request->commentable_id, 'parent_id' => $request->parent_id ?: null, 'content' => $content, 'gif_url' => $request->gif_url ?: null, 'status' => $status, 'like_count' => 0, ]); $comment->load('user'); if ($status !== 'approved') { return response()->json([ 'ok' => true, 'pending' => true, 'pending_reason' => $pendingReason, ]); } return response()->json([ 'ok' => true, 'pending' => false, 'comment' => $this->formatComment($comment, Auth::id()), ]); } /** * POST /comments/{comment}/like — beğen/beğenmekten vazgeç (toggle) */ public function like(Comment $comment) { $userId = Auth::id(); $existing = CommentLike::where('user_id', $userId) ->where('comment_id', $comment->id) ->first(); if ($existing) { $existing->delete(); $comment->decrement('like_count'); $liked = false; } else { CommentLike::create(['user_id' => $userId, 'comment_id' => $comment->id]); $comment->increment('like_count'); $liked = true; } return response()->json([ 'ok' => true, 'liked' => $liked, 'like_count' => $comment->fresh()->like_count, ]); } /** * GET /comments/gif-search — GIF arama (Giphy öncelikli, Tenor fallback) */ public function gifSearch(Request $request) { $query = $request->query('q', 'anime reaction'); $giphyKey = Setting::get('giphy_api_key', ''); $tenorKey = Setting::get('tenor_api_key', ''); // Giphy if (!empty($giphyKey)) { return $this->searchGiphy($query, $giphyKey); } // Tenor if (!empty($tenorKey)) { return $this->searchTenor($query, $tenorKey); } return response()->json(['results' => [], 'error' => 'no_key']); } private function searchGiphy(string $query, string $apiKey) { try { $res = Http::timeout(8)->get('https://api.giphy.com/v1/gifs/search', [ 'api_key' => $apiKey, 'q' => $query, 'limit' => 24, 'rating' => 'pg-13', 'lang' => 'en', ]); if (!$res->successful()) { \Log::warning('Giphy API failed', ['status' => $res->status()]); return response()->json(['results' => [], 'error' => 'giphy_fail']); } $gifs = collect($res->json('data', []))->map(function ($r) { $images = $r['images'] ?? []; $preview = $images['fixed_height_small']['url'] ?? $images['fixed_height']['url'] ?? $images['downsized']['url'] ?? null; $full = $images['downsized_medium']['url'] ?? $images['fixed_height']['url'] ?? $images['original']['url'] ?? $preview; if (!$preview || !$full) return null; return [ 'id' => $r['id'], 'preview' => $preview, 'url' => $full, 'title' => $r['title'] ?? '', ]; })->filter()->values(); return response()->json(['results' => $gifs]); } catch (\Exception $e) { \Log::error('Giphy error: ' . $e->getMessage()); return response()->json(['results' => [], 'error' => $e->getMessage()]); } } private function searchTenor(string $query, string $apiKey) { try { $res = Http::timeout(8)->get('https://tenor.googleapis.com/v2/search', [ 'q' => $query, 'key' => $apiKey, 'limit' => 24, 'media_filter' => 'tinygif,gif', 'contentfilter' => 'medium', ]); if (!$res->successful()) { return response()->json(['results' => [], 'error' => 'tenor_fail']); } $gifs = collect($res->json('results', []))->map(function ($r) { $formats = $r['media_formats'] ?? []; $preview = $formats['tinygif']['url'] ?? $formats['mediumgif']['url'] ?? $formats['gif']['url'] ?? null; $full = $formats['gif']['url'] ?? $formats['mediumgif']['url'] ?? $preview ?? null; if (!$preview || !$full) return null; return [ 'id' => $r['id'], 'preview' => $preview, 'url' => $full, 'title' => $r['content_description'] ?? '', ]; })->filter()->values(); return response()->json(['results' => $gifs]); } catch (\Exception $e) { return response()->json(['results' => [], 'error' => $e->getMessage()]); } } /** * GET /comments — bölüm yorumlarını getir (AJAX sayfalama) */ public function index(Request $request) { $userId = Auth::id(); $query = Comment::where('commentable_type', $request->type) ->where('commentable_id', $request->id) ->whereNull('parent_id') ->where('status', 'approved') ->with(['user', 'replies' => fn($q) => $q->where('status', 'approved')->with('user')->orderBy('created_at')]) ->orderByDesc('is_pinned') ->orderByDesc('like_count') ->orderByDesc('created_at'); $comments = $query->paginate(20); return response()->json([ 'data' => $comments->map(fn($c) => $this->formatComment($c, $userId, true)), 'has_more' => $comments->hasMorePages(), 'next_page'=> $comments->currentPage() + 1, ]); } private function formatComment(Comment $c, ?int $userId, bool $withReplies = false): array { $data = [ 'id' => $c->id, 'content' => $c->content, 'gif_url' => $c->gif_url, 'like_count' => $c->like_count, 'is_liked' => $userId ? $c->likes()->where('user_id', $userId)->exists() : false, 'is_pinned' => $c->is_pinned, 'parent_id' => $c->parent_id, 'created_at' => $c->created_at?->diffForHumans(), 'user' => $c->user ? [ 'id' => $c->user->id, 'name' => $c->user->name, 'username' => $c->user->username, 'avatar' => $c->user->gif_avatar && $c->user->hasPerk('gif_avatar') ? $c->user->gif_avatar : ($c->user->avatar ? \App\Support\MediaUrl::fromStoragePath($c->user->avatar) : null), 'role' => $c->user->role, 'is_following' => $userId && $userId !== $c->user->id ? \App\Models\UserFollow::where('follower_id', $userId)->where('following_id', $c->user->id)->exists() : false, 'comment_bg' => $c->user->comment_bg, 'comment_glow' => $c->user->comment_glow, 'comment_signature' => $c->user->comment_signature, 'username_color' => $c->user->username_color, 'username_effect' => $c->user->username_effect, 'profile_frame' => $c->user->profile_frame, 'profile_badge' => $c->user->profile_badge, 'admin_badge' => $c->user->admin_badge, 'watch_rank' => $c->user->watchRank(), ] : null, ]; if ($withReplies && $c->relationLoaded('replies')) { $data['replies'] = $c->replies->map(fn($r) => $this->formatComment($r, $userId))->toArray(); } return $data; } }