Compare commits

..

15 Commits

Author SHA1 Message Date
w33b 2480c5b309 Redo user comments list 2026-04-18 16:28:50 +02:00
w33b 4fc11d7329 Redo notification list 2026-04-18 15:47:06 +02:00
w33b f3e5100d5d Pint 2026-04-18 14:18:52 +02:00
w33b 5b4d3d435e Display the correct file extension on download button 2026-03-06 22:06:20 +01:00
w33b 564f816fb9 Add matrix nsfw rooms 2026-03-04 14:34:16 +01:00
w33b 3709e378c3 Fix Admin Download Buttons 2026-03-03 21:17:43 +01:00
w33b 4a45dae593 Update Readme 2026-03-01 16:07:09 +01:00
w33b 2b0448d517 Add Disclaimer and Guide for Matrix 2026-03-01 16:06:58 +01:00
w33b 3bb6af73c3 Add matrix 2026-02-18 12:34:10 +01:00
w33b 57cf153560 Update Livewire to fix CorruptComponentPayloadException 2026-02-15 14:52:16 +01:00
w33b e45fd4b148 Update axios 2026-02-15 13:02:08 +01:00
w33b d479369770 Merge branch 'laravel-12' 2026-01-18 18:54:25 +01:00
w33b b8ba17b33f Fix patreon role check (again again) 2026-01-16 15:18:29 +00:00
w33b 5a8dd12cb8 Fix patreon role check (again)
Life without typecast is hard
2026-01-16 15:11:28 +00:00
w33b 3a77c4320d Fix patreon role check
We compare the roles using a strict type check, the json contents from the discord api are strings and not integers.
2026-01-16 14:42:39 +00:00
140 changed files with 1369 additions and 1014 deletions
+7 -7
View File
@@ -2,13 +2,13 @@
## hstream Website
### Install
### Install (Ubuntu)
```bash
# Install PHP
sudo add-apt-repository ppa:ondrej/php
apt update && apt upgrade
apt install php8.3 php8.3-xml php8.3-mysql php8.3-gd php8.3-zip php8.3-curl php8.3-mbstring
apt install php8.4 php8.4-xml php8.4-mysql php8.4-gd php8.4-zip php8.4-curl php8.4-mbstring
# Install NodeJS
curl -sL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh
@@ -22,7 +22,7 @@ mv composer.phar composer
# Install NGINX (skip for local dev)
apt install nginx
apt install php8.3-fpm
apt install php8.4-fpm
# Install MariaDB
apt install mariadb-server
@@ -54,7 +54,7 @@ nano /etc/supervisor/conf.d/laravel-queue.conf :
[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/hstream/artisan queue:work --queue=default --sleep=3 --tries=3 --max-time=3600
command=php84 /var/www/hstream/artisan queue:work --queue=default --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
@@ -83,9 +83,9 @@ zip -r hstream_2023_11_30.zip hstream/
### Update
```bash
php artisan down
php84 artisan down
git pull
npm run build
php artisan view:clear && php artisan optimize:clear && php artisan cache:clear && service php8.4-fpm restart
php artisan up
php84 artisan view:clear && php84 artisan optimize:clear && php84 artisan cache:clear && service php8.4-fpm restart
php84 artisan up
```
+2 -3
View File
@@ -3,11 +3,10 @@
namespace App\Console\Commands;
use App\Models\PopularDaily;
use App\Models\PopularWeekly;
use App\Models\PopularMonthly;
use Illuminate\Support\Carbon;
use App\Models\PopularWeekly;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
class AutoStats extends Command
{
-1
View File
@@ -4,7 +4,6 @@ namespace App\Console\Commands;
use App\Models\Episode;
use App\Models\Hentai;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Spatie\Sitemap\Sitemap;
+1 -2
View File
@@ -2,9 +2,8 @@
namespace App\Console\Commands;
use App\Models\Downloads;
use App\Jobs\GetFileSizeFromCDN;
use App\Models\Downloads;
use Illuminate\Console\Command;
class GetFileSize extends Command
+1 -2
View File
@@ -4,9 +4,8 @@ namespace App\Console\Commands;
use App\Models\User;
use App\Models\UserDownload;
use Illuminate\Support\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
class ResetUserDownloads extends Command
{
+17 -16
View File
@@ -5,21 +5,20 @@ namespace App\Helpers;
use App\Models\Comment;
use App\Models\Episode;
use App\Models\Hentai;
use App\Models\PopularDaily;
use App\Models\PopularMonthly;
use App\Models\PopularWeekly;
use App\Models\PopularDaily;
use Conner\Tagging\Model\Tag;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class CacheHelper
{
public static function getRecentlyReleased(bool $guest)
{
$guestString = $guest ? 'guest' : 'authed';
return Cache::remember("recently_released_".$guestString, now()->addMinutes(60), function () use ($guest) {
return Cache::remember('recently_released_'.$guestString, now()->addMinutes(60), function () use ($guest) {
return Episode::with('gallery')
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
->orderBy('release_date', 'desc')
@@ -31,7 +30,8 @@ class CacheHelper
public static function getRecentlyUploaded(bool $guest)
{
$guestString = $guest ? 'guest' : 'authed';
return Cache::remember("recently_uploaded".$guestString, now()->addMinutes(5), function () use ($guest) {
return Cache::remember('recently_uploaded'.$guestString, now()->addMinutes(5), function () use ($guest) {
return Episode::with('gallery')
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
->orderBy('created_at', 'desc')
@@ -42,21 +42,21 @@ class CacheHelper
public static function getTotalViewCount()
{
return Cache::remember("total_view_count", now()->addMinutes(60), function () {
return Cache::remember('total_view_count', now()->addMinutes(60), function () {
return Episode::sum('view_count');
});
}
public static function getTotalEpisodeCount()
{
return Cache::remember("total_episode_count", now()->addMinutes(60), function () {
return Cache::remember('total_episode_count', now()->addMinutes(60), function () {
return Episode::count();
});
}
public static function getTotalHentaiCount()
{
return Cache::remember("total_hentai_count", now()->addMinutes(60), function () {
return Cache::remember('total_hentai_count', now()->addMinutes(60), function () {
return Hentai::count();
});
}
@@ -64,7 +64,8 @@ class CacheHelper
public static function getPopularAllTime(bool $guest)
{
$guestString = $guest ? 'guest' : 'authed';
return Cache::remember("top_hentai_alltime".$guestString, now()->addMinutes(360), function () use ($guest) {
return Cache::remember('top_hentai_alltime'.$guestString, now()->addMinutes(360), function () use ($guest) {
return Episode::with('gallery')
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
->orderBy('view_count', 'desc')
@@ -75,7 +76,7 @@ class CacheHelper
public static function getPopularMonthly()
{
return Cache::remember("top_hentai_monthly", now()->addMinutes(360), function () {
return Cache::remember('top_hentai_monthly', now()->addMinutes(360), function () {
return PopularMonthly::groupBy('episode_id')
->select('episode_id', DB::raw('count(*) as total'))
->with('episode.gallery')
@@ -87,7 +88,7 @@ class CacheHelper
public static function getPopularWeekly()
{
return Cache::remember("top_hentai_weekly", now()->addMinutes(360), function () {
return Cache::remember('top_hentai_weekly', now()->addMinutes(360), function () {
return PopularWeekly::groupBy('episode_id')
->select('episode_id', DB::raw('count(*) as total'))
->with('episode.gallery')
@@ -100,7 +101,7 @@ class CacheHelper
public static function getPopularDaily()
{
return Cache::remember("top_hentai_daily", now()->addMinutes(30), function () {
return Cache::remember('top_hentai_daily', now()->addMinutes(30), function () {
return PopularDaily::groupBy('episode_id')
->select('episode_id', DB::raw('count(*) as total'))
->with('episode.gallery')
@@ -112,21 +113,21 @@ class CacheHelper
public static function getMostLikes()
{
return Cache::remember("top_likes", now()->addMinutes(30), function () {
return Cache::remember('top_likes', now()->addMinutes(30), function () {
return DB::table('markable_likes')->groupBy('markable_id')->select('markable_id', DB::raw('count(*) as total'))->orderBy('total', 'desc')->limit(16)->get();
});
}
public static function getAllTags()
{
return Cache::remember("all_tags", now()->addMinutes(10080), function () {
return Cache::remember('all_tags', now()->addMinutes(10080), function () {
return Tag::where('count', '>', 0)->orderBy('slug', 'ASC')->get();
});
}
public static function getLatestComments()
{
return Cache::remember("latest_comments", now()->addMinutes(60), function () {
return Cache::remember('latest_comments', now()->addMinutes(60), function () {
return Comment::latest()->take(10)->get();
});
}
+1 -1
View File
@@ -8,7 +8,7 @@ class GitHelper
{
public static function shortCommit()
{
return Cache::remember("git_commit", now()->addMinutes(60), function () {
return Cache::remember('git_commit', now()->addMinutes(60), function () {
try {
return trim(exec('git rev-parse --short HEAD'));
} catch (\Exception $e) {
@@ -15,7 +15,7 @@ class ContactController extends Controller
$contacts = Contact::orderBy('created_at', 'DESC')->get();
return view('admin.contact.index', [
'contacts' => $contacts
'contacts' => $contacts,
]);
}
@@ -3,9 +3,8 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Episode;
use App\Jobs\DiscordReleaseNotification;
use App\Models\Episode;
use App\Services\DownloadService;
use App\Services\EpisodeService;
use App\Services\GalleryService;
@@ -14,7 +13,9 @@ use Illuminate\Http\Request;
class EpisodeController extends Controller
{
protected EpisodeService $episodeService;
protected GalleryService $galleryService;
protected DownloadService $downloadService;
public function __construct(
@@ -43,7 +44,7 @@ class EpisodeController extends Controller
// Discord Alert
if ($request->has('censored')) {
DiscordReleaseNotification::dispatch($referenceEpisode->title." - ".$episodeNumber, 'release-censored');
DiscordReleaseNotification::dispatch($referenceEpisode->title.' - '.$episodeNumber, 'release-censored');
} else {
DiscordReleaseNotification::dispatch($episode->slug, 'release');
}
@@ -51,7 +52,7 @@ class EpisodeController extends Controller
cache()->flush();
return to_route('hentai.index', [
'title' => $episode->slug
'title' => $episode->slug,
]);
}
@@ -87,7 +88,7 @@ class EpisodeController extends Controller
cache()->flush();
return to_route('hentai.index', [
'title' => $episode->slug
'title' => $episode->slug,
]);
}
}
@@ -3,9 +3,8 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Hentai;
use App\Jobs\DiscordReleaseNotification;
use App\Models\Hentai;
use App\Services\DownloadService;
use App\Services\EpisodeService;
use App\Services\GalleryService;
@@ -14,7 +13,9 @@ use Illuminate\Http\Request;
class ReleaseController extends Controller
{
protected EpisodeService $episodeService;
protected GalleryService $galleryService;
protected DownloadService $downloadService;
public function __construct(
@@ -3,15 +3,13 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\SiteBackground;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Intervention\Image\Laravel\Facades\Image;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Laravel\Facades\Image;
class SiteBackgroundController extends Controller
{
@@ -44,7 +42,7 @@ class SiteBackgroundController extends Controller
$bg = SiteBackground::create(array_merge(
$request->only(['date_start', 'date_end']),
[
'default' => (bool) $request->input('default', false)
'default' => (bool) $request->input('default', false),
]
));
@@ -55,13 +53,14 @@ class SiteBackgroundController extends Controller
Image::read($file->getRealPath())
->scaleDown(height: $resolution)
->encode(new WebpEncoder())
->encode(new WebpEncoder)
->save(public_path($targetPath));
}
} catch (\Exception $e) {
DB::rollBack();
Log::error($e->getMessage());
return redirect()->back();
}
@@ -85,7 +84,7 @@ class SiteBackgroundController extends Controller
SiteBackground::where('id', $request->input('id'))->update(array_merge(
$request->only(['date_start', 'date_end']),
[
'default' => (bool) $request->input('default', false)
'default' => (bool) $request->input('default', false),
]
));
@@ -116,6 +115,7 @@ class SiteBackgroundController extends Controller
} catch (\Exception $e) {
DB::rollBack();
Log::error($e->getMessage());
return redirect()->back();
}
@@ -2,10 +2,10 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Episode;
use App\Models\EpisodeSubtitle;
use App\Models\Subtitle;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class SubtitleController extends Controller
@@ -3,8 +3,8 @@
namespace App\Http\Controllers\Admin;
use App\Enums\UserRole;
use App\Models\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
@@ -27,7 +27,6 @@ class UserController extends Controller
'action' => 'required',
]);
$user = User::findOrFail($validated['id']);
switch ($validated['action']) {
@@ -3,10 +3,10 @@
namespace App\Http\Controllers\Api;
use App\Helpers\CacheHelper;
use App\Http\Controllers\Controller;
use App\Models\Episode;
use App\Models\Studios;
use App\Models\Subtitle;
use App\Http\Controllers\Controller;
class AdminApiController extends Controller
{
@@ -2,13 +2,11 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Downloads;
use App\Models\Episode;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use GrantHolle\Altcha\Rules\ValidAltcha;
use Illuminate\Http\Request;
class DownloadApiController extends Controller
{
@@ -2,12 +2,11 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Hentai;
use App\Models\PopularMonthly;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Controller;
class HentaiApiController extends Controller
{
@@ -3,9 +3,7 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Episode;
use Illuminate\Http\Request;
class StreamApiController extends Controller
@@ -33,7 +31,7 @@ class StreamApiController extends Controller
'stream_url' => $episode->dmca_takedown ? 'stuff/dmca' : $episode->url,
'stream_domains' => config('hstream.stream_domain'),
'asia_stream_domains' => config('hstream.asia_stream_domain'),
'extra_subtitles' => $subtitles
'extra_subtitles' => $subtitles,
], 200);
}
}
@@ -4,9 +4,8 @@ namespace App\Http\Controllers\Api;
use App\Helpers\CacheHelper;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Conner\Tagging\Model\Tag;
use Illuminate\Http\Request;
class UserApiController extends Controller
{
@@ -33,11 +32,10 @@ class UserApiController extends Controller
}
}
return response()->json([
'message' => 'success',
'tags' => $tagWhiteList,
'usertags' => $tagBlackList
'usertags' => $tagBlackList,
], 200);
}
}
@@ -3,11 +3,9 @@
namespace App\Http\Controllers\Auth;
use App\Enums\UserRole;
use App\Models\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
@@ -90,6 +88,7 @@ class DiscordAuthController extends Controller
// User is not in the guild
if ($response->status() === 404) {
$user->removeRole(UserRole::SUPPORTER);
return;
}
@@ -114,6 +113,7 @@ class DiscordAuthController extends Controller
if (! $hasSupporterRole) {
// Remove role if not found
$user->removeRole(UserRole::SUPPORTER);
return;
}
@@ -16,8 +16,7 @@ class PasswordController extends Controller
public function update(Request $request): RedirectResponse
{
// If user logged in with Discord and has not yet a password, allow to set password
if ($request->user()->discord_id && is_null($request->user()->password))
{
if ($request->user()->discord_id && is_null($request->user()->password)) {
$validated = $request->validateWithBag('updatePassword', [
'password' => ['required', Password::defaults(), 'confirmed'],
]);
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use GrantHolle\Altcha\Rules\ValidAltcha;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -11,8 +12,6 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use GrantHolle\Altcha\Rules\ValidAltcha;
class RegisteredUserController extends Controller
{
/**
+2 -3
View File
@@ -3,9 +3,8 @@
namespace App\Http\Controllers;
use App\Models\Contact;
use Illuminate\Http\Request;
use GrantHolle\Altcha\Rules\ValidAltcha;
use Illuminate\Http\Request;
class ContactController extends Controller
{
@@ -30,7 +29,7 @@ class ContactController extends Controller
'altcha' => ['required', new ValidAltcha],
]);
$contact = new Contact();
$contact = new Contact;
$contact->name = $request->input('name');
$contact->email = $request->input('email');
$contact->message = $request->input('message');
+3 -5
View File
@@ -2,11 +2,10 @@
namespace App\Http\Controllers;
use App\Models\Episode;
use App\Helpers\CacheHelper;
use App\Models\Episode;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cookie;
class HomeController extends Controller
{
@@ -19,15 +18,14 @@ class HomeController extends Controller
$guestString = $guest ? 'guest' : 'authed';
$mostLikes = \cache()->remember('mostLikes'.$guestString, 300, fn () =>
Episode::with('gallery')
$mostLikes = \cache()->remember('mostLikes'.$guestString, 300, fn () => Episode::with('gallery')
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
->whereIn('id', function ($query) {
$mostLikesIds = CacheHelper::getMostLikes()->pluck('markable_id')->toArray();
$query->selectRaw('id')
->from('episodes')
->whereIn('id', $mostLikesIds)
->orderByRaw("FIELD(id, " . implode(',', $mostLikesIds) . ")");
->orderByRaw('FIELD(id, '.implode(',', $mostLikesIds).')');
})
->get()
);
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\MatrixRegisterRequest;
use App\Services\MatrixRegistrationService;
use Illuminate\Http\Request;
class MatrixController extends Controller
{
/**
* Display the user page.
*/
public function index(Request $request): \Illuminate\View\View
{
$rooms = [
['name' => '🏠 General', 'description' => 'Our main chat.', 'alias' => 'https://matrix.to/#/#general:hstream.moe'],
['name' => '📡 Releases', 'description' => 'Were we @everyone for new releases.', 'alias' => 'https://matrix.to/#/#releases:hstream.moe'],
['name' => '👗 NSFW 2D', 'description' => 'Channel for R18 2D Media.', 'alias' => 'https://matrix.to/#/#nsfw:hstream.moe'],
['name' => '👗 NSFW IRL', 'description' => 'Channel for R18 IRL Media.', 'alias' => 'https://matrix.to/#/#nsfw-irl:hstream.moe'],
];
return view('matrix.index', [
'user' => $request->user(),
'rooms' => $rooms,
]);
}
/**
* Create matrix user
*/
public function store(
MatrixRegisterRequest $request,
MatrixRegistrationService $matrixService
) {
try {
$result = $matrixService->registerUser(
$request->username,
$request->password
);
$user = $request->user();
$user->matrix_id = $result['user_id'];
$user->save();
return redirect()
->back()
->with('success', 'Matrix user created successfully.');
} catch (\Exception $e) {
return back()
->withErrors([
'username' => $e->getMessage(),
])
->withInput();
}
}
}
@@ -3,7 +3,6 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class NotificationController extends Controller
{
+7 -9
View File
@@ -7,7 +7,6 @@ use App\Models\Playlist;
use App\Models\PlaylistEpisode;
use App\Services\PlaylistService;
use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
class PlaylistController extends Controller
{
@@ -42,14 +41,13 @@ class PlaylistController extends Controller
]);
}
/**
* Display the user's playlists page.
*/
public function playlists(Request $request): \Illuminate\View\View
{
$title = 'Delete Playlist!';
$text = "Are you sure you want to delete?";
$text = 'Are you sure you want to delete?';
confirmDelete($title, $text);
return view('profile.playlists', [
@@ -85,7 +83,7 @@ class PlaylistController extends Controller
'name' => 'required|max:30',
]);
$playlist = new Playlist();
$playlist = new Playlist;
$playlist->user_id = $request->user()->id;
$playlist->name = $request->input('name');
$playlist->is_private = $request->input('visiblity') === 'private';
@@ -151,7 +149,7 @@ class PlaylistController extends Controller
$validated = $request->validate([
'playlist' => 'required|max:30',
'episode_id' => 'required'
'episode_id' => 'required',
]);
$playlist = Playlist::where('user_id', $user->id)->where('id', $request->input('playlist'))->firstOrFail();
@@ -161,7 +159,7 @@ class PlaylistController extends Controller
$exists = PlaylistEpisode::where('playlist_id', $playlist->id)->where('episode_id', $episode->id)->exists();
if ($exists) {
return response()->json([
'message' => 'already-added'
'message' => 'already-added',
], 200);
}
@@ -175,7 +173,7 @@ class PlaylistController extends Controller
]);
return response()->json([
'message' => 'success'
'message' => 'success',
], 200);
}
@@ -188,7 +186,7 @@ class PlaylistController extends Controller
'name' => 'required|max:30',
]);
$playlist = new Playlist();
$playlist = new Playlist;
$playlist->user_id = $request->user()->id;
$playlist->name = $request->input('name');
$playlist->is_private = $request->input('visiblity') === 'private';
@@ -196,7 +194,7 @@ class PlaylistController extends Controller
return response()->json([
'message' => 'success',
'playlist_id' => $playlist->id
'playlist_id' => $playlist->id,
], 200);
}
}
+6 -8
View File
@@ -2,22 +2,20 @@
namespace App\Http\Controllers;
use App\Http\Requests\ProfileUpdateRequest;
use App\Models\Episode;
use App\Models\User;
use App\Http\Requests\ProfileUpdateRequest;
use Conner\Tagging\Model\Tag;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Intervention\Image\Laravel\Facades\Image;
use Conner\Tagging\Model\Tag;
class ProfileController extends Controller
{
/**
@@ -121,6 +119,7 @@ class ProfileController extends Controller
if (! $tags) {
$user->tag_blacklist = null;
$user->save();
return Redirect::route('profile.settings')->with('status', 'blacklist-updated');
}
@@ -177,8 +176,8 @@ class ProfileController extends Controller
protected function storeAvatar(\Illuminate\Http\UploadedFile $file, User $user): void
{
// Create Folder for Image Upload
if (! Storage::disk('public')->exists("/images/avatars")) {
Storage::disk('public')->makeDirectory("/images/avatars");
if (! Storage::disk('public')->exists('/images/avatars')) {
Storage::disk('public')->makeDirectory('/images/avatars');
}
// Delete old avatar if it exists
@@ -196,5 +195,4 @@ class ProfileController extends Controller
$user->avatar = $filename;
}
}
+2 -5
View File
@@ -2,20 +2,18 @@
namespace App\Http\Controllers;
use App\Helpers\CacheHelper;
use App\Models\Episode;
use App\Models\Gallery;
use App\Models\Hentai;
use App\Models\Playlist;
use App\Models\PlaylistEpisode;
use App\Models\Watched;
use App\Helpers\CacheHelper;
use hisorange\BrowserDetect\Facade as Browser;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use hisorange\BrowserDetect\Facade as Browser;
class StreamController extends Controller
{
/**
@@ -37,7 +35,6 @@ class StreamController extends Controller
]);
}
$episode = Episode::where('slug', $title)->firstOrFail();
$gallery = Gallery::where('episode_id', $episode->id)->get();
$moreEpisodes = Episode::with(['gallery', 'studio'])->where('hentai_id', $episode->hentai_id)->whereNot('id', $episode->id)->get();
+7 -8
View File
@@ -1,29 +1,28 @@
<?php namespace app\Http\Middleware;
<?php
namespace app\Http\Middleware;
use App\Enums\UserRole;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class IsAdmin {
class IsAdmin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next): Response
{
if(Auth::check() && Auth::user()->hasRole(UserRole::ADMINISTRATOR))
{
if (Auth::check() && Auth::user()->hasRole(UserRole::ADMINISTRATOR)) {
return $next($request);
}
session()->flash('error_msg', 'This resource is restricted to Administrators!');
return redirect()->route('home.index');
}
}
+7 -9
View File
@@ -1,32 +1,30 @@
<?php namespace app\Http\Middleware;
<?php
namespace app\Http\Middleware;
use App\Enums\UserRole;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class IsBanned {
class IsBanned
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next): Response
{
if(Auth::check() && Auth::user()->hasRole(UserRole::BANNED))
{
if (Auth::check() && Auth::user()->hasRole(UserRole::BANNED)) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('home.banned');
}
return $next($request);
}
}
+2 -3
View File
@@ -3,7 +3,6 @@
namespace App\Http\Middleware;
use App\Enums\UserRole;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -18,12 +17,12 @@ class IsModerator
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::check() && Auth::user()->hasRole(UserRole::MODERATOR))
{
if (Auth::check() && Auth::user()->hasRole(UserRole::MODERATOR)) {
return $next($request);
}
session()->flash('error_msg', 'This resource is restricted to Administrators!');
return redirect()->route('home.index');
}
}
+2
View File
@@ -20,12 +20,14 @@ class SetLocale
// 1. Logged-in user preference
if (Auth::check() && Auth::user()->locale) {
App::setLocale(Auth::user()->locale);
return $next($request);
}
// 2. Session (guest or user override)
if (session()->has('locale') && in_array($request->language, config('app.supported_locales'), true)) {
App::setLocale(session('locale'));
return $next($request);
}
+1 -2
View File
@@ -2,6 +2,7 @@
namespace App\Http\Requests\Auth;
use GrantHolle\Altcha\Rules\ValidAltcha;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
@@ -9,8 +10,6 @@ use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use GrantHolle\Altcha\Rules\ValidAltcha;
class LoginRequest extends FormRequest
{
/**
@@ -0,0 +1,50 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MatrixRegisterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
$isOldEnough = $this->user()->created_at->lt(now()->subMonth());
$noAccount = ! $this->user()->matrix_id;
return $isOldEnough && $noAccount;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'username' => [
'required',
'string',
'min:3',
'max:32',
'regex:/^[a-z0-9._=-]+$/', // Valid Matrix localpart
],
'password' => [
'required',
'string',
'min:8',
'confirmed',
],
];
}
public function messages(): array
{
return [
'username.regex' => 'Username may only contain lowercase letters, numbers and . _ = -',
];
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ class ProfileUpdateRequest extends FormRequest
'nullable',
'image',
'mimes:jpg,png,jpeg,webp,gif',
'max:8192'
'max:8192',
],
'email' => [
'required',
+10 -12
View File
@@ -7,7 +7,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Spatie\DiscordAlerts\Facades\DiscordAlert;
class DiscordReleaseNotification implements ShouldQueue
@@ -32,26 +31,25 @@ class DiscordReleaseNotification implements ShouldQueue
*/
public function handle(): void
{
switch($this->messageType)
{
switch ($this->messageType) {
case 'release':
DiscordAlert::message("<@&868457842250764289> (´• ω •`)ノ New **4k** Release! Check it out here: https://hstream.moe/hentai/".$this->slug);
DiscordAlert::message('<@&868457842250764289> (´• ω •`)ノ New **4k** Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
break;
case 'release-censored':
# Because Discord TOS
DiscordAlert::message("<@&868457842250764289> (´• ω •`)ノ New **4k** Release: ".$this->slug." - *No link here because of* :pLoli:");
// Because Discord TOS
DiscordAlert::message('<@&868457842250764289> (´• ω •`)ノ New **4k** Release: '.$this->slug.' - *No link here because of* :pLoli:');
break;
case 'update':
# 1080p 48fps added
DiscordAlert::to('update')->message("<@&1283518462584426598> (´• ω •`)ノ Added **48fps** to Release! Check it out here: https://hstream.moe/hentai/".$this->slug);
// 1080p 48fps added
DiscordAlert::to('update')->message('<@&1283518462584426598> (´• ω •`)ノ Added **48fps** to Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
break;
case 'updateUHD':
# 4k 48fps added
DiscordAlert::to('update')->message("<@&1326860920902778963> (´• ω •`)ノ Added **48fps 4k** to Release! Check it out here: https://hstream.moe/hentai/".$this->slug);
// 4k 48fps added
DiscordAlert::to('update')->message('<@&1326860920902778963> (´• ω •`)ノ Added **48fps 4k** to Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
break;
case 'v2':
# v2 re-release
DiscordAlert::to('rerelease')->message("<@&1425505303075754035> (´• ω •`)ノ **v2 Re-**Release! Check it out here: https://hstream.moe/hentai/".$this->slug);
// v2 re-release
DiscordAlert::to('rerelease')->message('<@&1425505303075754035> (´• ω •`)ノ **v2 Re-**Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
break;
default:
break;
+3 -2
View File
@@ -7,12 +7,12 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Client\RequestException;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class GetFileSizeFromCDN implements ShouldQueue
{
@@ -37,6 +37,7 @@ class GetFileSizeFromCDN implements ShouldQueue
$download = Downloads::find($this->downloadId);
if (! $download) {
Log::error("Download not found for ID: {$this->downloadId}");
return;
}
+1 -2
View File
@@ -5,7 +5,6 @@ namespace App\Livewire;
use App\Models\Comment;
use Livewire\Component;
use Livewire\WithPagination;
use Illuminate\Support\Facades\DB;
class AdminCommentSearch extends Component
{
@@ -41,7 +40,7 @@ class AdminCommentSearch extends Component
->paginate(12);
return view('livewire.admin-comment-search', [
'comments' => $comments
'comments' => $comments,
]);
}
}
+2 -3
View File
@@ -5,10 +5,9 @@ namespace App\Livewire;
use App\Enums\UserRole;
use App\Models\Comment;
use App\Models\User;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
class AdminUserSearch extends Component
{
@@ -46,7 +45,7 @@ class AdminUserSearch extends Component
->paginate(20);
return view('livewire.admin-user-search', [
'users' => $users
'users' => $users,
]);
}
}
+6 -9
View File
@@ -2,12 +2,11 @@
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
use App\Models\SiteBackground;
use Illuminate\Support\Carbon;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
class BackgroundImages extends Component
{
@@ -20,16 +19,14 @@ class BackgroundImages extends Component
{
$now = Carbon::now();
$images = SiteBackground::when($this->filter === 'active', fn ($query) =>
$query->whereDate('date_start', '<=', $now)->whereDate('date_end', '>=', $now)
$images = SiteBackground::when($this->filter === 'active', fn ($query) => $query->whereDate('date_start', '<=', $now)->whereDate('date_end', '>=', $now)
)
->when($this->filter === 'inactive', fn ($query) =>
$query->whereDate('date_start', '>', $now)->orWhereDate('date_end', '<', $now)
->when($this->filter === 'inactive', fn ($query) => $query->whereDate('date_start', '>', $now)->orWhereDate('date_end', '<', $now)
)
->paginate(10);
return view('livewire.background-images', [
'images' => $images
'images' => $images,
]);
}
}
+15 -16
View File
@@ -1,20 +1,16 @@
<?php
namespace App\Livewire;
use App\Models\User;
use App\Models\Episode;
use App\Models\User;
use App\Notifications\CommentNotification;
use Livewire\Component;
use Illuminate\Support\Str;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Str;
use Livewire\Component;
use Maize\Markable\Models\Like;
class Comment extends Component
@@ -30,21 +26,21 @@ class Comment extends Component
public $liked = false;
public $replyState = [
'body' => ''
'body' => '',
];
public $isEditing = false;
public $editState = [
'body' => ''
'body' => '',
];
protected $listeners = [
'refresh' => '$refresh'
'refresh' => '$refresh',
];
protected $validationAttributes = [
'replyState.body' => 'reply'
'replyState.body' => 'reply',
];
public function updatedIsEditing($isEditing)
@@ -54,7 +50,7 @@ class Comment extends Component
}
$this->editState = [
'body' => $this->comment->body
'body' => $this->comment->body,
];
}
@@ -79,7 +75,8 @@ class Comment extends Component
public function postReply()
{
if (! ($this->comment->depth() < 2)) {
$this->addError('replyState.body', "Too many sub comments.");
$this->addError('replyState.body', 'Too many sub comments.');
return;
}
@@ -91,13 +88,14 @@ class Comment extends Component
$seconds = RateLimiter::availableIn($rateLimitKey);
$this->addError('replyState.body', "Too many comments. Try again in {$seconds} seconds.");
return;
}
RateLimiter::hit($rateLimitKey, $rateLimitMinutes);
$this->validate([
'replyState.body' => 'required'
'replyState.body' => 'required',
]);
$reply = $this->comment->children()->make($this->replyState);
@@ -123,7 +121,7 @@ class Comment extends Component
}
$this->replyState = [
'body' => ''
'body' => '',
];
$this->isReplying = false;
@@ -144,6 +142,7 @@ class Comment extends Component
if ($this->liked) {
$this->liked = false;
$this->likeCount--;
return;
}
+8 -8
View File
@@ -2,11 +2,10 @@
namespace App\Livewire;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Component;
use Livewire\WithPagination;
use Illuminate\Support\Facades\RateLimiter;
class Comments extends Component
{
use WithPagination;
@@ -14,21 +13,21 @@ class Comments extends Component
public $model;
public $newCommentState = [
'body' => ''
'body' => '',
];
protected $validationAttributes = [
'newCommentState.body' => 'comment'
'newCommentState.body' => 'comment',
];
protected $listeners = [
'refresh' => '$refresh'
'refresh' => '$refresh',
];
public function postComment()
{
$this->validate([
'newCommentState.body' => 'required'
'newCommentState.body' => 'required',
]);
$user = auth()->user();
@@ -39,6 +38,7 @@ class Comments extends Component
$seconds = RateLimiter::availableIn($rateLimitKey);
$this->addError('newCommentState.body', "Too many comments. Try again in {$seconds} seconds.");
return;
}
@@ -49,7 +49,7 @@ class Comments extends Component
$comment->save();
$this->newCommentState = [
'body' => ''
'body' => '',
];
$this->resetPage();
@@ -65,7 +65,7 @@ class Comments extends Component
->paginate(50);
return view('livewire.comments', [
'comments' => $comments
'comments' => $comments,
]);
}
}
+9
View File
@@ -21,6 +21,15 @@ class DownloadButton extends Component
public $background = 'bg-rose-600';
public $fileExtension = 'HEVC';
public function mount()
{
if (str_contains($this->downloadUrl, 'AV1')) {
$this->fileExtension = 'AV1';
}
}
public function clicked($downloadId)
{
$download = Downloads::find($downloadId);
+3 -3
View File
@@ -5,11 +5,9 @@ namespace App\Livewire;
use App\Models\Episode;
use App\Models\User;
use App\Models\UserDownload;
use Livewire\Component;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class DownloadsFree extends Component
{
@@ -51,6 +49,7 @@ class DownloadsFree extends Component
if (Carbon::parse($alreadyDownloaded->created_at)->addHours(6) <= Carbon::now()) {
// Already expired
$alreadyDownloaded->delete();
return;
}
@@ -65,6 +64,7 @@ class DownloadsFree extends Component
if ($user->downloads_left <= 0) {
// Daily limit reached
$this->granted = 3;
return;
}
+5 -2
View File
@@ -3,9 +3,9 @@
namespace App\Livewire;
use App\Models\Downloads;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
class DownloadsSearch extends Component
{
@@ -25,6 +25,7 @@ class DownloadsSearch extends Component
#[Url(history: true)]
public $studios = [];
public $studiosCopy = [];
// To toggle individual option selection
@@ -144,7 +145,9 @@ class DownloadsSearch extends Component
$downloads = Downloads::when($this->fileSearch != '', fn ($query) => $query->where('url', 'like', '%'.$this->fileSearch.'%'))
->whereIn('type', $this->getSelectedTypes())
->when($this->studios !== [], fn ($q) => $q->whereHas('episode', fn ($query) => $query->whereHas('studio', function ($query) { $query->whereIn('slug', $this->studios); })))
->when($this->studios !== [], fn ($q) => $q->whereHas('episode', fn ($query) => $query->whereHas('studio', function ($query) {
$query->whereIn('slug', $this->studios);
})))
->whereNotNull('size')
->orderBy($orderby, $orderdirection)
->paginate(20);
+2 -2
View File
@@ -4,10 +4,9 @@ namespace App\Livewire;
use App\Models\Episode;
use App\Models\User;
use Livewire\Component;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
use Maize\Markable\Models\Like;
class LikeButton extends Component
@@ -52,6 +51,7 @@ class LikeButton extends Component
if ($this->liked) {
$this->liked = false;
$this->likeCount--;
return;
}
+11 -4
View File
@@ -3,10 +3,10 @@
namespace App\Livewire;
use App\Models\Episode;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
use Illuminate\Support\Facades\Auth;
class LiveSearch extends Component
{
@@ -20,14 +20,17 @@ class LiveSearch extends Component
#[Url(history: true)]
public $tags = [];
public $tagsCopy = [];
#[Url(history: true)]
public $studios = [];
public $studiosCopy = [];
#[Url(history: true)]
public $blacklist = [];
public $blacklistCopy = [];
#[Url(history: true)]
@@ -118,10 +121,14 @@ class LiveSearch extends Component
}
$user_id = Auth::check() ? auth()->user()->id : 0;
$episodes = Episode::with('gallery')->when($this->search != '', fn ($query) => $query->where(function($query) { $query->where('title', 'like', '%'.$this->search.'%')->orWhere('title_search', 'like', '%'.$this->search.'%')->orWhere('title_jpn', 'like', '%'.$this->search.'%'); }))
$episodes = Episode::with('gallery')->when($this->search != '', fn ($query) => $query->where(function ($query) {
$query->where('title', 'like', '%'.$this->search.'%')->orWhere('title_search', 'like', '%'.$this->search.'%')->orWhere('title_jpn', 'like', '%'.$this->search.'%');
}))
->when($this->tags !== [], fn ($query) => $query->withAllTags($this->tags))
->when($this->blacklist !== [], fn ($query) => $query->withoutTags($this->blacklist))
->when($this->studios !== [], fn ($query) => $query->whereHas('studio', function ($query) { $query->whereIn('slug', $this->studios); }))
->when($this->studios !== [], fn ($query) => $query->whereHas('studio', function ($query) {
$query->whereIn('slug', $this->studios);
}))
->when($this->hideWatched !== [] && Auth::check(), fn ($query) => $query->whereDoesntHave('watched', function ($query) use ($user_id) {
$query->where('user_id', $user_id);
}))
+1 -1
View File
@@ -3,8 +3,8 @@
namespace App\Livewire;
use App\Models\Episode;
use Livewire\Component;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class NavLiveSearch extends Component
{
+3 -5
View File
@@ -5,13 +5,11 @@ namespace App\Livewire;
use App\Models\Playlist;
use App\Models\PlaylistEpisode;
use App\Services\PlaylistService;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Collection;
class PlaylistOverview extends Component
{
+2 -3
View File
@@ -3,10 +3,9 @@
namespace App\Livewire;
use App\Models\Playlist;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
class Playlists extends Component
{
@@ -59,7 +58,7 @@ class Playlists extends Component
->paginate($this->pagination);
return view('livewire.playlists', [
'playlists' => $playlists
'playlists' => $playlists,
]);
}
}
+2 -4
View File
@@ -3,7 +3,6 @@
namespace App\Livewire;
use App\Models\Comment;
use Livewire\Component;
use Livewire\WithPagination;
@@ -36,14 +35,13 @@ class UserComments extends Component
$orderdirection = 'desc';
}
$comments = Comment::where('user_id', $this->model->id)
->when($this->commentSearch != '', fn ($query) => $query->where('body', 'like', '%'.$this->commentSearch.'%'))
->orderBy($orderby, $orderdirection)
->paginate(10);
->paginate(6);
return view('livewire.user-comments', [
'comments' => $comments
'comments' => $comments,
]);
}
}
+11 -4
View File
@@ -3,10 +3,10 @@
namespace App\Livewire;
use App\Models\Episode;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
use Illuminate\Support\Facades\Auth;
class UserLikes extends Component
{
@@ -20,14 +20,17 @@ class UserLikes extends Component
#[Url(history: true)]
public $tags = [];
public $tagsCopy = [];
#[Url(history: true)]
public $studios = [];
public $studiosCopy = [];
#[Url(history: true)]
public $blacklist = [];
public $blacklistCopy = [];
#[Url(history: true)]
@@ -119,10 +122,14 @@ class UserLikes extends Component
$user_id = Auth::check() ? auth()->user()->id : 0;
$episodes = Episode::whereHasLike(auth()->user())
->when($this->search !== '', fn ($query) => $query->where(function($query) { $query->where('title', 'like', '%'.$this->search.'%')->orWhere('title_search', 'like', '%'.$this->search.'%')->orWhere('title_jpn', 'like', '%'.$this->search.'%'); }))
->when($this->search !== '', fn ($query) => $query->where(function ($query) {
$query->where('title', 'like', '%'.$this->search.'%')->orWhere('title_search', 'like', '%'.$this->search.'%')->orWhere('title_jpn', 'like', '%'.$this->search.'%');
}))
->when($this->tags !== [], fn ($query) => $query->withAllTags($this->tags))
->when($this->blacklist !== [], fn ($query) => $query->withoutTags($this->blacklist))
->when($this->studios !== [], fn ($query) => $query->whereHas('studio', function ($query) { $query->whereIn('slug', $this->studios); }))
->when($this->studios !== [], fn ($query) => $query->whereHas('studio', function ($query) {
$query->whereIn('slug', $this->studios);
}))
->when($this->hideWatched !== [] && Auth::check(), fn ($query) => $query->whereDoesntHave('watched', function ($query) use ($user_id) {
$query->where('user_id', $user_id);
}))
-1
View File
@@ -3,7 +3,6 @@
namespace App\Livewire;
use App\Models\Watched as UserWatched;
use App\Models\User;
use Illuminate\Support\Carbon;
use Livewire\Component;
use Livewire\WithPagination;
+5 -6
View File
@@ -4,19 +4,18 @@ namespace App\Models;
use App\Models\Presenters\CommentPresenter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Maize\Markable\Markable;
use Maize\Markable\Models\Like;
class Comment extends Model
{
use HasFactory, SoftDeletes, Markable;
use HasFactory, Markable, SoftDeletes;
protected static $marks = [
Like::class
Like::class,
];
/**
@@ -25,7 +24,7 @@ class Comment extends Model
* @var string[]
*/
protected $fillable = [
'body'
'body',
];
public function presenter()
+13 -18
View File
@@ -2,35 +2,28 @@
namespace App\Models;
use App\Models\Downloads;
use App\Models\PopularMonthly;
use App\Models\PopularWeekly;
use App\Models\PopularDaily;
use Conner\Tagging\Taggable;
use Laravel\Scout\Searchable;
use Maize\Markable\Markable;
use Maize\Markable\Models\Like;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Laravel\Scout\Searchable;
use Maize\Markable\Markable;
use Maize\Markable\Models\Like;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
class Episode extends Model implements Sitemapable
{
use Markable, Taggable;
use HasFactory;
use Markable, Taggable;
use Searchable;
protected static $marks = [
Like::class
Like::class,
];
/**
@@ -180,6 +173,7 @@ class Episode extends Model implements Sitemapable
$problematicResults .= $pTag;
}
return $problematicResults;
}
@@ -234,9 +228,10 @@ class Episode extends Model implements Sitemapable
return $this->hasMany(Watched::class);
}
public function getDownloadByType(string $type): Downloads | null
public function getDownloadByType(string $type): ?Downloads
{
$cacheKey = "episode_{$this->id}_download_{$type}";
return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($type) {
return $this->downloads()->where('type', $type)->first();
});
+7 -8
View File
@@ -2,19 +2,18 @@
namespace App\Models;
use Conner\Tagging\Taggable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Conner\Tagging\Taggable;
class Hentai extends Model implements Sitemapable
{
use Taggable;
use HasFactory;
use Taggable;
/**
* The attributes that are mass assignable.
@@ -31,7 +30,7 @@ class Hentai extends Model implements Sitemapable
return $this->hasMany(Episode::class, 'hentai_id');
}
public function title(): String
public function title(): string
{
return $this->episodes->first()->title;
}
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Playlist extends Model
{
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PlaylistEpisode extends Model
{
+1 -2
View File
@@ -4,7 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
class SiteBackground extends Model
{
@@ -16,7 +15,7 @@ class SiteBackground extends Model
protected $fillable = [
'date_start',
'date_end',
'default'
'default',
];
/**
+1 -1
View File
@@ -3,8 +3,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Studios extends Model
{
+4 -8
View File
@@ -5,13 +5,12 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Enums\UserRole;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class User extends Authenticatable
{
@@ -62,7 +61,6 @@ class User extends Authenticatable
'discord_avatar' => 'string',
];
/**
* Has Many Playlists.
*/
@@ -108,13 +106,11 @@ class User extends Authenticatable
*/
public function getAvatar(): string
{
if ($this->discord_id && $this->discord_avatar && !$this->avatar)
{
if ($this->discord_id && $this->discord_avatar && ! $this->avatar) {
return "https://external-content.duckduckgo.com/iu/?u={$this->discord_avatar}";
}
if ($this->avatar)
{
if ($this->avatar) {
return Storage::url($this->avatar);
}
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class UserDownload extends Model
{
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Watched extends Model
{
+2 -2
View File
@@ -3,8 +3,6 @@
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class CommentNotification extends Notification
@@ -12,7 +10,9 @@ class CommentNotification extends Notification
use Queueable;
protected $type;
protected $message;
protected $url;
/**
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Policies;
use App\Models\User;
use App\Models\Comment;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class CommentPolicy
+1 -3
View File
@@ -2,11 +2,9 @@
namespace App\Services;
use App\Jobs\GetFileSizeFromCDN;
use App\Models\Downloads;
use App\Models\Episode;
use App\Jobs\GetFileSizeFromCDN;
use Illuminate\Http\Request;
class DownloadService
+7 -10
View File
@@ -5,14 +5,12 @@ namespace App\Services;
use App\Models\Episode;
use App\Models\Hentai;
use App\Models\Studios;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Laravel\Facades\Image;
use Illuminate\Support\Str;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Laravel\Facades\Image;
class EpisodeService
{
@@ -24,6 +22,7 @@ class EpisodeService
if (is_numeric($lastPart) && $lastPart < 1000) {
$slugParts[array_key_last($slugParts)] = 's'.$lastPart;
return implode('-', $slugParts);
}
@@ -36,11 +35,10 @@ class EpisodeService
int $episodeNumber,
?Studios $studio = null,
?Episode $referenceEpisode = null
): Episode
{
$episode = new Episode();
): Episode {
$episode = new Episode;
$episode->title = $referenceEpisode->title ?? $request->input('title');
$episode->title_search = preg_replace("/[^A-Za-z0-9 ]/", '', $episode->title);
$episode->title_search = preg_replace('/[^A-Za-z0-9 ]/', '', $episode->title);
$episode->title_jpn = $referenceEpisode->title_jpn ?? $request->input('title_jpn');
$episode->slug = "{$hentai->slug}-{$episodeNumber}";
$episode->hentai_id = $hentai->id;
@@ -96,7 +94,6 @@ class EpisodeService
);
}
public function createOrUpdateCover(Request $request, Episode $episode, string $slug, int $episodeNumber): void
{
if (! $request->hasFile("episodecover{$episodeNumber}")) {
@@ -111,7 +108,7 @@ class EpisodeService
// Encode and save cover image
Image::read($request->file("episodecover{$episodeNumber}")->getRealPath())
->cover(268, 394)
->encode(new WebpEncoder())
->encode(new WebpEncoder)
->save(Storage::disk('public')->path($episode->cover_url));
}
}
+4 -8
View File
@@ -5,17 +5,13 @@ namespace App\Services;
use App\Models\Episode;
use App\Models\Gallery;
use App\Models\Hentai;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Laravel\Facades\Image;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Laravel\Facades\Image;
class GalleryService
{
public function createOrUpdateGallery(Request $request, Hentai $hentai, Episode $episode, int $episodeNumber, bool $override = false): void
{
$galleryInputNumber = $override ? 1 : $episodeNumber;
@@ -45,7 +41,7 @@ class GalleryService
private function createGallery(Hentai $hentai, Episode $episode, int $episodeNumber, int $counter): Gallery
{
$gallery = new Gallery();
$gallery = new Gallery;
$gallery->hentai_id = $hentai->id;
$gallery->episode_id = $episode->id;
$gallery->image_url = "/images/hentai/{$hentai->slug}/gallery-ep-{$episodeNumber}-{$counter}.webp";
@@ -59,12 +55,12 @@ class GalleryService
{
Image::read($sourceImage->getRealPath())
->cover(1920, 1080)
->encode(new WebpEncoder())
->encode(new WebpEncoder)
->save(Storage::disk('public')->path($gallery->image_url));
Image::read($sourceImage->getRealPath())
->cover(960, 540)
->encode(new WebpEncoder())
->encode(new WebpEncoder)
->save(Storage::disk('public')->path($gallery->thumbnail_url));
}
@@ -0,0 +1,49 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class MatrixRegistrationService
{
public function registerUser(string $username, string $password)
{
$server = config('services.matrix.server');
$secret = config('services.matrix.shared_secret');
// Get nonce from Synapse
$nonceResponse = Http::get("$server/_synapse/admin/v1/register");
if (! $nonceResponse->ok()) {
throw new \Exception('Could not fetch nonce from Matrix.');
}
$nonce = $nonceResponse->json()['nonce'];
// Generate MAC
$mac = hash_hmac(
'sha1',
$nonce."\0".
$username."\0".
$password."\0".
'notadmin',
$secret
);
// Send registration request
$response = Http::post("$server/_synapse/admin/v1/register", [
'nonce' => $nonce,
'username' => $username,
'password' => $password,
'admin' => false,
'mac' => $mac,
]);
if ($response->failed()) {
$error = $response->json()['error'] ?? $response->body();
throw new \Exception($error);
}
return $response->json();
}
}
Generated
+8 -8
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2e359f5cfd56c822e336b74becc9c9d9",
"content-hash": "3400111a6254560d548639295422875c",
"packages": [
{
"name": "altcha-org/altcha",
@@ -3036,16 +3036,16 @@
},
{
"name": "livewire/livewire",
"version": "v3.7.3",
"version": "v3.7.10",
"source": {
"type": "git",
"url": "https://github.com/livewire/livewire.git",
"reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c"
"reference": "0dc679eb4c8b4470cb12522b5927ef08ca2358bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/livewire/livewire/zipball/a5384df9fbd3eaf02e053bc49aabc8ace293fc1c",
"reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c",
"url": "https://api.github.com/repos/livewire/livewire/zipball/0dc679eb4c8b4470cb12522b5927ef08ca2358bb",
"reference": "0dc679eb4c8b4470cb12522b5927ef08ca2358bb",
"shasum": ""
},
"require": {
@@ -3100,7 +3100,7 @@
"description": "A front-end framework for Laravel.",
"support": {
"issues": "https://github.com/livewire/livewire/issues",
"source": "https://github.com/livewire/livewire/tree/v3.7.3"
"source": "https://github.com/livewire/livewire/tree/v3.7.10"
},
"funding": [
{
@@ -3108,7 +3108,7 @@
"type": "github"
}
],
"time": "2025-12-19T02:00:29+00:00"
"time": "2026-02-09T22:49:33+00:00"
},
{
"name": "maize-tech/laravel-markable",
@@ -11584,5 +11584,5 @@
"php": "^8.2"
},
"platform-dev": {},
"plugin-api-version": "2.9.0"
"plugin-api-version": "2.6.0"
}
+1 -1
View File
@@ -12,7 +12,7 @@ return [
'803325441942356059', // Tier-3
'803322725576736858', // Tier-2
'802270568912519198', // Tier-1
'802234830384267315' // admin
'802234830384267315', // admin
],
'discord_bot_token' => env('DISCORD_BOT_TOKEN'),
+2 -2
View File
@@ -30,11 +30,11 @@ return [
// Asia Fallback (HTTP)
'asia_download_domain' => [
'https://komako-b-ddl.musume-h.xyz'
'https://komako-b-ddl.musume-h.xyz',
],
'asia_stream_domain' => [
'https://komako-b-str.musume-h.xyz'
'https://komako-b-str.musume-h.xyz',
],
// Free 4k Downloads
+1 -1
View File
@@ -38,5 +38,5 @@ return [
'autoOrientation' => true,
'decodeAnimation' => true,
'blendingColor' => 'ffffff',
]
],
];
+2 -2
View File
@@ -149,12 +149,12 @@ return [
'title_jpn',
'slug',
'description',
'tags'
'tags',
],
'sortableAttributes' => [
'created_at',
'release_date',
'title'
'title',
],
],
],
+7 -1
View File
@@ -47,5 +47,11 @@ return [
'avatar_default_extension' => env('DISCORD_EXTENSION_DEFAULT', 'webp'), // only pick from jpg, png, webp
],
/**
* Matrix Registration
*/
'matrix' => [
'server' => env('MATRIX_SERVER'),
'shared_secret' => env('MATRIX_SHARED_SECRET'),
],
];
-1
View File
@@ -263,5 +263,4 @@ return [
'confirm_delete_icon' => env('SWEET_ALERT_CONFIRM_DELETE_ICON', 'warning'),
'confirm_delete_show_loader_on_confirm' => env('SWEET_ALERT_CONFIRM_DELETE_SHOW_LOADER_ON_CONFIRM', true),
];
-1
View File
@@ -2,7 +2,6 @@
namespace Database\Factories;
use App\Models\Studios;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
@@ -5,8 +5,8 @@ use App\Models\Hentai;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
@@ -22,8 +22,7 @@ return new class extends Migration
*/
public function down()
{
Schema::table('gallery', function (Blueprint $table)
{
Schema::table('gallery', function (Blueprint $table) {
$table->dropIndex(['episode_id', 'hentai_id']);
});
}
@@ -2,8 +2,8 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
@@ -29,7 +29,7 @@ return new class extends Migration
Downloads::create([
'episode_id' => $episode->id,
'type' => 'FHD',
'url' => $episode->download_url
'url' => $episode->download_url,
]);
}
@@ -38,7 +38,7 @@ return new class extends Migration
Downloads::create([
'episode_id' => $episode->id,
'type' => 'FHDi',
'url' => $episode->download_url_interpolated
'url' => $episode->download_url_interpolated,
]);
}
@@ -47,7 +47,7 @@ return new class extends Migration
Downloads::create([
'episode_id' => $episode->id,
'type' => 'UHD',
'url' => $episode->download_url_4k
'url' => $episode->download_url_4k,
]);
}
}
@@ -2,7 +2,6 @@
use App\Models\Episode;
use App\Models\Hentai;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
@@ -18,7 +18,7 @@ return new class extends Migration
// Update the title_search field with sanitized titles
DB::table('episodes')->update([
'title_search' => DB::raw("REGEXP_REPLACE(title, '[^A-Za-z0-9 ]', '')")
'title_search' => DB::raw("REGEXP_REPLACE(title, '[^A-Za-z0-9 ]', '')"),
]);
}
@@ -2,8 +2,8 @@
use App\Models\Playlist;
use App\Models\PlaylistEpisode;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
@@ -1,11 +1,10 @@
<?php
use App\Models\Downloads;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
@@ -14,10 +13,10 @@ return new class extends Migration
*/
public function up(): void
{
# Delete entries with "#" as URL
// Delete entries with "#" as URL
Downloads::where('url', '#')->delete();
# Remove duplicate entries
// Remove duplicate entries
$duplicates = DB::table('downloads')
->select('episode_id', 'type', DB::raw('COUNT(*) as count'))
->groupBy('episode_id', 'type')
@@ -1,7 +1,6 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@@ -46,7 +46,6 @@ return new class extends Migration
$table->rememberToken()->after('password');
});
/**
* --------------------------------------------------------------------
* Fix Discord Profile Pictures
@@ -54,8 +53,7 @@ return new class extends Migration
* The oauth package by socialite now returns a full url of the avatar.
* Meaning all the old entries have to be fixed.
*/
foreach (User::whereNotNull('discord_avatar')->get() as $user)
{
foreach (User::whereNotNull('discord_avatar')->get() as $user) {
$isGif = preg_match('/a_.+/m', $user->discord_avatar) === 1;
$extension = $isGif ? 'gif' : 'webp';
$user->discord_avatar = sprintf('https://cdn.discordapp.com/avatars/%s/%s.%s', $user->id, $user->discord_avatar, $extension);
@@ -3,8 +3,8 @@
use App\Models\Playlist;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
@@ -69,7 +69,7 @@ return new class extends Migration
// Re-Add foreign key constraint and index
Schema::table('comments', function (Blueprint $table) {
$table->foreign('child_id')->references('id')->on('comments')->onDelete('cascade');
$table->index(["commenter_id", "commenter_type"]);
$table->index(['commenter_id', 'commenter_type']);
});
}
};
@@ -14,17 +14,17 @@ return new class extends Migration
{
// Migrate supporters
DB::table('users')->where('is_patreon', 1)->update([
'roles' => DB::raw("JSON_ARRAY('supporter')")
'roles' => DB::raw("JSON_ARRAY('supporter')"),
]);
// Migrate banned
DB::table('users')->where('is_banned', 1)->update([
'roles' => DB::raw("JSON_ARRAY('banned')")
'roles' => DB::raw("JSON_ARRAY('banned')"),
]);
// Migrate admins
DB::table('users')->where('is_admin', 1)->update([
'roles' => DB::raw("JSON_ARRAY('admin')")
'roles' => DB::raw("JSON_ARRAY('admin')"),
]);
// Drop columns
@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('matrix_id')
->nullable()
->after('discord_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('matrix_id');
});
}
};
+1 -1
View File
@@ -13,7 +13,7 @@ class DatabaseSeeder extends Seeder
public function run(): void
{
$this->call([
DeletedUserSeeder::class
DeletedUserSeeder::class,
]);
}
}
+1 -3
View File
@@ -3,11 +3,9 @@
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DeletedUserSeeder extends Seeder
{
+6 -5
View File
@@ -4,6 +4,7 @@
"requires": true,
"packages": {
"": {
"name": "hstream",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1",
"@jellyfin/libass-wasm": "^4.1.1",
@@ -1263,14 +1264,14 @@
}
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
+3
View File
@@ -0,0 +1,3 @@
{
"preset": "laravel"
}
@@ -49,6 +49,22 @@
<i class="fa-brands fa-discord"></i> {{ __('nav.our-discord-server') }}
</x-dropdown-link>
<x-dropdown-link :href="route('join.matrix')">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-brand-matrix pr-1">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M4 3h-1v18h1" />
<path d="M20 21h1v-18h-1" />
<path d="M7 9v6" />
<path d="M12 15v-3.5a2.5 2.5 0 1 0 -5 0v.5" />
<path d="M17 15v-3.5a2.5 2.5 0 1 0 -5 0v.5" />
</svg>
Join our Matrix
</div>
</x-dropdown-link>
<x-dropdown-link>
<div class="grid grid-cols-2">
<p class="cursor-default">{{ __('nav.theme') }}</p>
@@ -12,7 +12,7 @@
@else
<p class="text-lg">Episode {{ $episodeNumber }}</p>
@endif
<p class="text-xs">HEVC MKV {{ $fileSize ?? '' }}</p>
<p class="text-xs">{{ $fileExtension }} MKV {{ $fileSize ?? '' }}</p>
<p class="text-xs" id="count-{{ $downloadId }}">Downloaded {{ $downloadCount }} times</p>
</div>
</button>
@@ -235,6 +235,11 @@
'/' .
$expire;
}
$fileExtension = "HEVC";
if (str_contains($download->url, 'AV1')) {
$fileExtension = "AV1";
}
@endphp
<a href="{{ $downloadURL }}" wire:click="clicked({{ $download->id }})"
download>
@@ -249,7 +254,7 @@
</svg>
</div>
<div class="flex flex-col text-center w-full">
<p class="text-xs">HEVC MKV</p>
<p class="text-xs">{{ $fileExtension }} MKV</p>
</div>
</button>
</a>
@@ -1,4 +1,10 @@
<a href="{{ $dldomains[array_rand($dldomains)] }}/{{ $hdl->getDownloadByType('FHD')->url }}">
@php
$fileExtension = "HEVC";
if (str_contains($hdl->getDownloadByType('FHD')->url, 'AV1')) {
$fileExtension = "AV1";
}
@endphp
<button class="group rounded-md shadow bg-rose-600 text-white cursor-pointer flex justify-between items-center overflow-hidden transition-all hover:glow m-1 w-[150px]">
<div class="relative w-12 h-12 bg-white bg-opacity-20 text-white flex justify-center items-center transition-all"><svg id="arrow" class="w-4 h-4 transition-all group-hover:-translate-y-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
@@ -7,7 +13,7 @@
</div>
<div class="w-32 text-center">
<p class="px-5 text-sm row-span-2">Episode {{ $hdl->episode }}</p>
<p class="px-5 text-xs">HEVC MKV</p>
<p class="px-5 text-xs">{{ $fileExtension }} MKV</p>
</div>
</button>
</a>
@@ -1,130 +1,101 @@
<div class="py-3 relative">
<div class="mx-auto sm:px-6 lg:px-8 space-y-6 max-w-[100%] lg:max-w-[90%] xl:max-w-[80%] 2xl:max-w-[90%] relative z-10">
<!-- Search Filter -->
<div class="p-4 sm:p-8 bg-white/30 dark:bg-neutral-950/40 shadow sm:rounded-lg backdrop-blur relative z-100">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 ">
<!-- Title -->
<div>
<label for="live-search"
class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Search</label>
<div class="relative right-2 left-0 sm:left-2 transition-all">
<div class="absolute inset-y-0 left-2 flex items-center pl-3 pointer-events-none">
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
</svg>
</div>
<div class="mx-auto max-w-5xl px-4 space-y-6">
<input wire:model.live.debounce.600ms="commentSearch" type="search" id="live-search"
class="block w-full p-4 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-rose-800 focus:border-rose-900 dark:bg-neutral-900 dark:border-neutral-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-rose-800 dark:focus:border-rose-900"
placeholder="Search comment..." required>
<!-- Filters -->
<div class="p-4 bg-white/40 dark:bg-neutral-950/40 backdrop-blur rounded-xl shadow flex flex-col sm:flex-row gap-3">
<div class="absolute right-0 top-[11px]" wire:loading>
<svg aria-hidden="true"
class="inline w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-pink-600"
viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor" />
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill" />
<!-- Search -->
<div class="relative flex-1">
<input
wire:model.live.debounce.500ms="commentSearch"
type="search"
placeholder="Search comments..."
class="w-full pl-10 pr-4 py-3 rounded-lg border-neutral-300 dark:text-neutral-300 bg-white/80 dark:bg-neutral-900/50 dark:border-neutral-700 focus:outline-none focus:ring-2 focus:ring-rose-600 focus:border-rose-700 transition"
>
<div class="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center">
<svg class="w-4 h-4 text-gray-400 dark:text-gray-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
</svg>
</div>
</div>
</div>
<!-- Ordering -->
<div>
<div class="relative right-2 left-0 sm:left-2 transition-all">
<div class="absolute inset-y-0 left-2 flex items-center pl-3 pointer-events-none">
<i class="fa-solid fa-sort text-gray-500 dark:text-gray-400"></i>
</div>
<select wire:model.live="order"
class="block w-full p-4 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-rose-800 focus:border-rose-900 dark:bg-neutral-900 dark:border-neutral-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-rose-800 dark:focus:border-rose-900">
<option value="created_at_desc">Created DESC</option>
<option value="created_at_asc">Created ASC</option>
<!-- Order -->
<select
wire:model.live="order"
class="px-4 py-3 rounded-lg border-neutral-300 dark:text-gray-300 bg-white/80 dark:bg-neutral-900/50 dark:border-neutral-700 min-w-[128px]"
>
<option value="created_at_desc">Newest</option>
<option value="created_at_asc">Oldest</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div
class="relative pt-5 mx-auto sm:px-6 lg:px-8 space-y-6 text-gray-900 dark:text-white max-w-[100%] lg:max-w-[90%] xl:max-w-[80%] 2xl:max-w-[90%] 2xl:w-[50vw]">
<div class="flex flex-col">
<div class="overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 sm:px-6 lg:px-8">
<div class="overflow-hidden">
<!-- Desktop -->
<div class="w-full text-left text-sm font-light">
<!-- Header -->
<div
class="flex bg-white/30 dark:bg-neutral-950/40 backdrop-blur font-medium dark:border-neutral-500 border-b rounded-tl-lg rounded-tr-lg">
<!-- Comments -->
<div class="space-y-4">
@forelse ($comments as $comment)
<div class="flex-1 px-6 py-4 text-center">Comment</div>
</div>
<!-- Rows -->
@foreach ($comments as $comment)
<div wire:key="comment-{{ $comment->id }}"
class="flex flex-col sm:flex-row items-center border-b bg-white dark:bg-neutral-950 dark:border-zinc-700 hover:bg-zinc-100 dark:hover:bg-neutral-800">
<!-- Image -->
<div class="flex w-fit sm:w-56">
@if($comment->commentable_type == \App\Models\Episode::class)
@php $episode = \App\Models\Episode::find($comment->commentable_id); @endphp
<div class="relative p-1 w-full transition duration-300 ease-in-out md:p-2 md:hover:-translate-y-1 md:hover:scale-110">
<a href="{{ route('hentai.index', ['title' => $episode->slug]) }}">
<img alt="{{ $episode->title }} - {{ $episode->episode }}" loading="lazy" width="1000"
class="block object-cover object-center relative z-20 rounded-lg aspect-video"
src="{{ $episode->gallery->first()->thumbnail_url }}" />
<p class="absolute left-1 md:left-2 bottom-1 md:bottom-2 bg-rose-700/70 !text-white rounded-bl-lg rounded-tr-lg p-1 pr-2 pl-2 font-semibold text-sm z-30">
<i class="fa-regular fa-eye"></i> {{ $episode->viewCountFormatted() }}
<i class="fa-regular fa-heart"></i> {{ $episode->likeCount() }}
<i class="fa-regular fa-comment"></i> {{ $episode->commentCount() }}
</p>
</a>
</div>
@elseif($comment->commentable_type == \App\Models\Hentai::class)
@php
$hentai = \App\Models\Hentai::find($comment->commentable_id);
$episode = $hentai->episodes->first();
$model = $comment->commentable;
$episode = $model instanceof \App\Models\Episode
? $model
: $model->episodes->first();
$url = route('hentai.index', ['title' => $model->slug]);
@endphp
<div class="relative p-1 w-full transition duration-300 ease-in-out md:p-2 md:hover:-translate-y-1 md:hover:scale-110">
<a href="{{ route('hentai.index', ['title' => $hentai->slug]) }}">
<img alt="{{ $episode->title }}" loading="lazy" width="1000"
class="block object-cover object-center relative z-20 rounded-lg aspect-video"
src="{{ $episode->gallery->first()->thumbnail_url }}" />
<a href="{{ $url }}#comment-{{ $comment->id }}"
wire:key="comment-{{ $comment->id }}"
class="block group">
</a>
</div>
@endif
<div class="bg-white/40 dark:bg-neutral-950/40 backdrop-blur rounded-xl shadow hover:shadow-lg transition overflow-hidden">
<div class="flex flex-col sm:flex-row">
<!-- Thumbnail -->
<div class="sm:w-48 shrink-0">
<img
src="{{ $episode->gallery->first()->thumbnail_url }}"
alt=""
class="w-full h-40 sm:h-full object-cover"
>
</div>
<!-- Body -->
<div class="flex text-lg flex-1 items-center space-x-2 px-3 py-2 bg-neutral-200 dark:bg-neutral-900 h-[115px] rounded-lg sm:mr-2">
<!-- Content -->
<div class="flex-1 p-4 flex flex-col justify-between">
<!-- Comment -->
<div class="text-gray-800 dark:text-gray-200 text-sm line-clamp-3">
{!! $comment->presenter()->markdownBody() !!}
</div>
<div class="space-x-2 sm:mr-2 w-24">
<span class="text-gray-500 dark:text-gray-300 font-medium">
<!-- Meta -->
<div class="flex items-center justify-between mt-3 text-xs text-gray-700 dark:text-gray-400">
<span>
{{ $comment->presenter()->relativeCreatedAt() }}
</span>
<span class="text-rose-600 font-medium group-hover:underline">
View comment
</span>
</div>
</div>
@endforeach
</div>
</div>
</a>
@empty
<div class="flex bg-white/40 dark:bg-neutral-950/40 backdrop-blur rounded-xl shadow hover:shadow-lg transition overflow-hidden">
<div class="text-gray-800 dark:text-gray-200 text-center w-full p-4">
<p class="text-lg">No results</p>
<p class="text-sm opacity-70">(╥﹏╥)</p>
</div>
</div>
@endforelse
</div>
<!-- Pagination -->
<div>
{{ $comments->links('pagination::tailwind') }}
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More