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 ## hstream Website
### Install ### Install (Ubuntu)
```bash ```bash
# Install PHP # Install PHP
sudo add-apt-repository ppa:ondrej/php sudo add-apt-repository ppa:ondrej/php
apt update && apt upgrade 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 # Install NodeJS
curl -sL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh 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) # Install NGINX (skip for local dev)
apt install nginx apt install nginx
apt install php8.3-fpm apt install php8.4-fpm
# Install MariaDB # Install MariaDB
apt install mariadb-server apt install mariadb-server
@@ -54,7 +54,7 @@ nano /etc/supervisor/conf.d/laravel-queue.conf :
[program:laravel-queue] [program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d 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 autostart=true
autorestart=true autorestart=true
stopasgroup=true stopasgroup=true
@@ -83,9 +83,9 @@ zip -r hstream_2023_11_30.zip hstream/
### Update ### Update
```bash ```bash
php artisan down php84 artisan down
git pull git pull
npm run build npm run build
php artisan view:clear && php artisan optimize:clear && php artisan cache:clear && service php8.4-fpm restart php84 artisan view:clear && php84 artisan optimize:clear && php84 artisan cache:clear && service php8.4-fpm restart
php artisan up php84 artisan up
``` ```
+2 -3
View File
@@ -3,11 +3,10 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\PopularDaily; use App\Models\PopularDaily;
use App\Models\PopularWeekly;
use App\Models\PopularMonthly; use App\Models\PopularMonthly;
use App\Models\PopularWeekly;
use Illuminate\Support\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
class AutoStats extends Command class AutoStats extends Command
{ {
-1
View File
@@ -4,7 +4,6 @@ namespace App\Console\Commands;
use App\Models\Episode; use App\Models\Episode;
use App\Models\Hentai; use App\Models\Hentai;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Spatie\Sitemap\Sitemap; use Spatie\Sitemap\Sitemap;
+1 -2
View File
@@ -2,9 +2,8 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Downloads;
use App\Jobs\GetFileSizeFromCDN; use App\Jobs\GetFileSizeFromCDN;
use App\Models\Downloads;
use Illuminate\Console\Command; use Illuminate\Console\Command;
class GetFileSize extends Command class GetFileSize extends Command
+1 -2
View File
@@ -4,9 +4,8 @@ namespace App\Console\Commands;
use App\Models\User; use App\Models\User;
use App\Models\UserDownload; use App\Models\UserDownload;
use Illuminate\Support\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
class ResetUserDownloads extends Command class ResetUserDownloads extends Command
{ {
+17 -16
View File
@@ -5,21 +5,20 @@ namespace App\Helpers;
use App\Models\Comment; use App\Models\Comment;
use App\Models\Episode; use App\Models\Episode;
use App\Models\Hentai; use App\Models\Hentai;
use App\Models\PopularDaily;
use App\Models\PopularMonthly; use App\Models\PopularMonthly;
use App\Models\PopularWeekly; use App\Models\PopularWeekly;
use App\Models\PopularDaily;
use Conner\Tagging\Model\Tag; use Conner\Tagging\Model\Tag;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class CacheHelper class CacheHelper
{ {
public static function getRecentlyReleased(bool $guest) public static function getRecentlyReleased(bool $guest)
{ {
$guestString = $guest ? 'guest' : 'authed'; $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') return Episode::with('gallery')
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota'])) ->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
->orderBy('release_date', 'desc') ->orderBy('release_date', 'desc')
@@ -31,7 +30,8 @@ class CacheHelper
public static function getRecentlyUploaded(bool $guest) public static function getRecentlyUploaded(bool $guest)
{ {
$guestString = $guest ? 'guest' : 'authed'; $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') return Episode::with('gallery')
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota'])) ->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
@@ -42,21 +42,21 @@ class CacheHelper
public static function getTotalViewCount() 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'); return Episode::sum('view_count');
}); });
} }
public static function getTotalEpisodeCount() 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(); return Episode::count();
}); });
} }
public static function getTotalHentaiCount() 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(); return Hentai::count();
}); });
} }
@@ -64,7 +64,8 @@ class CacheHelper
public static function getPopularAllTime(bool $guest) public static function getPopularAllTime(bool $guest)
{ {
$guestString = $guest ? 'guest' : 'authed'; $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') return Episode::with('gallery')
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota'])) ->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
->orderBy('view_count', 'desc') ->orderBy('view_count', 'desc')
@@ -75,7 +76,7 @@ class CacheHelper
public static function getPopularMonthly() 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') return PopularMonthly::groupBy('episode_id')
->select('episode_id', DB::raw('count(*) as total')) ->select('episode_id', DB::raw('count(*) as total'))
->with('episode.gallery') ->with('episode.gallery')
@@ -87,7 +88,7 @@ class CacheHelper
public static function getPopularWeekly() 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') return PopularWeekly::groupBy('episode_id')
->select('episode_id', DB::raw('count(*) as total')) ->select('episode_id', DB::raw('count(*) as total'))
->with('episode.gallery') ->with('episode.gallery')
@@ -100,7 +101,7 @@ class CacheHelper
public static function getPopularDaily() 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') return PopularDaily::groupBy('episode_id')
->select('episode_id', DB::raw('count(*) as total')) ->select('episode_id', DB::raw('count(*) as total'))
->with('episode.gallery') ->with('episode.gallery')
@@ -112,21 +113,21 @@ class CacheHelper
public static function getMostLikes() 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(); 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() 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(); return Tag::where('count', '>', 0)->orderBy('slug', 'ASC')->get();
}); });
} }
public static function getLatestComments() 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(); return Comment::latest()->take(10)->get();
}); });
} }
+1 -1
View File
@@ -8,7 +8,7 @@ class GitHelper
{ {
public static function shortCommit() public static function shortCommit()
{ {
return Cache::remember("git_commit", now()->addMinutes(60), function () { return Cache::remember('git_commit', now()->addMinutes(60), function () {
try { try {
return trim(exec('git rev-parse --short HEAD')); return trim(exec('git rev-parse --short HEAD'));
} catch (\Exception $e) { } catch (\Exception $e) {
@@ -15,7 +15,7 @@ class ContactController extends Controller
$contacts = Contact::orderBy('created_at', 'DESC')->get(); $contacts = Contact::orderBy('created_at', 'DESC')->get();
return view('admin.contact.index', [ return view('admin.contact.index', [
'contacts' => $contacts 'contacts' => $contacts,
]); ]);
} }
@@ -3,9 +3,8 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Episode;
use App\Jobs\DiscordReleaseNotification; use App\Jobs\DiscordReleaseNotification;
use App\Models\Episode;
use App\Services\DownloadService; use App\Services\DownloadService;
use App\Services\EpisodeService; use App\Services\EpisodeService;
use App\Services\GalleryService; use App\Services\GalleryService;
@@ -14,7 +13,9 @@ use Illuminate\Http\Request;
class EpisodeController extends Controller class EpisodeController extends Controller
{ {
protected EpisodeService $episodeService; protected EpisodeService $episodeService;
protected GalleryService $galleryService; protected GalleryService $galleryService;
protected DownloadService $downloadService; protected DownloadService $downloadService;
public function __construct( public function __construct(
@@ -43,7 +44,7 @@ class EpisodeController extends Controller
// Discord Alert // Discord Alert
if ($request->has('censored')) { if ($request->has('censored')) {
DiscordReleaseNotification::dispatch($referenceEpisode->title." - ".$episodeNumber, 'release-censored'); DiscordReleaseNotification::dispatch($referenceEpisode->title.' - '.$episodeNumber, 'release-censored');
} else { } else {
DiscordReleaseNotification::dispatch($episode->slug, 'release'); DiscordReleaseNotification::dispatch($episode->slug, 'release');
} }
@@ -51,7 +52,7 @@ class EpisodeController extends Controller
cache()->flush(); cache()->flush();
return to_route('hentai.index', [ return to_route('hentai.index', [
'title' => $episode->slug 'title' => $episode->slug,
]); ]);
} }
@@ -87,7 +88,7 @@ class EpisodeController extends Controller
cache()->flush(); cache()->flush();
return to_route('hentai.index', [ return to_route('hentai.index', [
'title' => $episode->slug 'title' => $episode->slug,
]); ]);
} }
} }
@@ -3,9 +3,8 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Hentai;
use App\Jobs\DiscordReleaseNotification; use App\Jobs\DiscordReleaseNotification;
use App\Models\Hentai;
use App\Services\DownloadService; use App\Services\DownloadService;
use App\Services\EpisodeService; use App\Services\EpisodeService;
use App\Services\GalleryService; use App\Services\GalleryService;
@@ -14,7 +13,9 @@ use Illuminate\Http\Request;
class ReleaseController extends Controller class ReleaseController extends Controller
{ {
protected EpisodeService $episodeService; protected EpisodeService $episodeService;
protected GalleryService $galleryService; protected GalleryService $galleryService;
protected DownloadService $downloadService; protected DownloadService $downloadService;
public function __construct( public function __construct(
@@ -3,15 +3,13 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\SiteBackground; use App\Models\SiteBackground;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Intervention\Image\Laravel\Facades\Image;
use Intervention\Image\Encoders\WebpEncoder; use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Laravel\Facades\Image;
class SiteBackgroundController extends Controller class SiteBackgroundController extends Controller
{ {
@@ -44,7 +42,7 @@ class SiteBackgroundController extends Controller
$bg = SiteBackground::create(array_merge( $bg = SiteBackground::create(array_merge(
$request->only(['date_start', 'date_end']), $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()) Image::read($file->getRealPath())
->scaleDown(height: $resolution) ->scaleDown(height: $resolution)
->encode(new WebpEncoder()) ->encode(new WebpEncoder)
->save(public_path($targetPath)); ->save(public_path($targetPath));
} }
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollBack(); DB::rollBack();
Log::error($e->getMessage()); Log::error($e->getMessage());
return redirect()->back(); return redirect()->back();
} }
@@ -85,7 +84,7 @@ class SiteBackgroundController extends Controller
SiteBackground::where('id', $request->input('id'))->update(array_merge( SiteBackground::where('id', $request->input('id'))->update(array_merge(
$request->only(['date_start', 'date_end']), $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) { } catch (\Exception $e) {
DB::rollBack(); DB::rollBack();
Log::error($e->getMessage()); Log::error($e->getMessage());
return redirect()->back(); return redirect()->back();
} }
@@ -2,10 +2,10 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Episode; use App\Models\Episode;
use App\Models\EpisodeSubtitle; use App\Models\EpisodeSubtitle;
use App\Models\Subtitle; use App\Models\Subtitle;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class SubtitleController extends Controller class SubtitleController extends Controller
@@ -3,8 +3,8 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Enums\UserRole; use App\Enums\UserRole;
use App\Models\User;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class UserController extends Controller class UserController extends Controller
@@ -27,7 +27,6 @@ class UserController extends Controller
'action' => 'required', 'action' => 'required',
]); ]);
$user = User::findOrFail($validated['id']); $user = User::findOrFail($validated['id']);
switch ($validated['action']) { switch ($validated['action']) {
@@ -3,10 +3,10 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Helpers\CacheHelper; use App\Helpers\CacheHelper;
use App\Http\Controllers\Controller;
use App\Models\Episode; use App\Models\Episode;
use App\Models\Studios; use App\Models\Studios;
use App\Models\Subtitle; use App\Models\Subtitle;
use App\Http\Controllers\Controller;
class AdminApiController extends Controller class AdminApiController extends Controller
{ {
@@ -2,13 +2,11 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Downloads; use App\Models\Downloads;
use App\Models\Episode; use App\Models\Episode;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use GrantHolle\Altcha\Rules\ValidAltcha; use GrantHolle\Altcha\Rules\ValidAltcha;
use Illuminate\Http\Request;
class DownloadApiController extends Controller class DownloadApiController extends Controller
{ {
@@ -2,12 +2,11 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Hentai; use App\Models\Hentai;
use App\Models\PopularMonthly; use App\Models\PopularMonthly;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Controller;
class HentaiApiController extends Controller class HentaiApiController extends Controller
{ {
@@ -3,9 +3,7 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Episode; use App\Models\Episode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class StreamApiController extends Controller class StreamApiController extends Controller
@@ -33,7 +31,7 @@ class StreamApiController extends Controller
'stream_url' => $episode->dmca_takedown ? 'stuff/dmca' : $episode->url, 'stream_url' => $episode->dmca_takedown ? 'stuff/dmca' : $episode->url,
'stream_domains' => config('hstream.stream_domain'), 'stream_domains' => config('hstream.stream_domain'),
'asia_stream_domains' => config('hstream.asia_stream_domain'), 'asia_stream_domains' => config('hstream.asia_stream_domain'),
'extra_subtitles' => $subtitles 'extra_subtitles' => $subtitles,
], 200); ], 200);
} }
} }
@@ -4,9 +4,8 @@ namespace App\Http\Controllers\Api;
use App\Helpers\CacheHelper; use App\Helpers\CacheHelper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Conner\Tagging\Model\Tag; use Conner\Tagging\Model\Tag;
use Illuminate\Http\Request;
class UserApiController extends Controller class UserApiController extends Controller
{ {
@@ -33,11 +32,10 @@ class UserApiController extends Controller
} }
} }
return response()->json([ return response()->json([
'message' => 'success', 'message' => 'success',
'tags' => $tagWhiteList, 'tags' => $tagWhiteList,
'usertags' => $tagBlackList 'usertags' => $tagBlackList,
], 200); ], 200);
} }
} }
@@ -3,11 +3,9 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Enums\UserRole; use App\Enums\UserRole;
use App\Models\User;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@@ -90,6 +88,7 @@ class DiscordAuthController extends Controller
// User is not in the guild // User is not in the guild
if ($response->status() === 404) { if ($response->status() === 404) {
$user->removeRole(UserRole::SUPPORTER); $user->removeRole(UserRole::SUPPORTER);
return; return;
} }
@@ -114,6 +113,7 @@ class DiscordAuthController extends Controller
if (! $hasSupporterRole) { if (! $hasSupporterRole) {
// Remove role if not found // Remove role if not found
$user->removeRole(UserRole::SUPPORTER); $user->removeRole(UserRole::SUPPORTER);
return; return;
} }
@@ -16,8 +16,7 @@ class PasswordController extends Controller
public function update(Request $request): RedirectResponse public function update(Request $request): RedirectResponse
{ {
// If user logged in with Discord and has not yet a password, allow to set password // 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', [ $validated = $request->validateWithBag('updatePassword', [
'password' => ['required', Password::defaults(), 'confirmed'], 'password' => ['required', Password::defaults(), 'confirmed'],
]); ]);
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use GrantHolle\Altcha\Rules\ValidAltcha;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -11,8 +12,6 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules; use Illuminate\Validation\Rules;
use GrantHolle\Altcha\Rules\ValidAltcha;
class RegisteredUserController extends Controller class RegisteredUserController extends Controller
{ {
/** /**
+2 -3
View File
@@ -3,9 +3,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Contact; use App\Models\Contact;
use Illuminate\Http\Request;
use GrantHolle\Altcha\Rules\ValidAltcha; use GrantHolle\Altcha\Rules\ValidAltcha;
use Illuminate\Http\Request;
class ContactController extends Controller class ContactController extends Controller
{ {
@@ -30,7 +29,7 @@ class ContactController extends Controller
'altcha' => ['required', new ValidAltcha], 'altcha' => ['required', new ValidAltcha],
]); ]);
$contact = new Contact(); $contact = new Contact;
$contact->name = $request->input('name'); $contact->name = $request->input('name');
$contact->email = $request->input('email'); $contact->email = $request->input('email');
$contact->message = $request->input('message'); $contact->message = $request->input('message');
+3 -5
View File
@@ -2,11 +2,10 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Episode;
use App\Helpers\CacheHelper; use App\Helpers\CacheHelper;
use App\Models\Episode;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cookie;
class HomeController extends Controller class HomeController extends Controller
{ {
@@ -19,15 +18,14 @@ class HomeController extends Controller
$guestString = $guest ? 'guest' : 'authed'; $guestString = $guest ? 'guest' : 'authed';
$mostLikes = \cache()->remember('mostLikes'.$guestString, 300, fn () => $mostLikes = \cache()->remember('mostLikes'.$guestString, 300, fn () => Episode::with('gallery')
Episode::with('gallery')
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota'])) ->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
->whereIn('id', function ($query) { ->whereIn('id', function ($query) {
$mostLikesIds = CacheHelper::getMostLikes()->pluck('markable_id')->toArray(); $mostLikesIds = CacheHelper::getMostLikes()->pluck('markable_id')->toArray();
$query->selectRaw('id') $query->selectRaw('id')
->from('episodes') ->from('episodes')
->whereIn('id', $mostLikesIds) ->whereIn('id', $mostLikesIds)
->orderByRaw("FIELD(id, " . implode(',', $mostLikesIds) . ")"); ->orderByRaw('FIELD(id, '.implode(',', $mostLikesIds).')');
}) })
->get() ->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; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class NotificationController extends Controller class NotificationController extends Controller
{ {
+7 -9
View File
@@ -7,7 +7,6 @@ use App\Models\Playlist;
use App\Models\PlaylistEpisode; use App\Models\PlaylistEpisode;
use App\Services\PlaylistService; use App\Services\PlaylistService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
class PlaylistController extends Controller class PlaylistController extends Controller
{ {
@@ -42,14 +41,13 @@ class PlaylistController extends Controller
]); ]);
} }
/** /**
* Display the user's playlists page. * Display the user's playlists page.
*/ */
public function playlists(Request $request): \Illuminate\View\View public function playlists(Request $request): \Illuminate\View\View
{ {
$title = 'Delete Playlist!'; $title = 'Delete Playlist!';
$text = "Are you sure you want to delete?"; $text = 'Are you sure you want to delete?';
confirmDelete($title, $text); confirmDelete($title, $text);
return view('profile.playlists', [ return view('profile.playlists', [
@@ -85,7 +83,7 @@ class PlaylistController extends Controller
'name' => 'required|max:30', 'name' => 'required|max:30',
]); ]);
$playlist = new Playlist(); $playlist = new Playlist;
$playlist->user_id = $request->user()->id; $playlist->user_id = $request->user()->id;
$playlist->name = $request->input('name'); $playlist->name = $request->input('name');
$playlist->is_private = $request->input('visiblity') === 'private'; $playlist->is_private = $request->input('visiblity') === 'private';
@@ -151,7 +149,7 @@ class PlaylistController extends Controller
$validated = $request->validate([ $validated = $request->validate([
'playlist' => 'required|max:30', 'playlist' => 'required|max:30',
'episode_id' => 'required' 'episode_id' => 'required',
]); ]);
$playlist = Playlist::where('user_id', $user->id)->where('id', $request->input('playlist'))->firstOrFail(); $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(); $exists = PlaylistEpisode::where('playlist_id', $playlist->id)->where('episode_id', $episode->id)->exists();
if ($exists) { if ($exists) {
return response()->json([ return response()->json([
'message' => 'already-added' 'message' => 'already-added',
], 200); ], 200);
} }
@@ -175,7 +173,7 @@ class PlaylistController extends Controller
]); ]);
return response()->json([ return response()->json([
'message' => 'success' 'message' => 'success',
], 200); ], 200);
} }
@@ -188,7 +186,7 @@ class PlaylistController extends Controller
'name' => 'required|max:30', 'name' => 'required|max:30',
]); ]);
$playlist = new Playlist(); $playlist = new Playlist;
$playlist->user_id = $request->user()->id; $playlist->user_id = $request->user()->id;
$playlist->name = $request->input('name'); $playlist->name = $request->input('name');
$playlist->is_private = $request->input('visiblity') === 'private'; $playlist->is_private = $request->input('visiblity') === 'private';
@@ -196,7 +194,7 @@ class PlaylistController extends Controller
return response()->json([ return response()->json([
'message' => 'success', 'message' => 'success',
'playlist_id' => $playlist->id 'playlist_id' => $playlist->id,
], 200); ], 200);
} }
} }
+6 -8
View File
@@ -2,22 +2,20 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\ProfileUpdateRequest;
use App\Models\Episode; use App\Models\Episode;
use App\Models\User; use App\Models\User;
use App\Http\Requests\ProfileUpdateRequest; use Conner\Tagging\Model\Tag;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\View\View; use Illuminate\View\View;
use Intervention\Image\Laravel\Facades\Image; use Intervention\Image\Laravel\Facades\Image;
use Conner\Tagging\Model\Tag;
class ProfileController extends Controller class ProfileController extends Controller
{ {
/** /**
@@ -121,6 +119,7 @@ class ProfileController extends Controller
if (! $tags) { if (! $tags) {
$user->tag_blacklist = null; $user->tag_blacklist = null;
$user->save(); $user->save();
return Redirect::route('profile.settings')->with('status', 'blacklist-updated'); 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 protected function storeAvatar(\Illuminate\Http\UploadedFile $file, User $user): void
{ {
// Create Folder for Image Upload // Create Folder for Image Upload
if (! Storage::disk('public')->exists("/images/avatars")) { if (! Storage::disk('public')->exists('/images/avatars')) {
Storage::disk('public')->makeDirectory("/images/avatars"); Storage::disk('public')->makeDirectory('/images/avatars');
} }
// Delete old avatar if it exists // Delete old avatar if it exists
@@ -196,5 +195,4 @@ class ProfileController extends Controller
$user->avatar = $filename; $user->avatar = $filename;
} }
} }
+2 -5
View File
@@ -2,20 +2,18 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Helpers\CacheHelper;
use App\Models\Episode; use App\Models\Episode;
use App\Models\Gallery; use App\Models\Gallery;
use App\Models\Hentai; use App\Models\Hentai;
use App\Models\Playlist; use App\Models\Playlist;
use App\Models\PlaylistEpisode; use App\Models\PlaylistEpisode;
use App\Models\Watched; use App\Models\Watched;
use App\Helpers\CacheHelper; use hisorange\BrowserDetect\Facade as Browser;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use hisorange\BrowserDetect\Facade as Browser;
class StreamController extends Controller class StreamController extends Controller
{ {
/** /**
@@ -37,7 +35,6 @@ class StreamController extends Controller
]); ]);
} }
$episode = Episode::where('slug', $title)->firstOrFail(); $episode = Episode::where('slug', $title)->firstOrFail();
$gallery = Gallery::where('episode_id', $episode->id)->get(); $gallery = Gallery::where('episode_id', $episode->id)->get();
$moreEpisodes = Episode::with(['gallery', 'studio'])->where('hentai_id', $episode->hentai_id)->whereNot('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 App\Enums\UserRole;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class IsAdmin { class IsAdmin
{
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle(Request $request, Closure $next): Response 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); return $next($request);
} }
session()->flash('error_msg', 'This resource is restricted to Administrators!'); session()->flash('error_msg', 'This resource is restricted to Administrators!');
return redirect()->route('home.index'); 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 App\Enums\UserRole;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class IsBanned { class IsBanned
{
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle(Request $request, Closure $next): Response 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(); Auth::logout();
$request->session()->invalidate(); $request->session()->invalidate();
$request->session()->regenerateToken(); $request->session()->regenerateToken();
return redirect()->route('home.banned'); return redirect()->route('home.banned');
} }
return $next($request); return $next($request);
} }
} }
+2 -3
View File
@@ -3,7 +3,6 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Enums\UserRole; use App\Enums\UserRole;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@@ -18,12 +17,12 @@ class IsModerator
*/ */
public function handle(Request $request, Closure $next): Response 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); return $next($request);
} }
session()->flash('error_msg', 'This resource is restricted to Administrators!'); session()->flash('error_msg', 'This resource is restricted to Administrators!');
return redirect()->route('home.index'); return redirect()->route('home.index');
} }
} }
+2
View File
@@ -20,12 +20,14 @@ class SetLocale
// 1. Logged-in user preference // 1. Logged-in user preference
if (Auth::check() && Auth::user()->locale) { if (Auth::check() && Auth::user()->locale) {
App::setLocale(Auth::user()->locale); App::setLocale(Auth::user()->locale);
return $next($request); return $next($request);
} }
// 2. Session (guest or user override) // 2. Session (guest or user override)
if (session()->has('locale') && in_array($request->language, config('app.supported_locales'), true)) { if (session()->has('locale') && in_array($request->language, config('app.supported_locales'), true)) {
App::setLocale(session('locale')); App::setLocale(session('locale'));
return $next($request); return $next($request);
} }
+1 -2
View File
@@ -2,6 +2,7 @@
namespace App\Http\Requests\Auth; namespace App\Http\Requests\Auth;
use GrantHolle\Altcha\Rules\ValidAltcha;
use Illuminate\Auth\Events\Lockout; use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@@ -9,8 +10,6 @@ use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use GrantHolle\Altcha\Rules\ValidAltcha;
class LoginRequest extends FormRequest 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', 'nullable',
'image', 'image',
'mimes:jpg,png,jpeg,webp,gif', 'mimes:jpg,png,jpeg,webp,gif',
'max:8192' 'max:8192',
], ],
'email' => [ 'email' => [
'required', 'required',
+10 -12
View File
@@ -7,7 +7,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Spatie\DiscordAlerts\Facades\DiscordAlert; use Spatie\DiscordAlerts\Facades\DiscordAlert;
class DiscordReleaseNotification implements ShouldQueue class DiscordReleaseNotification implements ShouldQueue
@@ -32,26 +31,25 @@ class DiscordReleaseNotification implements ShouldQueue
*/ */
public function handle(): void public function handle(): void
{ {
switch($this->messageType) switch ($this->messageType) {
{
case 'release': 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; break;
case 'release-censored': case 'release-censored':
# Because Discord TOS // Because Discord TOS
DiscordAlert::message("<@&868457842250764289> (´• ω •`)ノ New **4k** Release: ".$this->slug." - *No link here because of* :pLoli:"); DiscordAlert::message('<@&868457842250764289> (´• ω •`)ノ New **4k** Release: '.$this->slug.' - *No link here because of* :pLoli:');
break; break;
case 'update': case 'update':
# 1080p 48fps added // 1080p 48fps added
DiscordAlert::to('update')->message("<@&1283518462584426598> (´• ω •`)ノ Added **48fps** to Release! Check it out here: https://hstream.moe/hentai/".$this->slug); DiscordAlert::to('update')->message('<@&1283518462584426598> (´• ω •`)ノ Added **48fps** to Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
break; break;
case 'updateUHD': case 'updateUHD':
# 4k 48fps added // 4k 48fps added
DiscordAlert::to('update')->message("<@&1326860920902778963> (´• ω •`)ノ Added **48fps 4k** to Release! Check it out here: https://hstream.moe/hentai/".$this->slug); DiscordAlert::to('update')->message('<@&1326860920902778963> (´• ω •`)ノ Added **48fps 4k** to Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
break; break;
case 'v2': case 'v2':
# v2 re-release // v2 re-release
DiscordAlert::to('rerelease')->message("<@&1425505303075754035> (´• ω •`)ノ **v2 Re-**Release! Check it out here: https://hstream.moe/hentai/".$this->slug); DiscordAlert::to('rerelease')->message('<@&1425505303075754035> (´• ω •`)ノ **v2 Re-**Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
break; break;
default: default:
break; break;
+3 -2
View File
@@ -7,12 +7,12 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Client\RequestException; use Illuminate\Http\Client\RequestException;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class GetFileSizeFromCDN implements ShouldQueue class GetFileSizeFromCDN implements ShouldQueue
{ {
@@ -37,6 +37,7 @@ class GetFileSizeFromCDN implements ShouldQueue
$download = Downloads::find($this->downloadId); $download = Downloads::find($this->downloadId);
if (! $download) { if (! $download) {
Log::error("Download not found for ID: {$this->downloadId}"); Log::error("Download not found for ID: {$this->downloadId}");
return; return;
} }
+1 -2
View File
@@ -5,7 +5,6 @@ namespace App\Livewire;
use App\Models\Comment; use App\Models\Comment;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Illuminate\Support\Facades\DB;
class AdminCommentSearch extends Component class AdminCommentSearch extends Component
{ {
@@ -41,7 +40,7 @@ class AdminCommentSearch extends Component
->paginate(12); ->paginate(12);
return view('livewire.admin-comment-search', [ 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\Enums\UserRole;
use App\Models\Comment; use App\Models\Comment;
use App\Models\User; use App\Models\User;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url;
class AdminUserSearch extends Component class AdminUserSearch extends Component
{ {
@@ -46,7 +45,7 @@ class AdminUserSearch extends Component
->paginate(20); ->paginate(20);
return view('livewire.admin-user-search', [ return view('livewire.admin-user-search', [
'users' => $users 'users' => $users,
]); ]);
} }
} }
+6 -9
View File
@@ -2,12 +2,11 @@
namespace App\Livewire; namespace App\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
use App\Models\SiteBackground; use App\Models\SiteBackground;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
class BackgroundImages extends Component class BackgroundImages extends Component
{ {
@@ -20,16 +19,14 @@ class BackgroundImages extends Component
{ {
$now = Carbon::now(); $now = Carbon::now();
$images = SiteBackground::when($this->filter === 'active', fn ($query) => $images = SiteBackground::when($this->filter === 'active', fn ($query) => $query->whereDate('date_start', '<=', $now)->whereDate('date_end', '>=', $now)
$query->whereDate('date_start', '<=', $now)->whereDate('date_end', '>=', $now)
) )
->when($this->filter === 'inactive', fn ($query) => ->when($this->filter === 'inactive', fn ($query) => $query->whereDate('date_start', '>', $now)->orWhereDate('date_end', '<', $now)
$query->whereDate('date_start', '>', $now)->orWhereDate('date_end', '<', $now)
) )
->paginate(10); ->paginate(10);
return view('livewire.background-images', [ return view('livewire.background-images', [
'images' => $images 'images' => $images,
]); ]);
} }
} }
+15 -16
View File
@@ -1,20 +1,16 @@
<?php <?php
namespace App\Livewire; namespace App\Livewire;
use App\Models\User;
use App\Models\Episode; use App\Models\Episode;
use App\Models\User;
use App\Notifications\CommentNotification; use App\Notifications\CommentNotification;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Support\Str;
use Livewire\Component;
use Maize\Markable\Models\Like; use Maize\Markable\Models\Like;
class Comment extends Component class Comment extends Component
@@ -30,21 +26,21 @@ class Comment extends Component
public $liked = false; public $liked = false;
public $replyState = [ public $replyState = [
'body' => '' 'body' => '',
]; ];
public $isEditing = false; public $isEditing = false;
public $editState = [ public $editState = [
'body' => '' 'body' => '',
]; ];
protected $listeners = [ protected $listeners = [
'refresh' => '$refresh' 'refresh' => '$refresh',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'replyState.body' => 'reply' 'replyState.body' => 'reply',
]; ];
public function updatedIsEditing($isEditing) public function updatedIsEditing($isEditing)
@@ -54,7 +50,7 @@ class Comment extends Component
} }
$this->editState = [ $this->editState = [
'body' => $this->comment->body 'body' => $this->comment->body,
]; ];
} }
@@ -79,7 +75,8 @@ class Comment extends Component
public function postReply() public function postReply()
{ {
if (! ($this->comment->depth() < 2)) { if (! ($this->comment->depth() < 2)) {
$this->addError('replyState.body', "Too many sub comments."); $this->addError('replyState.body', 'Too many sub comments.');
return; return;
} }
@@ -91,13 +88,14 @@ class Comment extends Component
$seconds = RateLimiter::availableIn($rateLimitKey); $seconds = RateLimiter::availableIn($rateLimitKey);
$this->addError('replyState.body', "Too many comments. Try again in {$seconds} seconds."); $this->addError('replyState.body', "Too many comments. Try again in {$seconds} seconds.");
return; return;
} }
RateLimiter::hit($rateLimitKey, $rateLimitMinutes); RateLimiter::hit($rateLimitKey, $rateLimitMinutes);
$this->validate([ $this->validate([
'replyState.body' => 'required' 'replyState.body' => 'required',
]); ]);
$reply = $this->comment->children()->make($this->replyState); $reply = $this->comment->children()->make($this->replyState);
@@ -123,7 +121,7 @@ class Comment extends Component
} }
$this->replyState = [ $this->replyState = [
'body' => '' 'body' => '',
]; ];
$this->isReplying = false; $this->isReplying = false;
@@ -144,6 +142,7 @@ class Comment extends Component
if ($this->liked) { if ($this->liked) {
$this->liked = false; $this->liked = false;
$this->likeCount--; $this->likeCount--;
return; return;
} }
+8 -8
View File
@@ -2,11 +2,10 @@
namespace App\Livewire; namespace App\Livewire;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Illuminate\Support\Facades\RateLimiter;
class Comments extends Component class Comments extends Component
{ {
use WithPagination; use WithPagination;
@@ -14,21 +13,21 @@ class Comments extends Component
public $model; public $model;
public $newCommentState = [ public $newCommentState = [
'body' => '' 'body' => '',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'newCommentState.body' => 'comment' 'newCommentState.body' => 'comment',
]; ];
protected $listeners = [ protected $listeners = [
'refresh' => '$refresh' 'refresh' => '$refresh',
]; ];
public function postComment() public function postComment()
{ {
$this->validate([ $this->validate([
'newCommentState.body' => 'required' 'newCommentState.body' => 'required',
]); ]);
$user = auth()->user(); $user = auth()->user();
@@ -39,6 +38,7 @@ class Comments extends Component
$seconds = RateLimiter::availableIn($rateLimitKey); $seconds = RateLimiter::availableIn($rateLimitKey);
$this->addError('newCommentState.body', "Too many comments. Try again in {$seconds} seconds."); $this->addError('newCommentState.body', "Too many comments. Try again in {$seconds} seconds.");
return; return;
} }
@@ -49,7 +49,7 @@ class Comments extends Component
$comment->save(); $comment->save();
$this->newCommentState = [ $this->newCommentState = [
'body' => '' 'body' => '',
]; ];
$this->resetPage(); $this->resetPage();
@@ -65,7 +65,7 @@ class Comments extends Component
->paginate(50); ->paginate(50);
return view('livewire.comments', [ 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 $background = 'bg-rose-600';
public $fileExtension = 'HEVC';
public function mount()
{
if (str_contains($this->downloadUrl, 'AV1')) {
$this->fileExtension = 'AV1';
}
}
public function clicked($downloadId) public function clicked($downloadId)
{ {
$download = Downloads::find($downloadId); $download = Downloads::find($downloadId);
+3 -3
View File
@@ -5,11 +5,9 @@ namespace App\Livewire;
use App\Models\Episode; use App\Models\Episode;
use App\Models\User; use App\Models\User;
use App\Models\UserDownload; use App\Models\UserDownload;
use Livewire\Component;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class DownloadsFree extends Component class DownloadsFree extends Component
{ {
@@ -51,6 +49,7 @@ class DownloadsFree extends Component
if (Carbon::parse($alreadyDownloaded->created_at)->addHours(6) <= Carbon::now()) { if (Carbon::parse($alreadyDownloaded->created_at)->addHours(6) <= Carbon::now()) {
// Already expired // Already expired
$alreadyDownloaded->delete(); $alreadyDownloaded->delete();
return; return;
} }
@@ -65,6 +64,7 @@ class DownloadsFree extends Component
if ($user->downloads_left <= 0) { if ($user->downloads_left <= 0) {
// Daily limit reached // Daily limit reached
$this->granted = 3; $this->granted = 3;
return; return;
} }
+5 -2
View File
@@ -3,9 +3,9 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Downloads; use App\Models\Downloads;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url;
class DownloadsSearch extends Component class DownloadsSearch extends Component
{ {
@@ -25,6 +25,7 @@ class DownloadsSearch extends Component
#[Url(history: true)] #[Url(history: true)]
public $studios = []; public $studios = [];
public $studiosCopy = []; public $studiosCopy = [];
// To toggle individual option selection // 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.'%')) $downloads = Downloads::when($this->fileSearch != '', fn ($query) => $query->where('url', 'like', '%'.$this->fileSearch.'%'))
->whereIn('type', $this->getSelectedTypes()) ->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') ->whereNotNull('size')
->orderBy($orderby, $orderdirection) ->orderBy($orderby, $orderdirection)
->paginate(20); ->paginate(20);
+2 -2
View File
@@ -4,10 +4,9 @@ namespace App\Livewire;
use App\Models\Episode; use App\Models\Episode;
use App\Models\User; use App\Models\User;
use Livewire\Component;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Livewire\Component;
use Maize\Markable\Models\Like; use Maize\Markable\Models\Like;
class LikeButton extends Component class LikeButton extends Component
@@ -52,6 +51,7 @@ class LikeButton extends Component
if ($this->liked) { if ($this->liked) {
$this->liked = false; $this->liked = false;
$this->likeCount--; $this->likeCount--;
return; return;
} }
+11 -4
View File
@@ -3,10 +3,10 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Episode; use App\Models\Episode;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url;
use Illuminate\Support\Facades\Auth;
class LiveSearch extends Component class LiveSearch extends Component
{ {
@@ -20,14 +20,17 @@ class LiveSearch extends Component
#[Url(history: true)] #[Url(history: true)]
public $tags = []; public $tags = [];
public $tagsCopy = []; public $tagsCopy = [];
#[Url(history: true)] #[Url(history: true)]
public $studios = []; public $studios = [];
public $studiosCopy = []; public $studiosCopy = [];
#[Url(history: true)] #[Url(history: true)]
public $blacklist = []; public $blacklist = [];
public $blacklistCopy = []; public $blacklistCopy = [];
#[Url(history: true)] #[Url(history: true)]
@@ -118,10 +121,14 @@ class LiveSearch extends Component
} }
$user_id = Auth::check() ? auth()->user()->id : 0; $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->tags !== [], fn ($query) => $query->withAllTags($this->tags))
->when($this->blacklist !== [], fn ($query) => $query->withoutTags($this->blacklist)) ->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) { ->when($this->hideWatched !== [] && Auth::check(), fn ($query) => $query->whereDoesntHave('watched', function ($query) use ($user_id) {
$query->where('user_id', $user_id); $query->where('user_id', $user_id);
})) }))
+1 -1
View File
@@ -3,8 +3,8 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Episode; use App\Models\Episode;
use Livewire\Component;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class NavLiveSearch extends Component class NavLiveSearch extends Component
{ {
+3 -5
View File
@@ -5,13 +5,11 @@ namespace App\Livewire;
use App\Models\Playlist; use App\Models\Playlist;
use App\Models\PlaylistEpisode; use App\Models\PlaylistEpisode;
use App\Services\PlaylistService; use App\Services\PlaylistService;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Collection;
class PlaylistOverview extends Component class PlaylistOverview extends Component
{ {
+2 -3
View File
@@ -3,10 +3,9 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Playlist; use App\Models\Playlist;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url;
class Playlists extends Component class Playlists extends Component
{ {
@@ -59,7 +58,7 @@ class Playlists extends Component
->paginate($this->pagination); ->paginate($this->pagination);
return view('livewire.playlists', [ return view('livewire.playlists', [
'playlists' => $playlists 'playlists' => $playlists,
]); ]);
} }
} }
+2 -4
View File
@@ -3,7 +3,6 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Comment; use App\Models\Comment;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
@@ -36,14 +35,13 @@ class UserComments extends Component
$orderdirection = 'desc'; $orderdirection = 'desc';
} }
$comments = Comment::where('user_id', $this->model->id) $comments = Comment::where('user_id', $this->model->id)
->when($this->commentSearch != '', fn ($query) => $query->where('body', 'like', '%'.$this->commentSearch.'%')) ->when($this->commentSearch != '', fn ($query) => $query->where('body', 'like', '%'.$this->commentSearch.'%'))
->orderBy($orderby, $orderdirection) ->orderBy($orderby, $orderdirection)
->paginate(10); ->paginate(6);
return view('livewire.user-comments', [ return view('livewire.user-comments', [
'comments' => $comments 'comments' => $comments,
]); ]);
} }
} }
+11 -4
View File
@@ -3,10 +3,10 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Episode; use App\Models\Episode;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url;
use Illuminate\Support\Facades\Auth;
class UserLikes extends Component class UserLikes extends Component
{ {
@@ -20,14 +20,17 @@ class UserLikes extends Component
#[Url(history: true)] #[Url(history: true)]
public $tags = []; public $tags = [];
public $tagsCopy = []; public $tagsCopy = [];
#[Url(history: true)] #[Url(history: true)]
public $studios = []; public $studios = [];
public $studiosCopy = []; public $studiosCopy = [];
#[Url(history: true)] #[Url(history: true)]
public $blacklist = []; public $blacklist = [];
public $blacklistCopy = []; public $blacklistCopy = [];
#[Url(history: true)] #[Url(history: true)]
@@ -119,10 +122,14 @@ class UserLikes extends Component
$user_id = Auth::check() ? auth()->user()->id : 0; $user_id = Auth::check() ? auth()->user()->id : 0;
$episodes = Episode::whereHasLike(auth()->user()) $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->tags !== [], fn ($query) => $query->withAllTags($this->tags))
->when($this->blacklist !== [], fn ($query) => $query->withoutTags($this->blacklist)) ->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) { ->when($this->hideWatched !== [] && Auth::check(), fn ($query) => $query->whereDoesntHave('watched', function ($query) use ($user_id) {
$query->where('user_id', $user_id); $query->where('user_id', $user_id);
})) }))
-1
View File
@@ -3,7 +3,6 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Watched as UserWatched; use App\Models\Watched as UserWatched;
use App\Models\User;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
+5 -6
View File
@@ -4,19 +4,18 @@ namespace App\Models;
use App\Models\Presenters\CommentPresenter; use App\Models\Presenters\CommentPresenter;
use Illuminate\Database\Eloquent\Builder; 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\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Maize\Markable\Markable; use Maize\Markable\Markable;
use Maize\Markable\Models\Like; use Maize\Markable\Models\Like;
class Comment extends Model class Comment extends Model
{ {
use HasFactory, SoftDeletes, Markable; use HasFactory, Markable, SoftDeletes;
protected static $marks = [ protected static $marks = [
Like::class Like::class,
]; ];
/** /**
@@ -25,7 +24,7 @@ class Comment extends Model
* @var string[] * @var string[]
*/ */
protected $fillable = [ protected $fillable = [
'body' 'body',
]; ];
public function presenter() public function presenter()
+13 -18
View File
@@ -2,35 +2,28 @@
namespace App\Models; namespace App\Models;
use App\Models\Downloads;
use App\Models\PopularMonthly;
use App\Models\PopularWeekly;
use App\Models\PopularDaily;
use Conner\Tagging\Taggable; 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\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; 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 class Episode extends Model implements Sitemapable
{ {
use Markable, Taggable;
use HasFactory; use HasFactory;
use Markable, Taggable;
use Searchable; use Searchable;
protected static $marks = [ protected static $marks = [
Like::class Like::class,
]; ];
/** /**
@@ -180,6 +173,7 @@ class Episode extends Model implements Sitemapable
$problematicResults .= $pTag; $problematicResults .= $pTag;
} }
return $problematicResults; return $problematicResults;
} }
@@ -234,9 +228,10 @@ class Episode extends Model implements Sitemapable
return $this->hasMany(Watched::class); return $this->hasMany(Watched::class);
} }
public function getDownloadByType(string $type): Downloads | null public function getDownloadByType(string $type): ?Downloads
{ {
$cacheKey = "episode_{$this->id}_download_{$type}"; $cacheKey = "episode_{$this->id}_download_{$type}";
return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($type) { return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($type) {
return $this->downloads()->where('type', $type)->first(); return $this->downloads()->where('type', $type)->first();
}); });
+7 -8
View File
@@ -2,19 +2,18 @@
namespace App\Models; 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\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url; 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 class Hentai extends Model implements Sitemapable
{ {
use Taggable;
use HasFactory; use HasFactory;
use Taggable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@@ -31,7 +30,7 @@ class Hentai extends Model implements Sitemapable
return $this->hasMany(Episode::class, 'hentai_id'); return $this->hasMany(Episode::class, 'hentai_id');
} }
public function title(): String public function title(): string
{ {
return $this->episodes->first()->title; return $this->episodes->first()->title;
} }
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Playlist extends Model class Playlist extends Model
{ {
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PlaylistEpisode extends Model class PlaylistEpisode extends Model
{ {
+1 -2
View File
@@ -4,7 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
class SiteBackground extends Model class SiteBackground extends Model
{ {
@@ -16,7 +15,7 @@ class SiteBackground extends Model
protected $fillable = [ protected $fillable = [
'date_start', 'date_start',
'date_end', 'date_end',
'default' 'default',
]; ];
/** /**
+1 -1
View File
@@ -3,8 +3,8 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Studios extends Model class Studios extends Model
{ {
+4 -8
View File
@@ -5,13 +5,12 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Enums\UserRole; use App\Enums\UserRole;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class User extends Authenticatable class User extends Authenticatable
{ {
@@ -62,7 +61,6 @@ class User extends Authenticatable
'discord_avatar' => 'string', 'discord_avatar' => 'string',
]; ];
/** /**
* Has Many Playlists. * Has Many Playlists.
*/ */
@@ -108,13 +106,11 @@ class User extends Authenticatable
*/ */
public function getAvatar(): string 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}"; return "https://external-content.duckduckgo.com/iu/?u={$this->discord_avatar}";
} }
if ($this->avatar) if ($this->avatar) {
{
return Storage::url($this->avatar); return Storage::url($this->avatar);
} }
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class UserDownload extends Model class UserDownload extends Model
{ {
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Watched extends Model class Watched extends Model
{ {
+2 -2
View File
@@ -3,8 +3,6 @@
namespace App\Notifications; namespace App\Notifications;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
class CommentNotification extends Notification class CommentNotification extends Notification
@@ -12,7 +10,9 @@ class CommentNotification extends Notification
use Queueable; use Queueable;
protected $type; protected $type;
protected $message; protected $message;
protected $url; protected $url;
/** /**
+1 -1
View File
@@ -2,8 +2,8 @@
namespace App\Policies; namespace App\Policies;
use App\Models\User;
use App\Models\Comment; use App\Models\Comment;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
class CommentPolicy class CommentPolicy
+1 -3
View File
@@ -2,11 +2,9 @@
namespace App\Services; namespace App\Services;
use App\Jobs\GetFileSizeFromCDN;
use App\Models\Downloads; use App\Models\Downloads;
use App\Models\Episode; use App\Models\Episode;
use App\Jobs\GetFileSizeFromCDN;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class DownloadService class DownloadService
+7 -10
View File
@@ -5,14 +5,12 @@ namespace App\Services;
use App\Models\Episode; use App\Models\Episode;
use App\Models\Hentai; use App\Models\Hentai;
use App\Models\Studios; use App\Models\Studios;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Laravel\Facades\Image;
use Intervention\Image\Encoders\WebpEncoder; use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Laravel\Facades\Image;
class EpisodeService class EpisodeService
{ {
@@ -24,6 +22,7 @@ class EpisodeService
if (is_numeric($lastPart) && $lastPart < 1000) { if (is_numeric($lastPart) && $lastPart < 1000) {
$slugParts[array_key_last($slugParts)] = 's'.$lastPart; $slugParts[array_key_last($slugParts)] = 's'.$lastPart;
return implode('-', $slugParts); return implode('-', $slugParts);
} }
@@ -36,11 +35,10 @@ class EpisodeService
int $episodeNumber, int $episodeNumber,
?Studios $studio = null, ?Studios $studio = null,
?Episode $referenceEpisode = null ?Episode $referenceEpisode = null
): Episode ): Episode {
{ $episode = new Episode;
$episode = new Episode();
$episode->title = $referenceEpisode->title ?? $request->input('title'); $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->title_jpn = $referenceEpisode->title_jpn ?? $request->input('title_jpn');
$episode->slug = "{$hentai->slug}-{$episodeNumber}"; $episode->slug = "{$hentai->slug}-{$episodeNumber}";
$episode->hentai_id = $hentai->id; $episode->hentai_id = $hentai->id;
@@ -96,7 +94,6 @@ class EpisodeService
); );
} }
public function createOrUpdateCover(Request $request, Episode $episode, string $slug, int $episodeNumber): void public function createOrUpdateCover(Request $request, Episode $episode, string $slug, int $episodeNumber): void
{ {
if (! $request->hasFile("episodecover{$episodeNumber}")) { if (! $request->hasFile("episodecover{$episodeNumber}")) {
@@ -111,7 +108,7 @@ class EpisodeService
// Encode and save cover image // Encode and save cover image
Image::read($request->file("episodecover{$episodeNumber}")->getRealPath()) Image::read($request->file("episodecover{$episodeNumber}")->getRealPath())
->cover(268, 394) ->cover(268, 394)
->encode(new WebpEncoder()) ->encode(new WebpEncoder)
->save(Storage::disk('public')->path($episode->cover_url)); ->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\Episode;
use App\Models\Gallery; use App\Models\Gallery;
use App\Models\Hentai; use App\Models\Hentai;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Intervention\Image\Laravel\Facades\Image;
use Intervention\Image\Encoders\WebpEncoder; use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Laravel\Facades\Image;
class GalleryService class GalleryService
{ {
public function createOrUpdateGallery(Request $request, Hentai $hentai, Episode $episode, int $episodeNumber, bool $override = false): void public function createOrUpdateGallery(Request $request, Hentai $hentai, Episode $episode, int $episodeNumber, bool $override = false): void
{ {
$galleryInputNumber = $override ? 1 : $episodeNumber; $galleryInputNumber = $override ? 1 : $episodeNumber;
@@ -45,7 +41,7 @@ class GalleryService
private function createGallery(Hentai $hentai, Episode $episode, int $episodeNumber, int $counter): Gallery private function createGallery(Hentai $hentai, Episode $episode, int $episodeNumber, int $counter): Gallery
{ {
$gallery = new Gallery(); $gallery = new Gallery;
$gallery->hentai_id = $hentai->id; $gallery->hentai_id = $hentai->id;
$gallery->episode_id = $episode->id; $gallery->episode_id = $episode->id;
$gallery->image_url = "/images/hentai/{$hentai->slug}/gallery-ep-{$episodeNumber}-{$counter}.webp"; $gallery->image_url = "/images/hentai/{$hentai->slug}/gallery-ep-{$episodeNumber}-{$counter}.webp";
@@ -59,12 +55,12 @@ class GalleryService
{ {
Image::read($sourceImage->getRealPath()) Image::read($sourceImage->getRealPath())
->cover(1920, 1080) ->cover(1920, 1080)
->encode(new WebpEncoder()) ->encode(new WebpEncoder)
->save(Storage::disk('public')->path($gallery->image_url)); ->save(Storage::disk('public')->path($gallery->image_url));
Image::read($sourceImage->getRealPath()) Image::read($sourceImage->getRealPath())
->cover(960, 540) ->cover(960, 540)
->encode(new WebpEncoder()) ->encode(new WebpEncoder)
->save(Storage::disk('public')->path($gallery->thumbnail_url)); ->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", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "2e359f5cfd56c822e336b74becc9c9d9", "content-hash": "3400111a6254560d548639295422875c",
"packages": [ "packages": [
{ {
"name": "altcha-org/altcha", "name": "altcha-org/altcha",
@@ -3036,16 +3036,16 @@
}, },
{ {
"name": "livewire/livewire", "name": "livewire/livewire",
"version": "v3.7.3", "version": "v3.7.10",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/livewire/livewire.git", "url": "https://github.com/livewire/livewire.git",
"reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c" "reference": "0dc679eb4c8b4470cb12522b5927ef08ca2358bb"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/livewire/livewire/zipball/a5384df9fbd3eaf02e053bc49aabc8ace293fc1c", "url": "https://api.github.com/repos/livewire/livewire/zipball/0dc679eb4c8b4470cb12522b5927ef08ca2358bb",
"reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c", "reference": "0dc679eb4c8b4470cb12522b5927ef08ca2358bb",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -3100,7 +3100,7 @@
"description": "A front-end framework for Laravel.", "description": "A front-end framework for Laravel.",
"support": { "support": {
"issues": "https://github.com/livewire/livewire/issues", "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": [ "funding": [
{ {
@@ -3108,7 +3108,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-12-19T02:00:29+00:00" "time": "2026-02-09T22:49:33+00:00"
}, },
{ {
"name": "maize-tech/laravel-markable", "name": "maize-tech/laravel-markable",
@@ -11584,5 +11584,5 @@
"php": "^8.2" "php": "^8.2"
}, },
"platform-dev": {}, "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 '803325441942356059', // Tier-3
'803322725576736858', // Tier-2 '803322725576736858', // Tier-2
'802270568912519198', // Tier-1 '802270568912519198', // Tier-1
'802234830384267315' // admin '802234830384267315', // admin
], ],
'discord_bot_token' => env('DISCORD_BOT_TOKEN'), 'discord_bot_token' => env('DISCORD_BOT_TOKEN'),
+2 -2
View File
@@ -30,11 +30,11 @@ return [
// Asia Fallback (HTTP) // Asia Fallback (HTTP)
'asia_download_domain' => [ 'asia_download_domain' => [
'https://komako-b-ddl.musume-h.xyz' 'https://komako-b-ddl.musume-h.xyz',
], ],
'asia_stream_domain' => [ 'asia_stream_domain' => [
'https://komako-b-str.musume-h.xyz' 'https://komako-b-str.musume-h.xyz',
], ],
// Free 4k Downloads // Free 4k Downloads
+1 -1
View File
@@ -38,5 +38,5 @@ return [
'autoOrientation' => true, 'autoOrientation' => true,
'decodeAnimation' => true, 'decodeAnimation' => true,
'blendingColor' => 'ffffff', 'blendingColor' => 'ffffff',
] ],
]; ];
+2 -2
View File
@@ -149,12 +149,12 @@ return [
'title_jpn', 'title_jpn',
'slug', 'slug',
'description', 'description',
'tags' 'tags',
], ],
'sortableAttributes' => [ 'sortableAttributes' => [
'created_at', 'created_at',
'release_date', '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 '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_icon' => env('SWEET_ALERT_CONFIRM_DELETE_ICON', 'warning'),
'confirm_delete_show_loader_on_confirm' => env('SWEET_ALERT_CONFIRM_DELETE_SHOW_LOADER_ON_CONFIRM', true), '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; namespace Database\Factories;
use App\Models\Studios;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
@@ -5,8 +5,8 @@ use App\Models\Hentai;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
@@ -22,8 +22,7 @@ return new class extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('gallery', function (Blueprint $table) Schema::table('gallery', function (Blueprint $table) {
{
$table->dropIndex(['episode_id', 'hentai_id']); $table->dropIndex(['episode_id', 'hentai_id']);
}); });
} }
@@ -2,8 +2,8 @@
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
@@ -29,7 +29,7 @@ return new class extends Migration
Downloads::create([ Downloads::create([
'episode_id' => $episode->id, 'episode_id' => $episode->id,
'type' => 'FHD', 'type' => 'FHD',
'url' => $episode->download_url 'url' => $episode->download_url,
]); ]);
} }
@@ -38,7 +38,7 @@ return new class extends Migration
Downloads::create([ Downloads::create([
'episode_id' => $episode->id, 'episode_id' => $episode->id,
'type' => 'FHDi', 'type' => 'FHDi',
'url' => $episode->download_url_interpolated 'url' => $episode->download_url_interpolated,
]); ]);
} }
@@ -47,7 +47,7 @@ return new class extends Migration
Downloads::create([ Downloads::create([
'episode_id' => $episode->id, 'episode_id' => $episode->id,
'type' => 'UHD', 'type' => 'UHD',
'url' => $episode->download_url_4k 'url' => $episode->download_url_4k,
]); ]);
} }
} }
@@ -2,7 +2,6 @@
use App\Models\Episode; use App\Models\Episode;
use App\Models\Hentai; use App\Models\Hentai;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -18,7 +18,7 @@ return new class extends Migration
// Update the title_search field with sanitized titles // Update the title_search field with sanitized titles
DB::table('episodes')->update([ 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\Playlist;
use App\Models\PlaylistEpisode; use App\Models\PlaylistEpisode;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@@ -1,11 +1,10 @@
<?php <?php
use App\Models\Downloads; use App\Models\Downloads;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
@@ -14,10 +13,10 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
# Delete entries with "#" as URL // Delete entries with "#" as URL
Downloads::where('url', '#')->delete(); Downloads::where('url', '#')->delete();
# Remove duplicate entries // Remove duplicate entries
$duplicates = DB::table('downloads') $duplicates = DB::table('downloads')
->select('episode_id', 'type', DB::raw('COUNT(*) as count')) ->select('episode_id', 'type', DB::raw('COUNT(*) as count'))
->groupBy('episode_id', 'type') ->groupBy('episode_id', 'type')
@@ -1,7 +1,6 @@
<?php <?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
@@ -46,7 +46,6 @@ return new class extends Migration
$table->rememberToken()->after('password'); $table->rememberToken()->after('password');
}); });
/** /**
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* Fix Discord Profile Pictures * 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. * The oauth package by socialite now returns a full url of the avatar.
* Meaning all the old entries have to be fixed. * 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; $isGif = preg_match('/a_.+/m', $user->discord_avatar) === 1;
$extension = $isGif ? 'gif' : 'webp'; $extension = $isGif ? 'gif' : 'webp';
$user->discord_avatar = sprintf('https://cdn.discordapp.com/avatars/%s/%s.%s', $user->id, $user->discord_avatar, $extension); $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 App\Models\Playlist;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
@@ -69,7 +69,7 @@ return new class extends Migration
// Re-Add foreign key constraint and index // Re-Add foreign key constraint and index
Schema::table('comments', function (Blueprint $table) { Schema::table('comments', function (Blueprint $table) {
$table->foreign('child_id')->references('id')->on('comments')->onDelete('cascade'); $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 // Migrate supporters
DB::table('users')->where('is_patreon', 1)->update([ DB::table('users')->where('is_patreon', 1)->update([
'roles' => DB::raw("JSON_ARRAY('supporter')") 'roles' => DB::raw("JSON_ARRAY('supporter')"),
]); ]);
// Migrate banned // Migrate banned
DB::table('users')->where('is_banned', 1)->update([ DB::table('users')->where('is_banned', 1)->update([
'roles' => DB::raw("JSON_ARRAY('banned')") 'roles' => DB::raw("JSON_ARRAY('banned')"),
]); ]);
// Migrate admins // Migrate admins
DB::table('users')->where('is_admin', 1)->update([ DB::table('users')->where('is_admin', 1)->update([
'roles' => DB::raw("JSON_ARRAY('admin')") 'roles' => DB::raw("JSON_ARRAY('admin')"),
]); ]);
// Drop columns // 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 public function run(): void
{ {
$this->call([ $this->call([
DeletedUserSeeder::class DeletedUserSeeder::class,
]); ]);
} }
} }
+1 -3
View File
@@ -3,11 +3,9 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\User; use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DeletedUserSeeder extends Seeder class DeletedUserSeeder extends Seeder
{ {
+6 -5
View File
@@ -4,6 +4,7 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hstream",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@jellyfin/libass-wasm": "^4.1.1", "@jellyfin/libass-wasm": "^4.1.1",
@@ -1263,14 +1264,14 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.13.2", "version": "1.13.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.11",
"form-data": "^4.0.4", "form-data": "^4.0.5",
"proxy-from-env": "^1.1.0" "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') }} <i class="fa-brands fa-discord"></i> {{ __('nav.our-discord-server') }}
</x-dropdown-link> </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> <x-dropdown-link>
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<p class="cursor-default">{{ __('nav.theme') }}</p> <p class="cursor-default">{{ __('nav.theme') }}</p>
@@ -12,7 +12,7 @@
@else @else
<p class="text-lg">Episode {{ $episodeNumber }}</p> <p class="text-lg">Episode {{ $episodeNumber }}</p>
@endif @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> <p class="text-xs" id="count-{{ $downloadId }}">Downloaded {{ $downloadCount }} times</p>
</div> </div>
</button> </button>
@@ -235,6 +235,11 @@
'/' . '/' .
$expire; $expire;
} }
$fileExtension = "HEVC";
if (str_contains($download->url, 'AV1')) {
$fileExtension = "AV1";
}
@endphp @endphp
<a href="{{ $downloadURL }}" wire:click="clicked({{ $download->id }})" <a href="{{ $downloadURL }}" wire:click="clicked({{ $download->id }})"
download> download>
@@ -249,7 +254,7 @@
</svg> </svg>
</div> </div>
<div class="flex flex-col text-center w-full"> <div class="flex flex-col text-center w-full">
<p class="text-xs">HEVC MKV</p> <p class="text-xs">{{ $fileExtension }} MKV</p>
</div> </div>
</button> </button>
</a> </a>
@@ -1,4 +1,10 @@
<a href="{{ $dldomains[array_rand($dldomains)] }}/{{ $hdl->getDownloadByType('FHD')->url }}"> <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]"> <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"> <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> <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>
<div class="w-32 text-center"> <div class="w-32 text-center">
<p class="px-5 text-sm row-span-2">Episode {{ $hdl->episode }}</p> <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> </div>
</button> </button>
</a> </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> <div>
<label for="live-search" <div class="mx-auto max-w-5xl px-4 space-y-6">
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>
<input wire:model.live.debounce.600ms="commentSearch" type="search" id="live-search" <!-- Filters -->
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" <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">
placeholder="Search comment..." required>
<div class="absolute right-0 top-[11px]" wire:loading> <!-- Search -->
<svg aria-hidden="true" <div class="relative flex-1">
class="inline w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-pink-600" <input
viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"> wire:model.live.debounce.500ms="commentSearch"
<path type="search"
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" placeholder="Search comments..."
fill="currentColor" /> 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"
<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" <div class="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center">
fill="currentFill" /> <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> </svg>
</div> </div>
</div> </div>
</div>
<!-- Ordering --> <!-- Order -->
<div> <select
<div class="relative right-2 left-0 sm:left-2 transition-all"> wire:model.live="order"
<div class="absolute inset-y-0 left-2 flex items-center pl-3 pointer-events-none"> 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]"
<i class="fa-solid fa-sort text-gray-500 dark:text-gray-400"></i> >
</div> <option value="created_at_desc">Newest</option>
<select wire:model.live="order" <option value="created_at_asc">Oldest</option>
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>
</select> </select>
</div> </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 --> <!-- Comments -->
<div class="w-full text-left text-sm font-light"> <div class="space-y-4">
<!-- Header --> @forelse ($comments as $comment)
<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">
<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 @php
$hentai = \App\Models\Hentai::find($comment->commentable_id); $model = $comment->commentable;
$episode = $hentai->episodes->first(); $episode = $model instanceof \App\Models\Episode
? $model
: $model->episodes->first();
$url = route('hentai.index', ['title' => $model->slug]);
@endphp @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 class="bg-white/40 dark:bg-neutral-950/40 backdrop-blur rounded-xl shadow hover:shadow-lg transition overflow-hidden">
</div>
@endif <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> </div>
<!-- Body --> <!-- Content -->
<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"> <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() !!} {!! $comment->presenter()->markdownBody() !!}
</div> </div>
<div class="space-x-2 sm:mr-2 w-24"> <!-- Meta -->
<span class="text-gray-500 dark:text-gray-300 font-medium"> <div class="flex items-center justify-between mt-3 text-xs text-gray-700 dark:text-gray-400">
<span>
{{ $comment->presenter()->relativeCreatedAt() }} {{ $comment->presenter()->relativeCreatedAt() }}
</span> </span>
<span class="text-rose-600 font-medium group-hover:underline">
View comment
</span>
</div> </div>
</div> </div>
@endforeach
</div> </div>
</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>
</div> </div>
@endforelse
</div> </div>
<!-- Pagination -->
<div>
{{ $comments->links('pagination::tailwind') }} {{ $comments->links('pagination::tailwind') }}
</div> </div>
</div>
</div> </div>

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