This commit is contained in:
2025-09-18 15:31:27 +02:00
commit 2abba0c2b7
406 changed files with 31879 additions and 0 deletions

18
app/Models/Alert.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Alert extends Model
{
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'text',
'type',
];
}

21
app/Models/Contact.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Contact extends Model
{
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'id',
'name',
'email',
'subject',
'message',
];
}

52
app/Models/Downloads.php Normal file
View File

@@ -0,0 +1,52 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Downloads extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'episode_id',
'type',
'url',
];
/**
* Belongs to an episode
*/
public function episode()
{
return $this->belongsTo(Episode::class);
}
/**
* Convert bytes to human readable format
*/
private static function bytesToHuman($bytes)
{
$units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
for ($i = 0; $bytes > 1024; $i++) {
$bytes /= 1024;
}
return round($bytes, 2) . ' ' . $units[$i];
}
/**
* Returns the human readable form of the file size
*/
public function getFileSize(): ?string
{
return $this->size === null ? null : self::bytesToHuman($this->size);
}
}

215
app/Models/Episode.php Normal file
View File

@@ -0,0 +1,215 @@
<?php
namespace App\Models;
use App\Models\Downloads;
use App\Models\PopularMonthly;
use App\Models\PopularWeekly;
use App\Models\PopularDaily;
use Conner\Tagging\Taggable;
use Laravelista\Comments\Commentable;
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\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Episode extends Model implements Sitemapable
{
use Commentable, Markable, Taggable;
use HasFactory;
protected static $marks = [
Like::class
];
/**
* Get the studio for the Hentai.
*/
public function studio(): BelongsTo
{
return $this->belongsTo(Studios::class, 'studios_id');
}
/**
* Get the hentai for the episode.
*/
public function hentai(): BelongsTo
{
return $this->belongsTo(Hentai::class, 'hentai_id');
}
/**
* Get the subtitles for the episode.
*/
public function subtitles(): HasMany
{
return $this->hasMany(EpisodeSubtitle::class);
}
/**
* Has a Gallery.
*/
public function gallery(): HasMany
{
return $this->hasMany(Gallery::class);
}
/**
* Has many Downloads.
*/
public function downloads(): HasMany
{
return $this->hasMany(Downloads::class);
}
/**
* Increment View Count.
*/
public function incrementViewCount(): bool
{
$this->view_count++;
return $this->save();
}
/**
* Increment Popular Count.
*/
public function incrementPopularCount(): void
{
PopularDaily::create(['episode_id' => $this->id]);
PopularWeekly::create(['episode_id' => $this->id]);
PopularMonthly::create(['episode_id' => $this->id]);
}
/**
* Get cached view count
*/
public function viewCount(): int
{
return cache()->remember('episodeViews' . $this->id, 300, fn() => $this->view_count);
}
/**
* Get view count in a human readable way
*/
public function viewCountFormatted(): string
{
if ($this->viewCount() < 1000) {
return $this->viewCount();
}
$units = ['k', 'M'];
$index = floor(log($this->viewCount(), 1000));
$shortNumber = $this->viewCount() / pow(1000, $index);
return round($shortNumber, 0) . $units[$index - 1];
}
/**
* Get cached like count
*/
public function likeCount(): int
{
return cache()->remember('episodeLikes' . $this->id, 300, fn() => $this->likes->count());
}
/**
* Get cached comment count
*/
public function commentCount(): int
{
return cache()->remember('episodeComments' . $this->id, 300, fn() => $this->comments->count());
}
public function getProblematicTags(): string
{
$problematicTags = ['Gore', 'Scat', 'Horror'];
$problematicResults = '';
foreach ($problematicTags as $pTag) {
if (! $this->tags->contains('name', $pTag)) {
continue;
}
if (! empty($problematicResults)) {
$problematicResults .= ' + ';
}
$problematicResults .= $pTag;
}
return $problematicResults;
}
/**
* Check if episode contains loli / shota tag
*/
public function isLoliOrShota(): bool
{
$problematicTags = ['Loli', 'Shota'];
return Cache::remember(
"episode:{$this->id}:has_problematic_tags",
now()->addMinutes(1440),
fn () => $this->tags->pluck('name')->intersect($problematicTags)->isNotEmpty()
);
}
/**
* If episode has machine translated subtitles
*/
public function hasAutoTrans(): bool
{
return cache()->remember('mt' . $this->id, 900, fn() => $this->subtitles()->exists());
}
public function is48Fps(): bool
{
return cache()->remember('48fps' . $this->id, 900, fn() => $this->interpolated);
}
public function isUHD48Fps(): bool
{
return cache()->remember('48fpsUHD' . $this->id, 900, fn() => $this->interpolated_uhd);
}
public function getResolution(): string
{
if ($this->isUHD48Fps()) {
return '4k | 4k 48fps | FHD 48fps';
}
return $this->is48Fps() ? '4k | FHD 48fps' : '4k';
}
public function userWatched(int $user_id): bool
{
return cache()->remember('user' . $user_id . 'watched' . $this->id, 300, fn() => Watched::where('user_id', $user_id)->where('episode_id', $this->id)->exists());
}
public function watched(): HasMany
{
return $this->hasMany(Watched::class);
}
public function getDownloadByType(string $type): Downloads | null
{
$cacheKey = "episode_{$this->id}_download_{$type}";
return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($type) {
return $this->downloads()->where('type', $type)->first();
});
}
public function toSitemapTag(): Url | string | array
{
return Url::create(route('hentai.index', $this->slug))
->setLastModificationDate(Carbon::create($this->created_at));
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class EpisodeSubtitle extends Model
{
use HasFactory;
/**
* Indicates If The Model Should Be Timestamped.
*
* @var bool
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'episode_id',
'subtitle_id',
];
/**
* Get the according subtitle.
*/
public function subtitle()
{
return $this->belongsTo(Subtitle::class, 'subtitle_id');
}
}

18
app/Models/Gallery.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Gallery extends Model
{
public $table = 'gallery';
/**
* Belongs To Episode.
*/
public function episode()
{
return $this->belongsTo(Episode::class);
}
}

72
app/Models/Hentai.php Normal file
View File

@@ -0,0 +1,72 @@
<?php
namespace App\Models;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Conner\Tagging\Taggable;
use Laravelista\Comments\Commentable;
class Hentai extends Model implements Sitemapable
{
use Commentable, Taggable;
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'slug',
'description',
];
public function episodes()
{
return $this->hasMany(Episode::class, 'hentai_id');
}
public function torrents()
{
return $this->hasMany(Torrents::class, 'hentai_id');
}
public function title(): String
{
return $this->episodes->first()->title;
}
/**
* Has a Gallery.
*/
public function gallery()
{
return $this->hasMany(Gallery::class);
}
/**
* Check if hentai contains loli / shota tag
*/
public function isLoliOrShota(): bool
{
$problematicTags = ['Loli', 'Shota'];
return Cache::remember(
"episode:{$this->id}:has_problematic_tags",
now()->addMinutes(1440),
fn () => $this->episodes[0]->tags->pluck('name')->intersect($problematicTags)->isNotEmpty()
);
}
public function toSitemapTag(): Url | string | array
{
return Url::create(route('hentai.index', $this->slug))
->setLastModificationDate(Carbon::create($this->created_at));
}
}

25
app/Models/Playlist.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;
class Playlist extends Model
{
/**
* Belongs To A User.
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Has Many Episodes.
*/
public function episodes(): HasMany
{
return $this->hasMany(PlaylistEpisode::class);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
class PlaylistEpisode extends Model
{
/**
* Indicates If The Model Should Be Timestamped.
*
* @var bool
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'playlist_id',
'episode_id',
'position',
];
/**
* Belongs To A Episode.
*/
public function episode(): BelongsTo
{
return $this->belongsTo(Episode::class);
}
/**
* Belongs To A Playlist.
*/
public function playlist(): BelongsTo
{
return $this->belongsTo(Playlist::class);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PopularDaily extends Model
{
public $table = 'popular_daily';
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [ 'episode_id' ];
/**
* Get the Episode.
*/
public function episode()
{
return $this->belongsTo(Episode::class, 'episode_id');
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PopularMonthly extends Model
{
public $table = 'popular_monthly';
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [ 'episode_id' ];
/**
* Get the Episode.
*/
public function episode()
{
return $this->belongsTo(Episode::class, 'episode_id');
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PopularWeekly extends Model
{
public $table = 'popular_weekly';
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [ 'episode_id' ];
/**
* Get the Episode.
*/
public function episode()
{
return $this->belongsTo(Episode::class, 'episode_id');
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
class SiteBackground extends Model
{
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'date_start',
'date_end',
'default'
];
/**
* Returns the current IDs of active wallpaper
*/
public function getImages(): ? \Illuminate\Support\Collection
{
$now = Carbon::now();
$byDates = $this->whereDate('date_start', '<=', $now)->whereDate('date_end', '>=', $now)->get()->pluck('id');
$default = $this->where('default', true)->get()->pluck('id');
return $byDates->isEmpty() ? $default : $byDates;
}
}

30
app/Models/Studios.php Normal file
View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;
class Studios extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'name',
'slug',
];
/**
* Get the tags for the Hentai.
*/
public function hentais(): HasMany
{
return $this->hasMany(Hentai::class);
}
}

28
app/Models/Subtitle.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Subtitle extends Model
{
use HasFactory;
/**
* Indicates If The Model Should Be Timestamped.
*
* @var bool
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'name',
'slug',
];
}

22
app/Models/Torrents.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Torrents extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'hentai_id',
'torrent_url',
'episodes',
];
}

108
app/Models/User.php Normal file
View File

@@ -0,0 +1,108 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Jakyeru\Larascord\Traits\InteractsWithDiscord;
use Laravelista\Comments\Commenter;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Support\Facades\DB;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, InteractsWithDiscord, Commenter;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'id',
'username',
'global_name',
'discriminator',
'email',
'avatar',
'verified',
'banner',
'banner_color',
'accent_color',
'locale',
'mfa_enabled',
'premium_type',
'public_flags',
'roles',
'is_banned',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array
*/
protected $hidden = [
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'username' => 'string',
'global_name' => 'string',
'discriminator' => 'string',
'email' => 'string',
'avatar' => 'string',
'verified' => 'boolean',
'banner' => 'string',
'banner_color' => 'string',
'accent_color' => 'string',
'locale' => 'string',
'mfa_enabled' => 'boolean',
'premium_type' => 'integer',
'public_flags' => 'integer',
'roles' => 'json',
'tag_blacklist' => 'array',
];
/**
* Has Many Playlists.
*/
public function playlists(): HasMany
{
return $this->hasMany(Playlist::class);
}
/**
* Has Many Watched Episodes.
*/
public function watched(): HasMany
{
return $this->hasMany(Watched::class);
}
/**
* Has Many Watched Episodes.
*/
public function likes(): int
{
return DB::table('markable_likes')->where('user_id', $this->id)->count();
}
/**
* Has Many Comments.
*/
public function commentCount(): int
{
return DB::table('comments')->where('commenter_id', $this->id)->count();
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
class UserDownload extends Model
{
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'user_id',
'episode_id',
'interpolated',
];
/**
* Belongs To A User.
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Belongs To A Episode.
*/
public function episode(): BelongsTo
{
return $this->belongsTo(Episode::class);
}
}

34
app/Models/Watched.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
class Watched extends Model
{
public $table = 'watched';
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = ['episode_id', 'user_id'];
/**
* Get the Episode.
*/
public function episode(): BelongsTo
{
return $this->belongsTo(Episode::class, 'episode_id');
}
/**
* Get the User.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}