Initial commit: Animexe Laravel platform
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Frontend;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Comment;
|
||||
use App\Models\CommentLike;
|
||||
use App\Models\Setting;
|
||||
use App\Services\DeepSeekService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CommentController extends Controller
|
||||
{
|
||||
/**
|
||||
* POST /comments — yorum gönder
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$maxLength = ($user && $user->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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user