743 lines
54 KiB
PHP
743 lines
54 KiB
PHP
<?php
|
||
|
||
use Illuminate\Support\Facades\Route;
|
||
use App\Http\Controllers\Admin;
|
||
use App\Http\Controllers\Admin\AnalyticsController as AdminAnalyticsController;
|
||
use App\Http\Controllers\MediaController;
|
||
use App\Http\Controllers\Frontend;
|
||
use App\Http\Controllers\Frontend\EmailVerificationController;
|
||
use App\Http\Controllers\Frontend\PasswordResetController;
|
||
use App\Http\Controllers\Frontend\CommentController;
|
||
use App\Http\Controllers\Frontend\ProfileController;
|
||
use App\Http\Controllers\Frontend\AiController;
|
||
use App\Http\Controllers\Frontend\TrackingController;
|
||
use App\Http\Controllers\Frontend\UserFeatureController;
|
||
use App\Http\Controllers\Frontend\SocialController;
|
||
use App\Http\Controllers\Frontend\TribunalController;
|
||
use App\Http\Controllers\Frontend\MessageController;
|
||
use App\Http\Controllers\Frontend\VoiceCallController;
|
||
use App\Http\Controllers\Frontend\DiscoverController;
|
||
|
||
Route::get('/media/{path}', [MediaController::class, 'show'])
|
||
->where('path', '.*')
|
||
->name('media.show');
|
||
|
||
// ── Admin Login ──────────────────────────────────────────────────────────────
|
||
Route::prefix('admin')->name('admin.')->group(function () {
|
||
Route::get('login', [Admin\AuthController::class, 'showLogin'])->name('login');
|
||
Route::post('login', [Admin\AuthController::class, 'login'])->name('login.post');
|
||
Route::post('logout', [Admin\AuthController::class, 'logout'])->name('logout');
|
||
});
|
||
|
||
// ── Admin Panel ──────────────────────────────────────────────────────────────
|
||
// admin.access = auth kontrolü + admin veya moderatör rolü (permission yok)
|
||
// admin = sadece admin (veya moderatör + belirtilen permission)
|
||
Route::prefix('admin')->name('admin.')->middleware(['admin.access'])->group(function () {
|
||
|
||
// Dashboard — tüm moderatörlere açık
|
||
Route::get('/', [Admin\DashboardController::class, 'index'])->name('dashboard');
|
||
|
||
// Anime
|
||
Route::resource('animes', Admin\AnimeController::class)->middleware(['admin:animes.view']);
|
||
Route::post('animes/{anime}/permissions', [Admin\AnimeController::class, 'updatePermissions'])->name('animes.permissions')->middleware(['admin:animes.edit']);
|
||
Route::post('animes/{anime}/fetch-mal-seasons', [Admin\AnimeController::class, 'fetchMalSeasons'])->name('animes.fetch-mal-seasons')->middleware(['admin:animes.edit']);
|
||
Route::post('animes/{anime}/fetch-mal', [Admin\AnimeController::class, 'fetchMalSingle'])->name('animes.fetch-mal')->middleware(['admin:animes.edit']);
|
||
Route::post('animes/bulk-find-mal', [Admin\AnimeController::class, 'bulkFindMal'])->name('animes.bulk-find-mal')->middleware(['admin:animes.edit']);
|
||
Route::post('animes/bulk-destroy', [Admin\AnimeController::class, 'bulkDestroy'])->name('animes.bulk-destroy')->middleware(['admin:animes.edit']);
|
||
Route::post('animes/destroy-zero-episodes', [Admin\AnimeController::class, 'destroyZeroEpisodes'])->name('animes.destroy-zero-episodes')->middleware(['admin']);
|
||
|
||
// Sezon
|
||
Route::resource('animes.seasons', Admin\SeasonController::class)->shallow()->middleware(['admin:episodes.view']);
|
||
|
||
// Bölüm
|
||
Route::post('episodes/bulk-intro', [Admin\EpisodeController::class, 'bulkIntro'])->name('episodes.bulk-intro')->middleware(['admin:episodes.edit']);
|
||
Route::post('episodes/bulk-destroy', [Admin\EpisodeController::class, 'bulkDestroy'])->name('episodes.bulk-destroy')->middleware(['admin:episodes.edit']);
|
||
Route::post('episodes/{episode}/scan-hevc', [Admin\EpisodeController::class, 'scanHevc'])->name('episodes.scan-hevc')->middleware(['admin:episodes.edit']);
|
||
Route::resource('episodes', Admin\EpisodeController::class)->middleware(['admin:episodes.view']);
|
||
|
||
// Reklamlar
|
||
Route::post('ads-settings', [Admin\AdController::class, 'saveSettings'])->name('ads.settings')->middleware(['admin']);
|
||
Route::post('ads/{ad}/toggle', [Admin\AdController::class, 'toggle'])->name('ads.toggle')->middleware(['admin']);
|
||
Route::resource('ads', Admin\AdController::class)->except(['create', 'show'])->middleware(['admin']);
|
||
|
||
// Kullanıcı
|
||
Route::resource('users', Admin\UserController::class)->middleware(['admin:users.view']);
|
||
Route::post('users/{user}/ban', [Admin\UserController::class, 'ban'])->name('users.ban')->middleware(['admin:users.ban']);
|
||
Route::post('users/{user}/unban', [Admin\UserController::class, 'unban'])->name('users.unban')->middleware(['admin:users.ban']);
|
||
Route::post('users/{user}/give-premium', [Admin\UserController::class, 'givePremium'])->name('users.give-premium')->middleware(['admin:users.premium']);
|
||
Route::post('users/{user}/remove-premium', [Admin\UserController::class, 'removePremium'])->name('users.remove-premium')->middleware(['admin:users.premium']);
|
||
|
||
// Yorum
|
||
Route::resource('comments', Admin\CommentController::class)->only(['index', 'show', 'destroy'])->middleware(['admin:comments.view']);
|
||
Route::post('comments/{comment}/approve', [Admin\CommentController::class, 'approve'])->name('comments.approve')->middleware(['admin:comments.approve']);
|
||
Route::post('comments/{comment}/reject', [Admin\CommentController::class, 'reject'])->name('comments.reject')->middleware(['admin:comments.approve']);
|
||
Route::post('comments/{comment}/pin', [Admin\CommentController::class, 'pin'])->name('comments.pin')->middleware(['admin:comments.pin']);
|
||
Route::post('comments/{comment}/reply', [Admin\CommentController::class, 'reply'])->name('comments.reply')->middleware(['admin:comments.approve']);
|
||
|
||
// Üyelik planları — sadece admin
|
||
Route::resource('plans', Admin\PlanController::class)->middleware(['admin']);
|
||
|
||
// Abonelikler — sadece admin
|
||
Route::resource('subscriptions', Admin\SubscriptionController::class)->only(['index', 'show', 'store', 'destroy'])->middleware(['admin']);
|
||
|
||
// Aktivasyon kodları — sadece admin
|
||
Route::prefix('activation-codes')->name('activation-codes.')->middleware(['admin'])->group(function () {
|
||
Route::get('/', [Admin\ActivationCodeController::class, 'index'])->name('index');
|
||
Route::post('/generate', [Admin\ActivationCodeController::class, 'generate'])->name('generate');
|
||
Route::get('/export', [Admin\ActivationCodeController::class, 'export'])->name('export');
|
||
Route::delete('/{activationCode}', [Admin\ActivationCodeController::class, 'destroy'])->name('destroy');
|
||
Route::post('/destroy-batch', [Admin\ActivationCodeController::class, 'destroyBatch'])->name('destroy-batch');
|
||
Route::post('/destroy-selected', [Admin\ActivationCodeController::class, 'destroySelected'])->name('destroy-selected');
|
||
});
|
||
|
||
// Türler
|
||
Route::resource('genres', Admin\GenreController::class)->middleware(['admin:genres.manage']);
|
||
|
||
// Banner
|
||
Route::resource('banners', Admin\BannerController::class)->middleware(['admin:banners.manage']);
|
||
|
||
// Sezon JSON (AJAX)
|
||
Route::get('animes/{anime}/seasons-json', function (\App\Models\Anime $anime) {
|
||
return response()->json($anime->seasons()->select('id', 'season_number')->get());
|
||
})->name('animes.seasons-json')->middleware(['admin:animes.view']);
|
||
|
||
// Global İzin Ayarları — sadece admin
|
||
Route::get('permissions', [Admin\PermissionController::class, 'index'])->name('permissions.index')->middleware(['admin']);
|
||
Route::post('permissions', [Admin\PermissionController::class, 'update'])->name('permissions.update')->middleware(['admin']);
|
||
|
||
// Ayarlar — sadece admin
|
||
Route::get('settings', [Admin\SettingController::class, 'index'])->name('settings.index')->middleware(['admin']);
|
||
Route::post('settings', [Admin\SettingController::class, 'update'])->name('settings.update')->middleware(['admin']);
|
||
Route::post('settings/intro-upload', [Admin\SettingController::class, 'uploadIntro'])->name('settings.intro.upload')->middleware(['admin']);
|
||
Route::post('settings/favicon-upload', [Admin\SettingController::class, 'uploadFavicon'])->name('settings.favicon.upload')->middleware(['admin']);
|
||
Route::post('settings/mail-test', [Admin\SettingController::class, 'testMail'])->name('settings.mail.test')->middleware(['admin']);
|
||
|
||
// Trend Yönetimi — sadece admin
|
||
Route::get('trending', [Admin\TrendingController::class, 'index'])->name('trending.index')->middleware(['admin']);
|
||
Route::post('trending/{anime}/toggle', [Admin\TrendingController::class, 'toggle'])->name('trending.toggle')->middleware(['admin']);
|
||
Route::post('trending/{anime}/move', [Admin\TrendingController::class, 'move'])->name('trending.move')->middleware(['admin']);
|
||
Route::post('trending/reorder', [Admin\TrendingController::class, 'reorder'])->name('trending.reorder')->middleware(['admin']);
|
||
Route::get('trending/search', [Admin\TrendingController::class, 'search'])->name('trending.search')->middleware(['admin']);
|
||
Route::post('trending/compute-scores', [Admin\TrendingController::class, 'computeScores'])->name('trending.compute-scores')->middleware(['admin']);
|
||
|
||
// AI — sadece admin
|
||
Route::post('ai/generate', [Admin\AiController::class, 'generate'])->name('ai.generate')->middleware(['admin']);
|
||
Route::post('ai/anime-meta', [Admin\AiController::class, 'animeMeta'])->name('ai.anime-meta')->middleware(['admin']);
|
||
Route::get('ai/descriptions', [Admin\AiController::class, 'descriptionsPage'])->name('ai.descriptions')->middleware(['admin']);
|
||
Route::post('ai/episode-ids', [Admin\AiController::class, 'episodeIds'])->name('ai.episode-ids')->middleware(['admin']);
|
||
Route::post('ai/fill-one', [Admin\AiController::class, 'fillOne'])->name('ai.fill-one')->middleware(['admin']);
|
||
Route::get('ai/anime-meta-bulk', [Admin\AiController::class, 'animeMetaPage'])->name('ai.anime-meta-bulk')->middleware(['admin']);
|
||
Route::post('ai/anime-meta-ids', [Admin\AiController::class, 'animeMetaIds'])->name('ai.anime-meta-ids')->middleware(['admin']);
|
||
Route::post('ai/fill-anime-meta', [Admin\AiController::class, 'fillAnimeMeta'])->name('ai.fill-anime-meta')->middleware(['admin']);
|
||
|
||
// Analitik — genel sadece admin, kullanıcı analitik permission ile
|
||
Route::get('analytics', [AdminAnalyticsController::class, 'index'])->name('analytics.index')->middleware(['admin:analytics.view']);
|
||
Route::post('analytics/block-ip', [AdminAnalyticsController::class, 'blockIp'])->name('analytics.block-ip')->middleware(['admin:analytics.block']);
|
||
Route::post('analytics/unblock-ip',[AdminAnalyticsController::class, 'unblockIp'])->name('analytics.unblock-ip')->middleware(['admin:analytics.block']);
|
||
Route::get('stats', [\App\Http\Controllers\Admin\ContentStatsController::class, 'index'])->name('stats.index')->middleware(['admin']);
|
||
Route::get('health', [\App\Http\Controllers\Admin\HealthController::class, 'index'])->name('health.index')->middleware(['admin']);
|
||
Route::delete('health/anime/{anime}', [\App\Http\Controllers\Admin\HealthController::class, 'deleteAnime'])->name('health.delete-anime')->middleware(['admin']);
|
||
Route::post('health/anime/{anime}/delete-source', [\App\Http\Controllers\Admin\HealthController::class, 'deleteSourceEpisodes'])->name('health.delete-source-episodes')->middleware(['admin']);
|
||
Route::get('health/storage-stats', [\App\Http\Controllers\Admin\HealthController::class, 'storageStats'])->name('health.storage-stats')->middleware(['admin']);
|
||
Route::post('health/cleanup-storage', [\App\Http\Controllers\Admin\HealthController::class, 'cleanupStorage'])->name('health.cleanup-storage')->middleware(['admin']);
|
||
Route::get('health/session-info', [\App\Http\Controllers\Admin\HealthController::class, 'sessionInfo'])->name('health.session-info')->middleware(['admin']);
|
||
|
||
// Anime istekleri
|
||
Route::get('anime-requests', [\App\Http\Controllers\Admin\AnimeRequestController::class, 'index'])->name('anime-requests.index')->middleware(['admin:requests.manage']);
|
||
Route::patch('anime-requests/{animeRequest}', [\App\Http\Controllers\Admin\AnimeRequestController::class, 'update'])->name('anime-requests.update')->middleware(['admin:requests.manage']);
|
||
Route::delete('anime-requests/{animeRequest}', [\App\Http\Controllers\Admin\AnimeRequestController::class, 'destroy'])->name('anime-requests.destroy')->middleware(['admin:requests.manage']);
|
||
|
||
// Bildirim yönetimi
|
||
Route::get('notifications', [\App\Http\Controllers\Admin\NotificationController::class, 'index'])->name('notifications.index')->middleware(['admin:notifications.view']);
|
||
Route::post('notifications/send', [\App\Http\Controllers\Admin\NotificationController::class, 'send'])->name('notifications.send')->middleware(['admin:notifications.send']);
|
||
|
||
// Moderatör Yönetimi — sadece admin
|
||
Route::middleware(['admin'])->group(function () {
|
||
Route::get('moderators', [\App\Http\Controllers\Admin\ModeratorController::class, 'index'])->name('moderators.index');
|
||
Route::post('moderators/promote', [\App\Http\Controllers\Admin\ModeratorController::class, 'promote'])->name('moderators.promote');
|
||
Route::post('moderators/{user}/demote', [\App\Http\Controllers\Admin\ModeratorController::class, 'demote'])->name('moderators.demote');
|
||
Route::get('moderators/{user}/edit', [\App\Http\Controllers\Admin\ModeratorController::class, 'edit'])->name('moderators.edit');
|
||
Route::post('moderators/{user}/permissions', [\App\Http\Controllers\Admin\ModeratorController::class, 'savePermissions'])->name('moderators.permissions');
|
||
Route::post('moderators/{user}/toggle-permission', [\App\Http\Controllers\Admin\ModeratorController::class, 'togglePermission'])->name('moderators.toggle-permission');
|
||
});
|
||
|
||
// Kullanıcı Analitiği
|
||
Route::get('analytics/users', [\App\Http\Controllers\Admin\UserAnalyticsController::class, 'index'])->name('analytics.users')->middleware(['admin:analytics.users']);
|
||
Route::get('analytics/users/{user}', [\App\Http\Controllers\Admin\UserAnalyticsController::class, 'userDetail'])->name('analytics.user-detail')->middleware(['admin:analytics.users']);
|
||
|
||
// Mobil Uygulama Yönetimi — sadece admin
|
||
Route::get('mobile-app', [\App\Http\Controllers\Admin\MobileAppController::class, 'index'])->name('mobile.index')->middleware(['admin']);
|
||
Route::post('mobile-app', [\App\Http\Controllers\Admin\MobileAppController::class, 'update'])->name('mobile.update')->middleware(['admin']);
|
||
|
||
// Import (Anime çekme)
|
||
Route::get('import', [Admin\ImportController::class, 'index'])->name('import.index')->middleware(['admin:import.manage']);
|
||
Route::post('import', [Admin\ImportController::class, 'store'])->name('import.store')->middleware(['admin:import.manage']);
|
||
Route::get('import/{import}', [Admin\ImportController::class, 'show'])->name('import.show')->middleware(['admin:import.manage']);
|
||
Route::delete('import/{import}', [Admin\ImportController::class, 'destroy'])->name('import.destroy')->middleware(['admin:import.manage']);
|
||
Route::delete('import-failed', [Admin\ImportController::class, 'destroyFailed'])->name('import.destroyFailed')->middleware(['admin:import.manage']);
|
||
Route::delete('import-pending', [Admin\ImportController::class, 'destroyPending'])->name('import.destroyPending')->middleware(['admin:import.manage']);
|
||
Route::delete('import-stuck', [Admin\ImportController::class, 'destroyStuck'])->name('import.destroyStuck')->middleware(['admin:import.manage']);
|
||
Route::delete('import-by-status', [Admin\ImportController::class, 'destroyByStatus'])->name('import.destroyByStatus')->middleware(['admin:import.manage']);
|
||
Route::get('import-bulk-counts', [Admin\ImportController::class, 'bulkCounts'])->name('import.bulkCounts')->middleware(['admin:import.manage']);
|
||
// Import araçları (terminal gerektirmez)
|
||
Route::post('import-tools/fix-subtitles', [Admin\ImportController::class, 'fixSubtitles'])->name('import.fixSubtitles')->middleware(['admin:import.manage']);
|
||
Route::post('import-tools/requeue-anizium', [Admin\ImportController::class, 'requeueAnizium'])->name('import.requeueAnizium')->middleware(['admin:import.manage']);
|
||
Route::post('import-tools/requeue-animecix', [Admin\ImportController::class, 'requeueAnimecix'])->name('import.requeueAnimecix')->middleware(['admin:import.manage']);
|
||
Route::get('import-tools/source-stats', [Admin\ImportController::class, 'sourceStats'])->name('import.sourceStats')->middleware(['admin:import.manage']);
|
||
|
||
// SEO Paneli — sadece admin
|
||
Route::middleware(['admin'])->group(function () {
|
||
Route::get('seo', [\App\Http\Controllers\Admin\SeoController::class, 'index'])->name('seo.index');
|
||
Route::post('seo', [\App\Http\Controllers\Admin\SeoController::class, 'update'])->name('seo.update');
|
||
Route::post('seo/robots', [\App\Http\Controllers\Admin\SeoController::class, 'updateRobots'])->name('seo.robots');
|
||
Route::post('seo/ping', [\App\Http\Controllers\Admin\SeoController::class, 'pingSearchEngines'])->name('seo.ping');
|
||
Route::get('seo/audit', [\App\Http\Controllers\Admin\SeoController::class, 'auditJson'])->name('seo.audit');
|
||
Route::post('seo/keywords', [\App\Http\Controllers\Admin\SeoController::class, 'storeKeyword'])->name('seo.keywords.store');
|
||
Route::delete('seo/keywords/{keyword}', [\App\Http\Controllers\Admin\SeoController::class, 'destroyKeyword'])->name('seo.keywords.destroy');
|
||
Route::post('seo/redirects', [\App\Http\Controllers\Admin\SeoController::class, 'storeRedirect'])->name('seo.redirects.store');
|
||
Route::delete('seo/redirects/{redirect}', [\App\Http\Controllers\Admin\SeoController::class, 'destroyRedirect'])->name('seo.redirects.destroy');
|
||
Route::post('seo/redirects/{redirect}/toggle', [\App\Http\Controllers\Admin\SeoController::class, 'toggleRedirect'])->name('seo.redirects.toggle');
|
||
Route::post('seo/anime-bulk', [\App\Http\Controllers\Admin\SeoController::class, 'bulkSaveAnime'])->name('seo.anime-bulk');
|
||
Route::post('seo/anime/{anime}/generate', [\App\Http\Controllers\Admin\SeoController::class, 'generateAnimeSeo'])->name('seo.anime.generate');
|
||
Route::post('seo/anime-bulk-generate', [\App\Http\Controllers\Admin\SeoController::class, 'bulkGenerateAllSeo'])->name('seo.anime.bulk-generate');
|
||
Route::post('seo/ai-bulk-generate', [\App\Http\Controllers\Admin\SeoController::class, 'aiBulkGenerateSeo'])->name('seo.ai.bulk-generate');
|
||
Route::post('seo/bulk-fill-batch', [\App\Http\Controllers\Admin\SeoController::class, 'bulkFillBatch'])->name('seo.bulk-fill-batch');
|
||
Route::get('seo/bulk-fill-stats', [\App\Http\Controllers\Admin\SeoController::class, 'bulkFillStats'])->name('seo.bulk-fill-stats');
|
||
Route::post('seo/pagespeed', [\App\Http\Controllers\Admin\SeoController::class, 'pagespeedCheck'])->name('seo.pagespeed');
|
||
Route::get('seo/internal-links', [\App\Http\Controllers\Admin\SeoController::class, 'internalLinksAudit'])->name('seo.internal-links');
|
||
Route::get('seo/duplicate-content', [\App\Http\Controllers\Admin\SeoController::class, 'duplicateContent'])->name('seo.duplicate-content');
|
||
Route::post('seo/ai/chat', [\App\Http\Controllers\Admin\SeoController::class, 'aiChat'])->name('seo.ai.chat');
|
||
Route::post('seo/ai/anime/{anime}/generate', [\App\Http\Controllers\Admin\SeoController::class, 'aiGenerateAnimeSeo'])->name('seo.ai.anime-generate');
|
||
Route::post('seo/ai/keyword-suggest', [\App\Http\Controllers\Admin\SeoController::class, 'aiKeywordSuggest'])->name('seo.ai.keyword-suggest');
|
||
Route::post('seo/ai/page-analysis', [\App\Http\Controllers\Admin\SeoController::class, 'aiPageAnalysis'])->name('seo.ai.page-analysis');
|
||
Route::post('seo/ai/faq-schema', [\App\Http\Controllers\Admin\SeoController::class, 'aiFaqSchema'])->name('seo.ai.faq-schema');
|
||
Route::post('seo/ai/content-strategy', [\App\Http\Controllers\Admin\SeoController::class, 'aiContentStrategy'])->name('seo.ai.content-strategy');
|
||
Route::post('seo/ai/robots-txt', [\App\Http\Controllers\Admin\SeoController::class, 'aiRobotsTxt'])->name('seo.ai.robots-txt');
|
||
});
|
||
|
||
// Blog Yönetimi — sadece admin
|
||
Route::middleware(['admin'])->group(function () {
|
||
Route::get('blog', [\App\Http\Controllers\Admin\BlogController::class, 'index'])->name('blog.index');
|
||
Route::get('blog/create', [\App\Http\Controllers\Admin\BlogController::class, 'create'])->name('blog.create');
|
||
Route::post('blog', [\App\Http\Controllers\Admin\BlogController::class, 'store'])->name('blog.store');
|
||
Route::get('blog/{blog}/edit', [\App\Http\Controllers\Admin\BlogController::class, 'edit'])->name('blog.edit');
|
||
Route::put('blog/{blog}', [\App\Http\Controllers\Admin\BlogController::class, 'update'])->name('blog.update');
|
||
Route::delete('blog/{blog}', [\App\Http\Controllers\Admin\BlogController::class, 'destroy'])->name('blog.destroy');
|
||
Route::post('blog/generate-ai', [\App\Http\Controllers\Admin\BlogController::class, 'generateAi'])->name('blog.generate-ai');
|
||
Route::post('blog/bulk-generate', [\App\Http\Controllers\Admin\BlogController::class, 'bulkGenerate'])->name('blog.bulk-generate');
|
||
});
|
||
});
|
||
|
||
// ── Sitemap ──────────────────────────────────────────────────────────────────
|
||
Route::get('/sitemap.xml', [\App\Http\Controllers\SitemapController::class, 'index'])->name('sitemap');
|
||
Route::get('/sitemap-main.xml', [\App\Http\Controllers\SitemapController::class, 'main'])->name('sitemap.main');
|
||
Route::get('/sitemap-animes.xml', [\App\Http\Controllers\SitemapController::class, 'animes'])->name('sitemap.animes');
|
||
Route::get('/sitemap-videos.xml', [\App\Http\Controllers\SitemapController::class, 'videos'])->name('sitemap.videos');
|
||
Route::get('/sitemap-blog.xml', [\App\Http\Controllers\SitemapController::class, 'blog'])->name('sitemap.blog');
|
||
|
||
// ── Frontend ─────────────────────────────────────────────────────────────────
|
||
Route::get('/', [Frontend\HomeController::class, 'index'])->name('home');
|
||
Route::get('/search', [Frontend\HomeController::class, 'search'])->name('search');
|
||
Route::get('/search/suggest', [Frontend\HomeController::class, 'searchSuggest'])->name('search.suggest');
|
||
Route::get('/genre/{genre:slug}', [Frontend\HomeController::class, 'genre'])->name('genre');
|
||
Route::get('/anime/{anime:slug}', [Frontend\AnimeController::class, 'show'])->name('anime.show');
|
||
Route::get('/watch/{anime:slug}/{season}/{episode}', [Frontend\PlayerController::class, 'watch'])->where(['season' => '[0-9]+', 'episode' => '[0-9]+'])->name('watch')->middleware('secure.player');
|
||
|
||
// Keşfet (Tinder-style discovery)
|
||
Route::get('/kesfet', [DiscoverController::class, 'index'])->name('discover');
|
||
Route::get('/kesfet/cards', [DiscoverController::class, 'cards'])->name('discover.cards');
|
||
Route::post('/kesfet/swipe', [DiscoverController::class, 'swipe'])->name('discover.swipe');
|
||
Route::post('/kesfet/reset', [DiscoverController::class, 'reset'])->name('discover.reset');
|
||
Route::get('/kesfet/results', [DiscoverController::class, 'results'])->name('discover.results');
|
||
|
||
// Blog
|
||
Route::get('/blog', [\App\Http\Controllers\Frontend\BlogController::class, 'index'])->name('blog.index');
|
||
Route::get('/blog/{slug}', [\App\Http\Controllers\Frontend\BlogController::class, 'show'])->name('blog.show');
|
||
|
||
// Kullanıcı arama (share modal için)
|
||
Route::middleware('auth')->get('/api/users/search', function(\Illuminate\Http\Request $req) {
|
||
$q = trim($req->query('q', ''));
|
||
if (strlen($q) < 2) return response()->json([]);
|
||
$me = auth()->id();
|
||
$users = \App\Models\User::where('id', '!=', $me)
|
||
->where(fn($query) => $query->where('name','like',"%{$q}%")->orWhere('username','like',"%{$q}%"))
|
||
->limit(8)
|
||
->get()
|
||
->map(fn($u) => [
|
||
'id' => $u->id,
|
||
'name' => $u->name,
|
||
'username' => $u->username,
|
||
'avatar' => $u->avatar ? \App\Support\MediaUrl::fromStoragePath($u->avatar) : null,
|
||
]);
|
||
return response()->json($users);
|
||
})->name('api.users.search');
|
||
|
||
// Public profil
|
||
Route::get('/u/{user}', [Frontend\ProfileController::class, 'publicProfile'])->name('user.profile');
|
||
Route::get('/u/{user}/compatibility', [SocialController::class, 'compatibility'])->name('user.compatibility');
|
||
Route::get('/u/{user}/card', [SocialController::class, 'card'])->name('user.card');
|
||
|
||
// Watch Party (public read, auth write)
|
||
Route::get('/party/{roomCode}', [SocialController::class, 'partyShow'])->name('watch.party');
|
||
|
||
// NicoNico timestamp yorumları (okuma herkese açık)
|
||
Route::get('/episode/{episode}/timestamp-comments', [SocialController::class, 'timestampComments'])->name('episode.timestamp-comments');
|
||
|
||
// Tahmin sayısı (herkese açık)
|
||
Route::get('/episode/{episode}/predictions', [SocialController::class, 'predictions'])->name('episode.predictions');
|
||
|
||
// İlk kez izleyenler sayısı (herkese açık)
|
||
Route::get('/episode/{episode}/first-watch-count', [SocialController::class, 'firstWatchCount'])->name('episode.first-watch-count');
|
||
|
||
// Premium / Planlar
|
||
Route::get('/premium', [\App\Http\Controllers\Frontend\PremiumController::class, 'plans'])->name('premium.plans');
|
||
|
||
// Aktivasyon kodu
|
||
Route::get('/premium/activate', [\App\Http\Controllers\Frontend\ActivationController::class, 'show'])->name('premium.activate');
|
||
Route::middleware('auth')->post('/premium/activate', [\App\Http\Controllers\Frontend\ActivationController::class, 'redeem'])->name('premium.activate.redeem');
|
||
|
||
// Checkout (ödeme — iyzico, ileride kullanılabilir)
|
||
Route::middleware('auth')->group(function () {
|
||
Route::get('/checkout/{plan}', [\App\Http\Controllers\Frontend\CheckoutController::class, 'show'])->name('checkout.show');
|
||
Route::post('/checkout/{plan}', [\App\Http\Controllers\Frontend\CheckoutController::class, 'initialize'])->name('checkout.initialize');
|
||
Route::get('/checkout/success', [\App\Http\Controllers\Frontend\CheckoutController::class, 'success'])->name('checkout.success');
|
||
Route::get('/checkout/failed', [\App\Http\Controllers\Frontend\CheckoutController::class, 'failed'])->name('checkout.failed');
|
||
});
|
||
Route::post('/checkout/callback', [\App\Http\Controllers\Frontend\CheckoutController::class, 'callback'])->name('checkout.callback');
|
||
|
||
// ── Frontend — Auth required ──────────────────────────────────────────────────
|
||
Route::middleware('auth')->group(function () {
|
||
Route::get('/profile', [ProfileController::class, 'show'])->name('profile');
|
||
Route::get('/profile/settings', [ProfileController::class, 'settings'])->name('profile.settings');
|
||
Route::post('/profile/settings', [ProfileController::class, 'update'])->name('profile.update');
|
||
Route::post('/profile/avatar', [ProfileController::class, 'updateAvatar'])->name('profile.avatar');
|
||
Route::post('/profile/banner', [ProfileController::class, 'updateBanner'])->name('profile.banner');
|
||
Route::post('/profile/password', [ProfileController::class, 'updatePassword'])->name('profile.password');
|
||
Route::post('/profile/premium-cosmetics', [\App\Http\Controllers\Frontend\PremiumController::class, 'saveCosmetics'])->name('premium.cosmetics.save');
|
||
Route::post('/comments', [CommentController::class, 'store'])->name('comments.store')->middleware('throttle:12,1');
|
||
Route::post('/comments/{comment}/like', [CommentController::class, 'like'])->name('comments.like')->middleware('throttle:60,1');
|
||
|
||
// AI Hub
|
||
Route::get('/ai', [AiController::class, 'index'])->name('ai.index');
|
||
|
||
// Kullanıcı özellikleri
|
||
Route::get('/watchlist', [UserFeatureController::class, 'watchlistIndex'])->name('watchlist');
|
||
Route::get('/watchlist/export', [UserFeatureController::class, 'watchlistExport'])->name('watchlist.export');
|
||
Route::post('/watchlist/{anime}/toggle', [UserFeatureController::class, 'watchlistToggle'])->name('watchlist.toggle');
|
||
Route::post('/episode/{episode}/vote', [UserFeatureController::class, 'episodeVote'])->name('episode.vote');
|
||
Route::post('/anime/{anime}/rate', [UserFeatureController::class, 'animeRate'])->name('anime.rate');
|
||
Route::post('/continue-watching', [UserFeatureController::class, 'continueWatchingUpdate'])->name('continue.update');
|
||
|
||
// Anime istekleri (store + vote auth gerektirir)
|
||
Route::post('/anime-request', [UserFeatureController::class, 'requestStore'])->name('anime.request.store');
|
||
Route::post('/anime-request/{animeRequest}/vote', [UserFeatureController::class, 'requestVote'])->name('anime.request.vote');
|
||
|
||
// Takip
|
||
Route::post('/anime/{anime:slug}/follow', [UserFeatureController::class, 'followToggle'])->name('anime.follow');
|
||
|
||
// Kullanıcı takip
|
||
Route::post('/u/{user}/follow', [SocialController::class, 'followToggle'])->name('user.follow');
|
||
|
||
// Mesajlar
|
||
Route::get('/messages', [MessageController::class, 'index'])->name('messages.index');
|
||
Route::get('/messages/conversations', [MessageController::class, 'conversationsJson'])->name('messages.conversations');
|
||
Route::post('/messages/quick-share', [MessageController::class, 'quickShare'])->name('messages.quick-share');
|
||
Route::get('/messages/{conversation}', [MessageController::class, 'show'])->name('messages.show');
|
||
Route::post('/messages/{conversation}/send', [MessageController::class, 'send'])->name('messages.send')->middleware('throttle:30,1');
|
||
Route::get('/messages/{conversation}/poll', [MessageController::class, 'poll'])->name('messages.poll');
|
||
Route::post('/messages/start/{user}', [MessageController::class, 'startOrOpen'])->name('messages.start');
|
||
Route::post('/messages/upload-image', [MessageController::class, 'uploadImage'])->name('messages.upload-image')->middleware('throttle:20,1');
|
||
|
||
// Sesli Arama
|
||
Route::post('/calls/initiate', [VoiceCallController::class, 'initiate'])->name('calls.initiate')->middleware('throttle:10,1');
|
||
Route::post('/calls/{call}/answer', [VoiceCallController::class, 'answer'])->name('calls.answer');
|
||
Route::post('/calls/{call}/decline', [VoiceCallController::class, 'decline'])->name('calls.decline');
|
||
Route::post('/calls/{call}/end', [VoiceCallController::class, 'end'])->name('calls.end');
|
||
Route::get('/calls/poll', [VoiceCallController::class, 'poll'])->name('calls.poll');
|
||
|
||
// ── Sosyal Özellikler (auth gerektirir) ──────────────────────────────────
|
||
// NicoNico yorum yaz
|
||
Route::post('/episode/{episode}/timestamp-comments', [SocialController::class, 'timestampCommentStore'])->name('episode.timestamp-comment.store')->middleware('throttle:10,1');
|
||
|
||
// Tahmin oyunu
|
||
Route::post('/episode/{episode}/predictions', [SocialController::class, 'predictionStore'])->name('episode.prediction.store')->middleware('throttle:5,1');
|
||
Route::post('/predictions/{prediction}/vote', [SocialController::class, 'predictionVote'])->name('prediction.vote')->middleware('throttle:30,1');
|
||
|
||
// Watch Party
|
||
Route::post('/party/create', [SocialController::class, 'partyCreate'])->name('party.create');
|
||
Route::post('/party/{roomCode}/join', [SocialController::class, 'partyJoin'])->name('party.join');
|
||
Route::post('/party/{roomCode}/sync', [SocialController::class, 'partySync'])->name('party.sync');
|
||
Route::post('/party/{roomCode}/leave', [SocialController::class, 'partyLeave'])->name('party.leave');
|
||
|
||
// İlk kez izleyenler — kayıt
|
||
Route::post('/episode/{episode}/first-watch', [SocialController::class, 'firstWatchRegister'])->name('episode.first-watch');
|
||
|
||
// Bildirimler
|
||
Route::get('/notifications', [UserFeatureController::class, 'notificationsIndex'])->name('notifications');
|
||
|
||
// Bölüm notları
|
||
Route::post('/episode/{episode}/note', [UserFeatureController::class, 'noteStore'])->name('episode.note.store');
|
||
Route::delete('/notes/{note}', [UserFeatureController::class, 'noteDelete'])->name('episode.note.delete');
|
||
Route::get('/episode/{episode}/notes', [UserFeatureController::class, 'episodeNotesList'])->name('episode.notes.list');
|
||
Route::post('/ai/chat', [AiController::class, 'chat'])->name('ai.chat')->middleware('throttle:25,1');
|
||
Route::post('/ai/recommend', [AiController::class, 'recommend'])->name('ai.recommend')->middleware('throttle:20,1');
|
||
Route::post('/ai/search', [AiController::class, 'search'])->name('ai.search')->middleware('throttle:30,1');
|
||
Route::post('/ai/episode-info', [AiController::class, 'episodeInfo'])->name('ai.episode-info')->middleware('throttle:20,1');
|
||
Route::post('/ai/similar', [AiController::class, 'similar'])->name('ai.similar')->middleware('throttle:20,1');
|
||
});
|
||
// Anime istekleri listesi (herkese açık)
|
||
Route::get('/anime-request', [UserFeatureController::class, 'requestIndex'])->name('anime.request');
|
||
|
||
// Bildirim sayısı (AJAX, herkese açık — auth kontrolü controller'da)
|
||
Route::get('/notifications/count', [UserFeatureController::class, 'notificationsCount'])->name('notifications.count');
|
||
Route::get('/messages/unread-count', [MessageController::class, 'unreadCount'])->name('messages.unread-count');
|
||
|
||
Route::get('/comments', [CommentController::class, 'index'])->name('comments.index');
|
||
|
||
// ── Tracking (no auth required) ───────────────────────────────────────────────
|
||
Route::post('/track/pageview', [TrackingController::class, 'pageview'])->name('track.pageview');
|
||
Route::post('/track/watch', [TrackingController::class, 'watch'])->name('track.watch');
|
||
Route::post('/track/session-end', [TrackingController::class, 'sessionEnd'])->name('track.session-end');
|
||
Route::get('/comments/gif-search', [CommentController::class, 'gifSearch'])->name('comments.gif-search');
|
||
|
||
// ── Frontend Auth ─────────────────────────────────────────────────────────────
|
||
Route::get('/login', [Frontend\AuthController::class, 'showLogin'])->name('frontend.login')->middleware('guest');
|
||
Route::post('/login', [Frontend\AuthController::class, 'login'])->middleware(['guest', 'throttle:10,1']);
|
||
Route::get('/register', [Frontend\AuthController::class, 'showRegister'])->name('frontend.register')->middleware('guest');
|
||
Route::post('/register',[Frontend\AuthController::class, 'register'])->middleware(['guest', 'throttle:5,1']);
|
||
Route::post('/logout', [Frontend\AuthController::class, 'logout'])->name('frontend.logout')->middleware('auth');
|
||
|
||
// ── Social Auth ───────────────────────────────────────────────────────────────
|
||
Route::get('/auth/{provider}', [Frontend\AuthController::class, 'socialRedirect'])->name('social.redirect')->middleware('guest');
|
||
Route::get('/auth/{provider}/callback', [Frontend\AuthController::class, 'socialCallback'])->name('social.callback');
|
||
|
||
// ── Şifre Sıfırlama ──────────────────────────────────────────────────────────
|
||
Route::get('/forgot-password', [Frontend\PasswordResetController::class, 'showForgot'])->name('password.request')->middleware('guest');
|
||
Route::post('/forgot-password', [Frontend\PasswordResetController::class, 'sendResetLink'])->name('password.email')->middleware(['guest','throttle:5,1']);
|
||
Route::get('/reset-password/{token}', [Frontend\PasswordResetController::class, 'showReset'])->name('password.reset')->middleware('guest');
|
||
Route::post('/reset-password', [Frontend\PasswordResetController::class, 'reset'])->name('password.update')->middleware(['guest','throttle:5,1']);
|
||
|
||
// ── E-posta Doğrulama ────────────────────────────────────────────────────────
|
||
Route::get('/email/verify', [Frontend\EmailVerificationController::class, 'notice'])->name('verification.notice')->middleware('auth');
|
||
Route::get('/email/verify/{id}/{hash}', [Frontend\EmailVerificationController::class, 'verify'])->name('verification.verify')->middleware(['auth','signed']);
|
||
Route::post('/email/verification-notification', [Frontend\EmailVerificationController::class, 'resend'])->name('verification.send')->middleware(['auth','throttle:3,1']);
|
||
|
||
// HLS Stream Proxy - CORS bypass for external m3u8 sources
|
||
Route::get("/stream/proxy", function (\Illuminate\Http\Request $request) {
|
||
$u = $request->query("u", "");
|
||
$url = base64_decode($u, true);
|
||
if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) abort(400);
|
||
|
||
// Block private/internal IPs (SSRF protection)
|
||
$host = parse_url($url, PHP_URL_HOST) ?? "";
|
||
if (!$host || preg_match('/^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/', $host)) abort(403);
|
||
// Only allow HTTPS streaming URLs (m3u8 or ts segments)
|
||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? "";
|
||
if (!in_array($scheme, ['http', 'https'])) abort(403);
|
||
|
||
// Referer: custom ref param > auto-detect from URL's own origin
|
||
$customRef = $request->query("ref", "");
|
||
if ($customRef) {
|
||
$decoded = base64_decode($customRef, true);
|
||
$referer = ($decoded && filter_var($decoded, FILTER_VALIDATE_URL)) ? rtrim($decoded, '/') . '/' : null;
|
||
}
|
||
if (empty($referer)) {
|
||
// Bilinen CDN'ler için doğru Referer'ı zorla — parametre gelmese bile
|
||
$knownRefs = [
|
||
'aniziumserver.sbs' => 'https://anizium.co/',
|
||
'aniziumserver.site' => 'https://anizium.co/',
|
||
'aniziumserver.com' => 'https://anizium.co/',
|
||
];
|
||
$referer = null;
|
||
foreach ($knownRefs as $domain => $ref) {
|
||
if ($host === $domain || str_ends_with($host, '.' . $domain)) {
|
||
$referer = $ref;
|
||
break;
|
||
}
|
||
}
|
||
if (!$referer) $referer = $scheme . '://' . $host . '/';
|
||
$customRef = base64_encode($referer);
|
||
}
|
||
$origin = rtrim($referer, '/');
|
||
|
||
// Tek istek yürüten yardımcı — farklı header setleriyle tekrar denemek için
|
||
$fetchUpstream = function (string $url, array $headers) {
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_FOLLOWLOCATION => true,
|
||
CURLOPT_MAXREDIRS => 5,
|
||
CURLOPT_TIMEOUT => 15,
|
||
CURLOPT_CONNECTTIMEOUT => 8,
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_SSL_VERIFYHOST => false,
|
||
CURLOPT_ENCODING => '',
|
||
CURLOPT_HTTPHEADER => $headers,
|
||
]);
|
||
$content = curl_exec($ch);
|
||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$ctype = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||
$errNo = curl_errno($ch);
|
||
$errStr = $errNo ? curl_strerror($errNo) : '';
|
||
curl_close($ch);
|
||
return [$content, $httpCode, $ctype, $errStr];
|
||
};
|
||
|
||
$UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
||
|
||
// 1) Referer-only (gerçek oynatıcı davranışı — Origin göndermez, hotlink koruması daha toleranslı)
|
||
[$content, $httpCode, $contentType, $curlErrStr] = $fetchUpstream($url, [
|
||
"Referer: {$referer}",
|
||
"User-Agent: {$UA}",
|
||
"Accept: */*",
|
||
]);
|
||
|
||
// 2) Başarısızsa Origin + Sec-Fetch header'larıyla (tarayıcı taklidi) tekrar dene
|
||
if (!$content || $httpCode < 200 || $httpCode >= 400) {
|
||
[$content, $httpCode, $contentType, $curlErrStr] = $fetchUpstream($url, [
|
||
"Referer: {$referer}",
|
||
"Origin: {$origin}",
|
||
"User-Agent: {$UA}",
|
||
"Accept: */*",
|
||
"Sec-Fetch-Dest: empty",
|
||
"Sec-Fetch-Mode: cors",
|
||
"Sec-Fetch-Site: cross-site",
|
||
]);
|
||
}
|
||
|
||
if (!$content || $httpCode < 200 || $httpCode >= 400) {
|
||
\Illuminate\Support\Facades\Log::warning('stream_proxy_fail', [
|
||
'url' => $url,
|
||
'http' => $httpCode,
|
||
'curl' => $curlErrStr,
|
||
'body' => substr((string)$content, 0, 300),
|
||
]);
|
||
abort(502);
|
||
}
|
||
|
||
$path = strtolower(parse_url($url, PHP_URL_PATH) ?? "");
|
||
$isM3u8 = str_ends_with($path, ".m3u8")
|
||
|| str_contains($contentType, "mpegurl")
|
||
|| str_starts_with(trim((string)$content), "#EXTM3U");
|
||
|
||
if ($isM3u8) {
|
||
$baseUrl = rtrim(dirname($url), "/");
|
||
$refSuffix = "&ref=" . urlencode($customRef);
|
||
$proxyBase = url("/stream/proxy") . "?u=";
|
||
$segBase = url("/stream/seg") . "?u=";
|
||
$lines = explode("\n", str_replace("\r\n", "\n", (string)$content));
|
||
$out = [];
|
||
foreach ($lines as $line) {
|
||
$t = rtrim($line);
|
||
if ($t === "") { $out[] = ""; continue; }
|
||
if (str_starts_with($t, "#")) {
|
||
// URI="..." attribute → şifreleme anahtarları proxy'den geçsin
|
||
$t = preg_replace_callback('/URI="([^"]+)"/', function ($m) use ($baseUrl, $proxyBase, $refSuffix) {
|
||
$abs = str_starts_with($m[1], "http") ? $m[1] : $baseUrl . "/" . ltrim($m[1], "/");
|
||
return "URI=\"" . $proxyBase . base64_encode($abs) . $refSuffix . "\"";
|
||
}, $t);
|
||
$out[] = $t;
|
||
} else {
|
||
$abs = str_starts_with($t, "http") ? $t : $baseUrl . "/" . ltrim($t, "/");
|
||
$absHost = parse_url($abs, PHP_URL_HOST) ?? "";
|
||
$absPath = strtolower(parse_url($abs, PHP_URL_PATH) ?? "");
|
||
// Sub-playlist (.m3u8) → manifest proxy (kendi segment URL'lerini de düzeltir, ref aktarılır)
|
||
if (str_ends_with($absPath, ".m3u8")) {
|
||
$out[] = $proxyBase . base64_encode($abs) . $refSuffix;
|
||
} else {
|
||
// BunnyCDN, animexe CDN → direkt (CORS destekli)
|
||
// Diğerleri → /stream/seg (Referer spoofing + önbellekli)
|
||
$isTrusted = str_ends_with($absHost, "b-cdn.net")
|
||
|| str_ends_with($absHost, "animexe.com");
|
||
$out[] = $isTrusted ? $abs : $segBase . base64_encode($abs) . $refSuffix;
|
||
}
|
||
}
|
||
}
|
||
return response(implode("\n", $out), 200, [
|
||
"Content-Type" => "application/vnd.apple.mpegurl; charset=utf-8",
|
||
"Access-Control-Allow-Origin" => "*",
|
||
"Cache-Control" => "no-cache, no-store",
|
||
]);
|
||
}
|
||
|
||
// Segment proxy: Content-Type düzeltmesi
|
||
// .png uzantılı ama aslında video segmenti olan dosyaları düzelt
|
||
$segCt = $contentType;
|
||
if (str_starts_with($segCt, "image/") || $segCt === "application/octet-stream") {
|
||
$segCt = "video/mp2t";
|
||
}
|
||
return response($content, 200, [
|
||
"Content-Type" => $segCt ?: "video/mp2t",
|
||
"Access-Control-Allow-Origin" => "*",
|
||
"Cache-Control" => "public, max-age=3600",
|
||
]);
|
||
})->name("stream.proxy");
|
||
|
||
// Segment proxy — önbellekli, Content-Type düzeltmeli (.png → video/mp2t)
|
||
// İlk istekte CDN'den çekip diske kaydeder, sonraki istekler PHP'yi atlatır
|
||
Route::get("/stream/seg", function (\Illuminate\Http\Request $request) {
|
||
$u = $request->query("u", "");
|
||
$url = base64_decode($u, true);
|
||
if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) abort(400);
|
||
|
||
$host = parse_url($url, PHP_URL_HOST) ?? "";
|
||
if (!$host || preg_match('/^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/', $host)) abort(403);
|
||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? "";
|
||
if (!in_array($scheme, ['http', 'https'])) abort(403);
|
||
|
||
// Referer: custom ref param > auto-detect from URL's own origin
|
||
$customRef = $request->query("ref", "");
|
||
if ($customRef) {
|
||
$decoded = base64_decode($customRef, true);
|
||
$referer = ($decoded && filter_var($decoded, FILTER_VALIDATE_URL)) ? rtrim($decoded, '/') . '/' : null;
|
||
}
|
||
if (empty($referer)) {
|
||
$referer = $scheme . '://' . $host . '/';
|
||
}
|
||
$origin = rtrim($referer, '/');
|
||
|
||
$cacheDir = storage_path('app/seg_cache');
|
||
if (!is_dir($cacheDir)) @mkdir($cacheDir, 0755, true);
|
||
$cachePath = $cacheDir . '/' . md5($url) . '.ts';
|
||
|
||
// Önbellekte varsa direkt sun
|
||
if (file_exists($cachePath)) {
|
||
$size = filesize($cachePath);
|
||
return response(file_get_contents($cachePath), 200, [
|
||
'Content-Type' => 'video/mp2t',
|
||
'Content-Length' => $size,
|
||
'Access-Control-Allow-Origin' => '*',
|
||
'Cache-Control' => 'public, max-age=86400',
|
||
]);
|
||
}
|
||
|
||
// CDN'den çek — önce Referer-only (oynatıcı taklidi), olmazsa Origin ekle
|
||
$segFetch = function (string $url, array $headers) {
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_FOLLOWLOCATION => true,
|
||
CURLOPT_MAXREDIRS => 5,
|
||
CURLOPT_TIMEOUT => 15,
|
||
CURLOPT_CONNECTTIMEOUT => 8,
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_SSL_VERIFYHOST => false,
|
||
CURLOPT_ENCODING => '',
|
||
CURLOPT_HTTPHEADER => $headers,
|
||
]);
|
||
$content = curl_exec($ch);
|
||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
return [$content, $httpCode];
|
||
};
|
||
$segUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
||
|
||
[$content, $httpCode] = $segFetch($url, [
|
||
"Referer: {$referer}",
|
||
"User-Agent: {$segUA}",
|
||
"Accept: */*",
|
||
]);
|
||
if (!$content || $httpCode < 200 || $httpCode >= 400) {
|
||
[$content, $httpCode] = $segFetch($url, [
|
||
"Referer: {$referer}",
|
||
"Origin: {$origin}",
|
||
"User-Agent: {$segUA}",
|
||
"Accept: */*",
|
||
"Sec-Fetch-Dest: empty",
|
||
"Sec-Fetch-Mode: cors",
|
||
"Sec-Fetch-Site: cross-site",
|
||
]);
|
||
}
|
||
|
||
if (!$content || $httpCode < 200 || $httpCode >= 400) abort(502);
|
||
|
||
// Diske kaydet (sadece dosya varsa — inode kontrolü)
|
||
$segFiles = glob("{$cacheDir}/*.ts") ?: [];
|
||
if (count($segFiles) < 300) {
|
||
// Max 300 segment tutuyoruz — inode patlamasını önler
|
||
file_put_contents($cachePath, $content);
|
||
}
|
||
|
||
// %10 ihtimalle temizlik: 1 saatten eski segmentleri sil
|
||
if (rand(1, 10) === 1) {
|
||
foreach (glob("{$cacheDir}/*.ts") ?: [] as $f) {
|
||
if (filemtime($f) < time() - 3600) @unlink($f);
|
||
}
|
||
// Max 300 üzerindeyse en eskileri sil
|
||
$files = glob("{$cacheDir}/*.ts") ?: [];
|
||
if (count($files) > 300) {
|
||
usort($files, fn($a, $b) => filemtime($a) - filemtime($b));
|
||
foreach (array_slice($files, 0, count($files) - 300) as $f) @unlink($f);
|
||
}
|
||
}
|
||
|
||
return response($content, 200, [
|
||
'Content-Type' => 'video/mp2t',
|
||
'Content-Length' => strlen($content),
|
||
'Access-Control-Allow-Origin' => '*',
|
||
'Cache-Control' => 'public, max-age=86400',
|
||
]);
|
||
})->name('stream.seg');
|
||
|
||
// VTT Proxy (CORS bypass for subtitle files)
|
||
Route::get('/vtt-proxy', function (\Illuminate\Http\Request $request) {
|
||
$url = $request->query('url', '');
|
||
if (!filter_var($url, FILTER_VALIDATE_URL)) abort(400);
|
||
$host = parse_url($url, PHP_URL_HOST) ?? '';
|
||
$allowed = ['b-cdn.net', 'bunnycdn.com', 'aniziumserver.site', 'aniziumserver.com', 'aniziumserver.sbs', 'anizium.co'];
|
||
$ok = false;
|
||
foreach ($allowed as $a) { if (str_ends_with($host, $a)) { $ok = true; break; } }
|
||
if (!$ok) abort(403);
|
||
try {
|
||
$resp = \Illuminate\Support\Facades\Http::timeout(15)
|
||
->withHeaders([
|
||
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||
'Accept' => 'text/vtt,text/plain,*/*',
|
||
'Accept-Language' => 'tr-TR,tr;q=0.9,en;q=0.8',
|
||
'Referer' => 'https://x.anizium.co/',
|
||
])
|
||
->get($url);
|
||
if (!$resp->successful()) abort($resp->status());
|
||
$body = $resp->body();
|
||
// Cloudflare / Anizium hata sayfası dönmüşse (WEBVTT içermiyor) 502
|
||
if (!str_contains(substr($body, 0, 50), 'WEBVTT')) abort(502);
|
||
return response($body, 200)
|
||
->header('Content-Type', 'text/vtt; charset=utf-8')
|
||
->header('Cache-Control', 'public, max-age=3600')
|
||
->header('Access-Control-Allow-Origin', '*');
|
||
} catch (\Exception $e) { abort(502); }
|
||
})->name('vtt.proxy');
|
||
|
||
// ── Anime Mahkemesi ───────────────────────────────────────────────────────────
|
||
Route::get('/mahkeme', [TribunalController::class, 'index'])->name('tribunal.index');
|
||
Route::get('/mahkeme/{tribunal}', [TribunalController::class, 'show'])->name('tribunal.show');
|
||
Route::get('/anime/{anime}/mahkeme', [TribunalController::class, 'forAnime'])->name('tribunal.for-anime');
|
||
Route::middleware('auth')->group(function () {
|
||
Route::post('/mahkeme', [TribunalController::class, 'store'])->name('tribunal.store');
|
||
Route::post('/mahkeme/{tribunal}/oy', [TribunalController::class, 'vote'])->name('tribunal.vote');
|
||
Route::post('/mahkeme/{tribunal}/arguman', [TribunalController::class, 'argue'])->name('tribunal.argue');
|
||
Route::post('/arguman/{argument}/oy', [TribunalController::class, 'argVote'])->name('tribunal.arg-vote');
|
||
});
|
||
|
||
// ── Zaman Kapsülü ─────────────────────────────────────────────────────────────
|
||
Route::middleware('auth')->group(function () {
|
||
Route::get('/kapsullerim', [SocialController::class, 'capsuleIndex'])->name('capsules.index');
|
||
Route::post('/kapsul', [SocialController::class, 'capsuleStore'])->name('capsule.store');
|
||
Route::post('/kapsul/{capsule}/ac', [SocialController::class, 'capsuleOpen'])->name('capsule.open');
|
||
});
|
||
|
||
// ── Spoiler Kilitli Kutu ──────────────────────────────────────────────────────
|
||
Route::get('/episode/{episode}/spoiler-boxes', [SocialController::class, 'spoilerBoxes'])->name('episode.spoiler-boxes');
|
||
Route::middleware('auth')->group(function () {
|
||
Route::post('/episode/{episode}/spoiler-boxes', [SocialController::class, 'spoilerBoxStore'])->name('episode.spoiler-box.store');
|
||
Route::post('/spoiler-box/{box}/like', [SocialController::class, 'spoilerBoxLike'])->name('spoiler-box.like');
|
||
});
|
||
|
||
// ── Ruh Hali Motoru ───────────────────────────────────────────────────────────
|
||
Route::post('/mood/recommend', [SocialController::class, 'moodRecommend'])->name('mood.recommend');
|
||
|
||
// ── Yasal Sayfalar ────────────────────────────────────────────────────────────
|
||
Route::get('/kullanim-kosullari', fn() => view('frontend.legal.terms'))->name('legal.terms');
|
||
Route::get('/gizlilik-politikasi', fn() => view('frontend.legal.privacy'))->name('legal.privacy');
|
||
Route::get('/telif-hakki', fn() => view('frontend.legal.dmca'))->name('legal.dmca');
|