Files
cv4/app/Http/Controllers/ComicController.php
2024-12-31 20:17:35 -05:00

404 lines
13 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Helper\ZhConversion;
use App\Models\Author;
use App\Models\Chapter;
use App\Models\Comic;
use App\Models\Image;
use App\Remote\CopyManga;
use App\Remote\ImageFetcher;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Application;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response as IlluminateHttpResponse;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
use Inertia\Response;
class ComicController extends Controller
{
public function __construct(private readonly CopyManga $copyManga, private readonly ZhConversion $zhConversion)
{
}
protected function scToZh(mixed $string): mixed
{
if (gettype($string) !== 'string') {
$string = serialize($string);
return unserialize(str_replace(array_keys($this->zhConversion::ZH_TO_HANT), array_values($this->zhConversion::ZH_TO_HANT), $string));
}
return str_replace(array_keys($this->zhConversion::ZH_TO_HANT), array_values($this->zhConversion::ZH_TO_HANT), $string);
}
/**
* Show user favourites
*
* @param Request $request
* @return Response
*/
public function favourites(Request $request): Response
{
$favourites = $this->scToZh($request->user()->favourites()->with(['authors'])->orderBy('upstream_updated_at', 'desc')->get());
return Inertia::render('Comic/Favourites', [
'favourites' => $favourites,
]);
}
/**
* Toggle user favourites
*
* @param Request $request
* @return JsonResponse
* @throws GuzzleException
*/
public function postFavourite(Request $request): JsonResponse
{
try {
// Get pathname to comic_id
$comic = Comic::where('pathword', $request->pathword)->firstOrFail();
} catch (ModelNotFoundException $e) {
// Fetch from remote
$remoteComic = $this->copyManga->comic($request->pathword);
$comic = new Comic;
$comic->pathword = $remoteComic['comic']['path_word'];
$comic->name = $remoteComic['comic']['name'];
$comic->cover = $remoteComic['comic']['cover'];
$comic->upstream_updated_at = $remoteComic['comic']['datetime_updated'];
$comic->uuid = $remoteComic['comic']['uuid'];
$comic->alias = explode(',', $remoteComic['comic']['alias']);
$comic->description = $remoteComic['comic']['brief'];
$comic->metadata = $remoteComic;
$comic->save();
}
// Set favourite
if ($request->user()->favourites()->where('comic_id', $comic->id)->exists()) {
$request->user()->favourites()->detach($comic->id);
} else {
$request->user()->favourites()->attach($comic->id);
}
return response()->json($request->user()->favourites()->get(['pathword'])->pluck('pathword'));
}
/**
* Show image via proxy
*
* @param Request $request
* @param string $url
* @return ResponseFactory|Application|IlluminateHttpResponse
* @throws ConnectionException
*/
public function image(Request $request, string $url): ResponseFactory|Application|IlluminateHttpResponse
{
// TODO: Ref check and make it require auth
$fetcher = new ImageFetcher(base64_decode($url));
return response($fetcher->fetch())->withHeaders([
'Content-Type' => $fetcher->getMimeType()->value,
'Cache-Control' => 'max-age=604800',
]);
}
// Internal function for Upsert comics to db
protected function comicsUpsert($comics): void
{
// Prep the array for upsert
$comicsUpsertArray = [];
$authorsUpsertArray = [];
foreach ($comics['list'] as $comic) {
$comicsUpsertArray[] = [
'pathword' => $comic['path_word'],
'uuid' => '',
'name' => $comic['name'],
'alias' => '{}',
'description' => '',
'cover' => $comic['cover'],
'upstream_updated_at' => $comic['datetime_updated'] ?? null,
];
foreach ($comic['author'] as $author) {
$authorsUpsertArray[] = [
'name' => $author['name']
];
}
}
// Do an upsert for comics
Comic::upsert($comicsUpsertArray, uniqueBy: 'pathword', update: ['upstream_updated_at']);
Author::upsert($authorsUpsertArray, uniqueBy: 'name');
// Had to do a second pass to insert the relationships
foreach ($comics['list'] as $comic) {
// Get the comic id
$comicObj = Comic::where('pathword', $comic['path_word'])->first();
foreach ($comic['author'] as $author) {
$authorObj = Author::where('name', $author['name'])->first();
$comicObj->authors()->sync($authorObj);
}
}
}
/**
* Index / Tags for comic listing
*
* @param Request $request
* @return Response
* @throws GuzzleException
*/
public function index(Request $request): Response
{
$params = [];
if ($request->has('tag')) {
$params['theme'] = $request->get('tag');
}
$comics = $this->copyManga->comics(30, $request->header('offset', 0), $request->get('top', 'all'), $params);
$this->comicsUpsert($comics);
return Inertia::render('Comic/Index', [
'comics' => $this->scToZh($comics),
'offset' => $request->header('offset', 0)
]);
}
/**
* Author for comic listing
*
* @param Request $request
* @param string $author
* @return Response
* @throws GuzzleException
*/
public function author(Request $request, string $author): Response
{
$params = [];
$params['author'] = $author;
$comics = $this->copyManga->comics(30, $request->header('offset', 0), $request->get('top', 'all'), $params);
$this->comicsUpsert($comics);
return Inertia::render('Comic/Index', [
'comics' => $this->scToZh($comics),
'offset' => $request->header('offset', 0)
]);
}
/**
* Search for comic listing
*
* @param Request $request
* @param string $search
* @return Response
* @throws GuzzleException
*/
public function search(Request $request, string $search): Response
{
$comics = $this->copyManga->search($search, 30, $request->header('offset', 0));
// Search API is limited, no upsert
return Inertia::render('Comic/Index', [
'comics' => $this->scToZh($comics),
'offset' => $request->header('offset', 0)
]);
}
/**
* Show comic details and chapters
*
* @param Request $request
* @param string $pathword
* @return RedirectResponse|Response
* @throws GuzzleException
*/
public function chapters(Request $request, string $pathword = ''): RedirectResponse|Response
{
if ($pathword === 'installHook.js.map') {
return to_route('comics.index');
}
$comic = $this->copyManga->comic($pathword);
$chapters = $this->copyManga->chapters($pathword, 200, $request->header('offset', 0), [], $request->get('group', 'default'));
// Get the comic object and fill other parameters
try {
$comicObject = Comic::where('pathword', $pathword)->firstOrFail();
$comicObject->uuid = $comic['comic']['uuid'];
$comicObject->alias = explode(',', $comic['comic']['alias']);
$comicObject->description = $comic['comic']['brief'];
$comicObject->metadata = $comic;
$comicObject->save();
} catch (ModelNotFoundException $e) {
$comicObject = Comic::create([
'name' => $comic['comic']['name'],
'pathword' => $comic['comic']['path_word'],
'cover' => $comic['comic']['cover'],
'upstream_updated_at' => $comic['comic']['datetime_updated'],
'uuid' => $comic['comic']['uuid'],
'alias' => explode(',', $comic['comic']['alias']),
'description' => $comic['comic']['brief'],
'metadata' => $comic
]);
}
// Get the authors and update the pathword
foreach ($comic['comic']['author'] as $author) {
$authorObj = Author::where('name', $author['name'])->whereNull('pathword')->first();
if ($authorObj) {
// Do nothing if pathword already exist
$authorObj->pathword = $author['path_word'];
$authorObj->save();
}
}
// Do the Chapters
// Prep the array for upsert
$arrayForUpsert = [];
foreach ($chapters['list'] as $chapter) {
$arrayForUpsert[] = [
'comic_id' => $comicObject->id,
'chapter_uuid' => $chapter['uuid'],
'name' => $chapter['name'],
'order' => $chapter['index'],
'upstream_created_at' => $chapter['datetime_created'],
'metadata' => json_encode($chapter),
];
}
// Do an upsert
Chapter::upsert($arrayForUpsert, uniqueBy: 'chapter_uuid');
// Get history
$histories = $request->user()->readingHistories()->where('reading_histories.comic_id', $comicObject->id)
->distinct()->select('chapter_uuid')->get()->pluck('chapter_uuid');
return Inertia::render('Comic/Chapters', [
'comic' => $this->scToZh($comic),
'chapters' => $this->scToZh($chapters),
'histories' => $histories,
'offset' => $request->header('offset', 0)
]);
}
/**
* Read a chapter, showing images
*
* @param Request $request
* @param string $pathword
* @param string $uuid
* @return Response
* @throws GuzzleException
*/
public function read(Request $request, string $pathword = '', string $uuid = ''): Response
{
$comic = $this->copyManga->comic($pathword);
$chapter = $this->copyManga->chapter($pathword, $uuid);
// Get the authors and update the pathword
foreach ($comic['comic']['author'] as $author) {
$authorObj = Author::where('name', $author['name'])->whereNull('pathword')->first();
if ($authorObj) {
// Do nothing if pathword already exist
$authorObj->pathword = $author['path_word'];
$authorObj->save();
}
}
$chapterObj = Chapter::where('chapter_uuid', $chapter['chapter']['uuid'])->first();
$comicObj = Comic::where('pathword', $pathword)->first();
$arrayForUpsert = [];
foreach ($chapter['sorted'] as $k => $image) {
$metadata = $chapter;
unset($metadata['sorted']);
unset($metadata['chapter']['contents'], $metadata['chapter']['words']);
$arrayForUpsert[] = [
'comic_id' => $comicObj->id,
'chapter_id' => $chapterObj->id,
'order' => $k,
'url' => $image['url'],
'metadata' => json_encode($metadata),
];
}
// Do an upsert
Image::upsert($arrayForUpsert, uniqueBy: 'url');
// Update history
$request->user()->readingHistories()->attach($chapterObj->id, ['comic_id' => $comicObj->id]);
return Inertia::render('Comic/Read', [
'comic' => $this->scToZh($comic),
'chapter' => $this->scToZh($chapter),
]);
}
/**
* Show user read histories
*
* @param Request $request
* @return Response
*/
public function histories(Request $request): Response
{
// Get history
$histories = $request->user()->readingHistories()->with(['comic:id,name,pathword'])->orderByDesc('reading_histories.created_at')
->select(['reading_histories.id as hid', 'reading_histories.created_at as read_at', 'chapters.comic_id', 'chapters.name'])->paginate(50)->toArray();
return Inertia::render('Comic/Histories', [
'histories' => $this->scToZh($histories)
]);
}
/**
* Delete user read histories by submitting ids
*
* @param Request $request
* @return RedirectResponse
*/
public function destroyHistories(Request $request): RedirectResponse
{
if (!is_array($request->get('ids')) && $request->get('ids') === 'all') {
$histories = $request->user()->readingHistories()->delete();
} else {
$histories = $request->user()->readingHistories()->whereIn('reading_histories.id', $request->get('ids'))->delete();
}
return redirect()->route('comics.histories');
}
/**
* Fetch tags
*
* @return JsonResponse
* @throws GuzzleException
*/
public function tags()
{
// TODO
$tags = $this->scToZh($this->copyManga->tags());
Cache::forever('tags', $tags);
return response()->json($tags);
}
}