mirror of
https://github.com/jikan-me/jikan-rest.git
synced 2025-02-20 11:23:35 +08:00
Merge branch 'master' into irfan-dahir-patch-1
This commit is contained in:
commit
9d572e4b8e
@ -69,7 +69,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push by digest
|
- name: Build and push by digest
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
|
env:
|
||||||
|
DOCKER_BUILD_NO_SUMMARY: true
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
|
24
COMMANDS.MD
24
COMMANDS.MD
@ -14,14 +14,15 @@ For an entire list of commands, you can run `php artisan list`
|
|||||||
- [Indexer](#indexer)
|
- [Indexer](#indexer)
|
||||||
- [Anime](#indexer-anime)
|
- [Anime](#indexer-anime)
|
||||||
- [Manga](#indexer-manga)
|
- [Manga](#indexer-manga)
|
||||||
|
- [Incremental](#indexer-incremental)
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### Serve
|
### Serve
|
||||||
Command: `serve`
|
Command: `serve`
|
||||||
Example: `php artisan serve`
|
Example: `php artisan serve`
|
||||||
|
|
||||||
Serve the application on the PHP development server
|
Serve the application on the PHP development server
|
||||||
|
|
||||||
### Queue
|
### Queue
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ Example: `cache:method queue`
|
|||||||
Since v4 uses MongoDB as a means to index cache on some endpoints, having a built cache is important since it
|
Since v4 uses MongoDB as a means to index cache on some endpoints, having a built cache is important since it
|
||||||
works best for endpoints like search or top.
|
works best for endpoints like search or top.
|
||||||
|
|
||||||
`Indexer:Anime` uses [https://github.com/seanbreckenridge/mal-id-cache](https://github.com/seanbreckenridge/mal-id-cache) to fetch available MAL IDs and indexes them.
|
`Indexer:Anime` uses [https://github.com/purarue/mal-id-cache](https://github.com/purarue/mal-id-cache) to fetch available MAL IDs and indexes them.
|
||||||
|
|
||||||
This function only needs to be run once. Any entry's cache updating will automatically be taken care of if it's expired, and a client makes a request for that entry.
|
This function only needs to be run once. Any entry's cache updating will automatically be taken care of if it's expired, and a client makes a request for that entry.
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ This translates to running entries that previously failed to index or update, in
|
|||||||
Since v4 uses MongoDB as a means to index cache on some endpoints, having a built cache is important since it
|
Since v4 uses MongoDB as a means to index cache on some endpoints, having a built cache is important since it
|
||||||
works best for endpoints like search or top.
|
works best for endpoints like search or top.
|
||||||
|
|
||||||
`Indexer:Manga` uses [https://github.com/seanbreckenridge/mal-id-cache](https://github.com/seanbreckenridge/mal-id-cache) to fetch available MAL IDs and indexes them.
|
`Indexer:Manga` uses [https://github.com/purarue/mal-id-cache](https://github.com/purarue/mal-id-cache) to fetch available MAL IDs and indexes them.
|
||||||
|
|
||||||
This function only needs to be run once. Any entry's cache updating will automatically be taken care of if it's expired, and a client makes a request for that entry.
|
This function only needs to be run once. Any entry's cache updating will automatically be taken care of if it's expired, and a client makes a request for that entry.
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ This function only needs to be run once. Any entry's cache updating will automat
|
|||||||
|
|
||||||
Command:
|
Command:
|
||||||
```
|
```
|
||||||
indexer:anime
|
indexer:manga
|
||||||
{--failed : Run only entries that failed to index last time}
|
{--failed : Run only entries that failed to index last time}
|
||||||
{--resume : Resume from the last position}
|
{--resume : Resume from the last position}
|
||||||
{--reverse : Start from the end of the array}
|
{--reverse : Start from the end of the array}
|
||||||
@ -109,3 +110,16 @@ indexer:anime
|
|||||||
Example: `indexer:manga`
|
Example: `indexer:manga`
|
||||||
|
|
||||||
This simply translates to running the indexer without any additional configuration.
|
This simply translates to running the indexer without any additional configuration.
|
||||||
|
|
||||||
|
#### Indexer: Incremental
|
||||||
|
Incrementally indexes media entries from MAL.
|
||||||
|
This command will compare the latest version of MAL ids from the [mal_id_cache](https://github.com/purarue/mal-id-cache)
|
||||||
|
github repository and compares them with the downloaded ids from the previous run. If no ids found from the previous run, a full indexing session is started.
|
||||||
|
|
||||||
|
Command:
|
||||||
|
```
|
||||||
|
indexer:incremental {mediaType*}
|
||||||
|
{--failed : Run only entries that failed to index last time}
|
||||||
|
{--resume : Resume from the last position}
|
||||||
|
{--delay=3 : Set a delay between requests}
|
||||||
|
```
|
||||||
|
@ -52,7 +52,6 @@ For any additional help, join our [Discord server](http://discord.jikan.moe/).
|
|||||||
| TypeScript | [jikants](https://github.com/Julien-Broyard/jikants) by Julien Broyard<br>[jikan-client](https://github.com/javi11/jikan-client) by Javier Blanco<br>🆕 **(v4)** [jikan-ts](https://github.com/tutkli/jikan-ts) by Clara Castillo |
|
| TypeScript | [jikants](https://github.com/Julien-Broyard/jikants) by Julien Broyard<br>[jikan-client](https://github.com/javi11/jikan-client) by Javier Blanco<br>🆕 **(v4)** [jikan-ts](https://github.com/tutkli/jikan-ts) by Clara Castillo |
|
||||||
| PHP | [jikan-php](https://github.com/janvernieuwe/jikan-jikanPHP) by Jan Vernieuwe |
|
| PHP | [jikan-php](https://github.com/janvernieuwe/jikan-jikanPHP) by Jan Vernieuwe |
|
||||||
| .NET | 🆕 **(v4)** [Jikan.net](https://github.com/Ervie/jikan.net) by Ervie |
|
| .NET | 🆕 **(v4)** [Jikan.net](https://github.com/Ervie/jikan.net) by Ervie |
|
||||||
| Elixir | [JikanEx](https://github.com/seanbreckenridge/jikan_ex) by Sean Breckenridge |
|
|
||||||
| Go | 🆕 **(v4)** [jikan-go](https://github.com/darenliang/jikan-go) by Daren Liang<br>[jikan2go](https://github.com/nokusukun/jikan2go) by nokusukun |
|
| Go | 🆕 **(v4)** [jikan-go](https://github.com/darenliang/jikan-go) by Daren Liang<br>[jikan2go](https://github.com/nokusukun/jikan2go) by nokusukun |
|
||||||
| Ruby | [Jikan.rb](https://github.com/Zerocchi/jikan.rb) by Zerocchi |
|
| Ruby | [Jikan.rb](https://github.com/Zerocchi/jikan.rb) by Zerocchi |
|
||||||
| Dart | [jikan-dart](https://github.com/charafau/jikan-dart) by Rafal Wachol |
|
| Dart | [jikan-dart](https://github.com/charafau/jikan-dart) by Rafal Wachol |
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands\Indexer;
|
namespace App\Console\Commands\Indexer;
|
||||||
|
|
||||||
use App\Exceptions\Console\CommandAlreadyRunningException;
|
|
||||||
use App\Exceptions\Console\FileNotFoundException;
|
use App\Exceptions\Console\FileNotFoundException;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@ -67,7 +66,7 @@ class AnimeIndexer extends Command
|
|||||||
$index = (int)$index;
|
$index = (int)$index;
|
||||||
$delay = (int)$delay;
|
$delay = (int)$delay;
|
||||||
|
|
||||||
$this->info("Info: AnimeIndexer uses seanbreckenridge/mal-id-cache fetch available MAL IDs and updates/indexes them\n\n");
|
$this->info("Info: AnimeIndexer uses purarue/mal-id-cache fetch available MAL IDs and updates/indexes them\n\n");
|
||||||
|
|
||||||
if ($failed && Storage::exists('indexer/indexer_anime.failed')) {
|
if ($failed && Storage::exists('indexer/indexer_anime.failed')) {
|
||||||
$this->ids = $this->loadFailedMalIds();
|
$this->ids = $this->loadFailedMalIds();
|
||||||
@ -140,14 +139,14 @@ class AnimeIndexer extends Command
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
* @url https://github.com/seanbreckenridge/mal-id-cache
|
* @url https://github.com/purarue/mal-id-cache
|
||||||
*/
|
*/
|
||||||
private function fetchMalIds() : array
|
private function fetchMalIds() : array
|
||||||
{
|
{
|
||||||
$this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json...\n");
|
$this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/anime_cache.json...\n");
|
||||||
|
|
||||||
$ids = json_decode(
|
$ids = json_decode(
|
||||||
file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json'),
|
file_get_contents('https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/anime_cache.json'),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -74,14 +74,14 @@ class AnimeSweepIndexer extends Command
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
* @url https://github.com/seanbreckenridge/mal-id-cache
|
* @url https://github.com/purarue/mal-id-cache
|
||||||
*/
|
*/
|
||||||
private function fetchMalIds(): array
|
private function fetchMalIds(): array
|
||||||
{
|
{
|
||||||
$this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json...\n");
|
$this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/anime_cache.json...\n");
|
||||||
|
|
||||||
$ids = json_decode(
|
$ids = json_decode(
|
||||||
file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json'),
|
file_get_contents('https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/anime_cache.json'),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
228
app/Console/Commands/Indexer/IncrementalIndexer.php
Normal file
228
app/Console/Commands/Indexer/IncrementalIndexer.php
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Indexer;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class IncrementalIndexer extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private bool $cancelled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'indexer:incremental {mediaType*}
|
||||||
|
{--delay=3 : Set a delay between requests}
|
||||||
|
{--resume : Resume from the last position}
|
||||||
|
{--failed : Run only entries that failed to index last time}';
|
||||||
|
|
||||||
|
protected function promptForMissingArgumentsUsing(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'mediaType' => ['The media type to index.', 'Valid values: anime, manga']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getExistingIds(string $mediaType): array
|
||||||
|
{
|
||||||
|
$existingIdsHash = "";
|
||||||
|
$existingIdsRaw = "";
|
||||||
|
|
||||||
|
if (Storage::exists("indexer/incremental/$mediaType.json"))
|
||||||
|
{
|
||||||
|
$existingIdsRaw = Storage::get("indexer/incremental/$mediaType.json");
|
||||||
|
$existingIdsHash = sha1($existingIdsRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$existingIdsHash, $existingIdsRaw];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getIdsToFetch(string $mediaType): array
|
||||||
|
{
|
||||||
|
$idsToFetch = [];
|
||||||
|
[$existingIdsHash, $existingIdsRaw] = $this->getExistingIds($mediaType);
|
||||||
|
|
||||||
|
if ($this->cancelled)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$newIdsRaw = file_get_contents("https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/${mediaType}_cache.json");
|
||||||
|
$newIdsHash = sha1($newIdsRaw);
|
||||||
|
|
||||||
|
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||||
|
if ($this->cancelled)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($newIdsHash !== $existingIdsHash)
|
||||||
|
{
|
||||||
|
$newIds = json_decode($newIdsRaw, true);
|
||||||
|
$existingIds = json_decode($existingIdsRaw, true);
|
||||||
|
|
||||||
|
if (is_null($existingIds) || count($existingIds) === 0)
|
||||||
|
{
|
||||||
|
$idsToFetch = $newIds;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (["sfw", "nsfw"] as $t)
|
||||||
|
{
|
||||||
|
$idsToFetch[$t] = array_diff($existingIds[$t], $newIds[$t]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::put("indexer/incremental/$mediaType.json.tmp", $newIdsRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $idsToFetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFailedIdsToFetch(string $mediaType): array
|
||||||
|
{
|
||||||
|
return json_decode(Storage::get("indexer/incremental/{$mediaType}_failed.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fetchIds(string $mediaType, array $idsToFetch, bool $resume): void
|
||||||
|
{
|
||||||
|
$index = 0;
|
||||||
|
$success = [];
|
||||||
|
$failedIds = [];
|
||||||
|
$idCount = count($idsToFetch);
|
||||||
|
if ($resume && Storage::exists("indexer/incremental/{$mediaType}_resume.save"))
|
||||||
|
{
|
||||||
|
$index = (int)Storage::get("indexer/incremental/{$mediaType}_resume.save");
|
||||||
|
$this->info("Resuming from index: $index");
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = array_merge($idsToFetch['sfw'], $idsToFetch['nsfw']);
|
||||||
|
|
||||||
|
if ($index > 0 && !isset($ids[$index]))
|
||||||
|
{
|
||||||
|
$index = 0;
|
||||||
|
$this->warn('Invalid index; set back to 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::put("indexer/incremental/{$mediaType}_resume.save", 0);
|
||||||
|
|
||||||
|
$this->info("$idCount $mediaType entries available");
|
||||||
|
|
||||||
|
for ($i = $index; $i <= ($idCount - 1); $i++)
|
||||||
|
{
|
||||||
|
if ($this->cancelled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $ids[$index];
|
||||||
|
|
||||||
|
$url = env('APP_URL') . "/v4/$mediaType/$id";
|
||||||
|
$this->info("Indexing/Updating " . ($i + 1) . "/$idCount $url [MAL ID: $id]");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$response = json_decode(file_get_contents($url), true);
|
||||||
|
if (!isset($response['error']) || $response['status'] == 404)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->error("[SKIPPED] Failed to fetch $url - {$response['error']}");
|
||||||
|
}
|
||||||
|
catch (\Exception)
|
||||||
|
{
|
||||||
|
$this->warn("[SKIPPED] Failed to fetch $url");
|
||||||
|
$failedIds[] = $id;
|
||||||
|
Storage::put("indexer/incremental/$mediaType.failed", json_encode($failedIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
$success[] = $id;
|
||||||
|
Storage::put("indexer/incremental/{$mediaType}_resume.save", $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::delete("indexer/incremental/{$mediaType}_resume.save");
|
||||||
|
|
||||||
|
$this->info("--- Indexing of $mediaType is complete.");
|
||||||
|
$this->info(count($success) . ' entries indexed or updated.');
|
||||||
|
if (count($failedIds) > 0)
|
||||||
|
{
|
||||||
|
$this->info(count($failedIds) . ' entries failed to index or update. Re-run with --failed to requeue failed entries only.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalize the latest state
|
||||||
|
Storage::move("indexer/incremental/$mediaType.json.tmp", "indexer/incremental/$mediaType.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
// validate inputs
|
||||||
|
$validator = Validator::make(
|
||||||
|
[
|
||||||
|
'mediaType' => $this->argument('mediaType'),
|
||||||
|
'delay' => $this->option('delay'),
|
||||||
|
'resume' => $this->option('resume') ?? false,
|
||||||
|
'failed' => $this->option('failed') ?? false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'mediaType' => 'required|in:anime,manga',
|
||||||
|
'delay' => 'integer|min:1',
|
||||||
|
'resume' => 'bool|prohibited_with:failed',
|
||||||
|
'failed' => 'bool|prohibited_with:resume'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
$this->error($validator->errors()->toJson());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we want to handle signals from the OS
|
||||||
|
$this->trap([SIGTERM, SIGQUIT, SIGINT], fn () => $this->cancelled = true);
|
||||||
|
|
||||||
|
$resume = $this->option('resume') ?? false;
|
||||||
|
$onlyFailed = $this->option('failed') ?? false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var $mediaTypes array
|
||||||
|
*/
|
||||||
|
$mediaTypes = $this->argument("mediaType");
|
||||||
|
|
||||||
|
foreach ($mediaTypes as $mediaType)
|
||||||
|
{
|
||||||
|
$idsToFetch = [];
|
||||||
|
|
||||||
|
// if "--failed" option is specified just run the failed ones
|
||||||
|
if ($onlyFailed && Storage::exists("indexer/incremental/{$mediaType}_failed.json"))
|
||||||
|
{
|
||||||
|
$idsToFetch["sfw"] = $this->getFailedIdsToFetch($mediaType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$idsToFetch = $this->getIdsToFetch($mediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->cancelled)
|
||||||
|
{
|
||||||
|
return 127;
|
||||||
|
}
|
||||||
|
|
||||||
|
$idCount = count($idsToFetch);
|
||||||
|
if ($idCount === 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fetchIds($mediaType, $idsToFetch, $resume);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -67,7 +67,7 @@ class MangaIndexer extends Command
|
|||||||
$index = (int)$index;
|
$index = (int)$index;
|
||||||
$delay = (int)$delay;
|
$delay = (int)$delay;
|
||||||
|
|
||||||
$this->info("Info: MangaIndexer uses seanbreckenridge/mal-id-cache fetch available MAL IDs and updates/indexes them\n\n");
|
$this->info("Info: MangaIndexer uses purarue/mal-id-cache fetch available MAL IDs and updates/indexes them\n\n");
|
||||||
|
|
||||||
if ($failed && Storage::exists('indexer/indexer_manga.failed')) {
|
if ($failed && Storage::exists('indexer/indexer_manga.failed')) {
|
||||||
$this->ids = $this->loadFailedMalIds();
|
$this->ids = $this->loadFailedMalIds();
|
||||||
@ -140,14 +140,14 @@ class MangaIndexer extends Command
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
* @url https://github.com/seanbreckenridge/mal-id-cache
|
* @url https://github.com/purarue/mal-id-cache
|
||||||
*/
|
*/
|
||||||
private function fetchMalIds() : array
|
private function fetchMalIds() : array
|
||||||
{
|
{
|
||||||
$this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json...\n");
|
$this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/manga_cache.json...\n");
|
||||||
|
|
||||||
$ids = json_decode(
|
$ids = json_decode(
|
||||||
file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json'),
|
file_get_contents('https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/manga_cache.json'),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -71,14 +71,14 @@ class MangaSweepIndexer extends Command
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
* @url https://github.com/seanbreckenridge/mal-id-cache
|
* @url https://github.com/purarue/mal-id-cache
|
||||||
*/
|
*/
|
||||||
private function fetchMalIds(): array
|
private function fetchMalIds(): array
|
||||||
{
|
{
|
||||||
$this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json...\n");
|
$this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/manga_cache.json...\n");
|
||||||
|
|
||||||
$ids = json_decode(
|
$ids = json_decode(
|
||||||
file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json'),
|
file_get_contents('https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/manga_cache.json'),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -24,7 +24,8 @@ class Kernel extends ConsoleKernel
|
|||||||
Indexer\GenreIndexer::class,
|
Indexer\GenreIndexer::class,
|
||||||
Indexer\ProducersIndexer::class,
|
Indexer\ProducersIndexer::class,
|
||||||
Indexer\AnimeSweepIndexer::class,
|
Indexer\AnimeSweepIndexer::class,
|
||||||
Indexer\MangaSweepIndexer::class
|
Indexer\MangaSweepIndexer::class,
|
||||||
|
Indexer\IncrementalIndexer::class
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,7 +10,7 @@ use Spatie\LaravelData\Optional;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @OA\Parameter(
|
* @OA\Parameter(
|
||||||
* name="spoiler",
|
* name="spoilers",
|
||||||
* in="query",
|
* in="query",
|
||||||
* required=false,
|
* required=false,
|
||||||
* description="Any reviews that are tagged as a spoiler. Spoiler reviews are not returned by default. e.g usage: `?spoiler=true`",
|
* description="Any reviews that are tagged as a spoiler. Spoiler reviews are not returned by default. e.g usage: `?spoiler=true`",
|
||||||
|
@ -4,7 +4,6 @@ namespace App\Dto;
|
|||||||
|
|
||||||
use App\Concerns\HasRequestFingerprint;
|
use App\Concerns\HasRequestFingerprint;
|
||||||
use App\Contracts\DataRequest;
|
use App\Contracts\DataRequest;
|
||||||
use App\DataPipes\MapRouteParametersDataPipe;
|
|
||||||
use App\Dto\Concerns\MapsRouteParameters;
|
use App\Dto\Concerns\MapsRouteParameters;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
@ -13,12 +12,6 @@ use Spatie\LaravelData\Attributes\Validation\Min;
|
|||||||
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
use Spatie\LaravelData\Attributes\Validation\Required;
|
use Spatie\LaravelData\Attributes\Validation\Required;
|
||||||
use Spatie\LaravelData\Data;
|
use Spatie\LaravelData\Data;
|
||||||
use Spatie\LaravelData\DataPipeline;
|
|
||||||
use Spatie\LaravelData\DataPipes\AuthorizedDataPipe;
|
|
||||||
use Spatie\LaravelData\DataPipes\CastPropertiesDataPipe;
|
|
||||||
use Spatie\LaravelData\DataPipes\DefaultValuesDataPipe;
|
|
||||||
use Spatie\LaravelData\DataPipes\MapPropertiesDataPipe;
|
|
||||||
use Spatie\LaravelData\DataPipes\ValidatePropertiesDataPipe;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all requests/commands which are for looking up things by id.
|
* Base class for all requests/commands which are for looking up things by id.
|
||||||
|
@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Dto;
|
namespace App\Dto;
|
||||||
|
|
||||||
use App\Casts\ContextualBooleanCast;
|
|
||||||
use App\Casts\EnumCast;
|
use App\Casts\EnumCast;
|
||||||
use App\Concerns\HasRequestFingerprint;
|
use App\Concerns\HasRequestFingerprint;
|
||||||
use App\Contracts\DataRequest;
|
use App\Contracts\DataRequest;
|
||||||
use App\Dto\Concerns\HasPreliminaryParameter;
|
use App\Dto\Concerns\HasPreliminaryParameter;
|
||||||
use App\Dto\Concerns\HasSpoilersParameter;
|
use App\Dto\Concerns\HasSpoilersParameter;
|
||||||
use App\Dto\Concerns\PreparesData;
|
use App\Dto\Concerns\PreparesData;
|
||||||
use App\Enums\TopAnimeFilterEnum;
|
|
||||||
use App\Enums\TopReviewsTypeEnum;
|
use App\Enums\TopReviewsTypeEnum;
|
||||||
use App\Rules\Attributes\EnumValidation;
|
use App\Rules\Attributes\EnumValidation;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@ -23,6 +21,6 @@ final class QueryTopReviewsCommand extends QueryTopItemsCommand implements DataR
|
|||||||
{
|
{
|
||||||
use HasRequestFingerprint, HasPreliminaryParameter, HasSpoilersParameter, PreparesData;
|
use HasRequestFingerprint, HasPreliminaryParameter, HasSpoilersParameter, PreparesData;
|
||||||
|
|
||||||
#[WithCast(EnumCast::class, TopAnimeFilterEnum::class), EnumValidation(TopReviewsTypeEnum::class)]
|
#[WithCast(EnumCast::class, TopReviewsTypeEnum::class), EnumValidation(TopReviewsTypeEnum::class)]
|
||||||
public TopReviewsTypeEnum|Optional $type;
|
public TopReviewsTypeEnum|Optional $type;
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
namespace App\Features;
|
namespace App\Features;
|
||||||
|
|
||||||
use App\Dto\QuerySpecificAnimeSeasonCommand;
|
use App\Dto\QuerySpecificAnimeSeasonCommand;
|
||||||
use App\Enums\AnimeSeasonEnum;
|
|
||||||
use App\Enums\AnimeStatusEnum;
|
|
||||||
use App\Enums\AnimeTypeEnum;
|
use App\Enums\AnimeTypeEnum;
|
||||||
use Illuminate\Contracts\Database\Query\Builder;
|
use Illuminate\Contracts\Database\Query\Builder;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
@ -7,7 +7,6 @@ use App\Enums\TopReviewsTypeEnum;
|
|||||||
use App\Support\CachedData;
|
use App\Support\CachedData;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Jikan\Helper\Constants;
|
|
||||||
use Jikan\MyAnimeList\MalClient;
|
use Jikan\MyAnimeList\MalClient;
|
||||||
use Jikan\Request\Reviews\ReviewsRequest;
|
use Jikan\Request\Reviews\ReviewsRequest;
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ final class QueryTopReviewsHandler extends RequestHandlerWithScraperCache
|
|||||||
$preliminary = $requestParams->get("preliminary", true);
|
$preliminary = $requestParams->get("preliminary", true);
|
||||||
return $this->scraperService->findList(
|
return $this->scraperService->findList(
|
||||||
$requestFingerPrint,
|
$requestFingerPrint,
|
||||||
fn (MalClient $jikan, ?int $page = null) => $jikan->getReviews(new ReviewsRequest($type, $page, $spoilers, $preliminary)),
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getReviews(new ReviewsRequest(ensureEnumPrimitiveValue($type), $page, $spoilers, $preliminary)),
|
||||||
$requestParams->get("page"));
|
$requestParams->get("page"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ use App\Dto\AnimeThemesLookupCommand;
|
|||||||
use App\Dto\AnimeUserUpdatesLookupCommand;
|
use App\Dto\AnimeUserUpdatesLookupCommand;
|
||||||
use App\Dto\AnimeVideosEpisodesLookupCommand;
|
use App\Dto\AnimeVideosEpisodesLookupCommand;
|
||||||
use App\Dto\AnimeVideosLookupCommand;
|
use App\Dto\AnimeVideosLookupCommand;
|
||||||
use Illuminate\Http\Request;
|
use OpenApi\Annotations as OA;
|
||||||
|
|
||||||
class AnimeController extends Controller
|
class AnimeController extends Controller
|
||||||
{
|
{
|
||||||
@ -225,15 +225,17 @@ class AnimeController extends Controller
|
|||||||
* nullable=true
|
* nullable=true
|
||||||
* ),
|
* ),
|
||||||
* @OA\Property(
|
* @OA\Property(
|
||||||
* property="duration",
|
|
||||||
* type="integer",
|
|
||||||
* description="Episode duration in seconds",
|
|
||||||
* nullable=true
|
|
||||||
* ),
|
|
||||||
* @OA\Property(
|
|
||||||
* property="aired",
|
* property="aired",
|
||||||
* type="string",
|
* type="string",
|
||||||
* description="Aired Date ISO8601",
|
* description="Aired Date ISO8601",
|
||||||
|
* nullable=true
|
||||||
|
* ),
|
||||||
|
* @OA\Property(
|
||||||
|
* property="score",
|
||||||
|
* type="float",
|
||||||
|
* description="Aggregated episode score (1.00 - 5.00) based on MyAnimeList user voting",
|
||||||
|
* minimum="1",
|
||||||
|
* maximum="5",
|
||||||
* nullable=true
|
* nullable=true
|
||||||
* ),
|
* ),
|
||||||
* @OA\Property(
|
* @OA\Property(
|
||||||
@ -669,7 +671,7 @@ class AnimeController extends Controller
|
|||||||
*
|
*
|
||||||
* @OA\Parameter(ref="#/components/parameters/page"),
|
* @OA\Parameter(ref="#/components/parameters/page"),
|
||||||
* @OA\Parameter(ref="#/components/parameters/preliminary"),
|
* @OA\Parameter(ref="#/components/parameters/preliminary"),
|
||||||
* @OA\Parameter(ref="#/components/parameters/spoiler"),
|
* @OA\Parameter(ref="#/components/parameters/spoilers"),
|
||||||
*
|
*
|
||||||
* @OA\Response(
|
* @OA\Response(
|
||||||
* response="200",
|
* response="200",
|
||||||
|
@ -15,7 +15,7 @@ use App\Dto\MangaRelationsLookupCommand;
|
|||||||
use App\Dto\MangaReviewsLookupCommand;
|
use App\Dto\MangaReviewsLookupCommand;
|
||||||
use App\Dto\MangaStatsLookupCommand;
|
use App\Dto\MangaStatsLookupCommand;
|
||||||
use App\Dto\MangaUserUpdatesLookupCommand;
|
use App\Dto\MangaUserUpdatesLookupCommand;
|
||||||
use Illuminate\Http\Request;
|
use OpenApi\Annotations as OA;
|
||||||
|
|
||||||
class MangaController extends Controller
|
class MangaController extends Controller
|
||||||
{
|
{
|
||||||
@ -387,7 +387,7 @@ class MangaController extends Controller
|
|||||||
*
|
*
|
||||||
* @OA\Parameter(ref="#/components/parameters/page"),
|
* @OA\Parameter(ref="#/components/parameters/page"),
|
||||||
* @OA\Parameter(ref="#/components/parameters/preliminary"),
|
* @OA\Parameter(ref="#/components/parameters/preliminary"),
|
||||||
* @OA\Parameter(ref="#/components/parameters/spoiler"),
|
* @OA\Parameter(ref="#/components/parameters/spoilers"),
|
||||||
*
|
*
|
||||||
* @OA\Response(
|
* @OA\Response(
|
||||||
* response="200",
|
* response="200",
|
||||||
|
@ -4,18 +4,20 @@ namespace App\Http\Controllers\V4DB;
|
|||||||
|
|
||||||
use App\Dto\QueryAnimeReviewsCommand;
|
use App\Dto\QueryAnimeReviewsCommand;
|
||||||
use App\Dto\QueryMangaReviewsCommand;
|
use App\Dto\QueryMangaReviewsCommand;
|
||||||
|
use OpenApi\Annotations as OA;
|
||||||
|
|
||||||
class ReviewsController extends Controller
|
class ReviewsController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @OA\Get(
|
* @OA\
|
||||||
|
* Get(
|
||||||
* path="/reviews/anime",
|
* path="/reviews/anime",
|
||||||
* operationId="getRecentAnimeReviews",
|
* operationId="getRecentAnimeReviews",
|
||||||
* tags={"reviews"},
|
* tags={"reviews"},
|
||||||
*
|
*
|
||||||
* @OA\Parameter(ref="#/components/parameters/page"),
|
* @OA\Parameter(ref="#/components/parameters/page"),
|
||||||
* @OA\Parameter(ref="#/components/parameters/preliminary"),
|
* @OA\Parameter(ref="#/components/parameters/preliminary"),
|
||||||
* @OA\Parameter(ref="#/components/parameters/spoiler"),
|
* @OA\Parameter(ref="#/components/parameters/spoilers"),
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @OA\Response(
|
* @OA\Response(
|
||||||
@ -70,7 +72,7 @@ class ReviewsController extends Controller
|
|||||||
*
|
*
|
||||||
* @OA\Parameter(ref="#/components/parameters/page"),
|
* @OA\Parameter(ref="#/components/parameters/page"),
|
||||||
* @OA\Parameter(ref="#/components/parameters/preliminary"),
|
* @OA\Parameter(ref="#/components/parameters/preliminary"),
|
||||||
* @OA\Parameter(ref="#/components/parameters/spoiler"),
|
* @OA\Parameter(ref="#/components/parameters/spoilers"),
|
||||||
*
|
*
|
||||||
* @OA\Response(
|
* @OA\Response(
|
||||||
* response="200",
|
* response="200",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Resources\V4;
|
namespace App\Http\Resources\V4;
|
||||||
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use OpenApi\Annotations as OA;
|
||||||
|
|
||||||
class ReviewsResource extends JsonResource
|
class ReviewsResource extends JsonResource
|
||||||
{
|
{
|
||||||
|
@ -72,6 +72,33 @@ class Producers extends JikanApiSearchableModel
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCollectionSchema(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $this->searchableAs(),
|
||||||
|
'fields' => [
|
||||||
|
[
|
||||||
|
'name' => '.*',
|
||||||
|
'type' => 'auto',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'titles',
|
||||||
|
'type' => 'string',
|
||||||
|
'optional' => false,
|
||||||
|
'infix' => true,
|
||||||
|
'sort' => true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'url',
|
||||||
|
'type' => 'string',
|
||||||
|
'optional' => false,
|
||||||
|
'infix' => true,
|
||||||
|
'sort' => true
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function typesenseQueryBy(): array
|
public function typesenseQueryBy(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -167,19 +167,39 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
|
|||||||
$finalFilter['$or'][] = [
|
$finalFilter['$or'][] = [
|
||||||
// note: this expression only works with mongodb version 5.0.0 or higher
|
// note: this expression only works with mongodb version 5.0.0 or higher
|
||||||
'$expr' => [
|
'$expr' => [
|
||||||
'$lte' => [
|
'$and' => [
|
||||||
[
|
[
|
||||||
'$dateDiff' => [
|
'$lte' => [
|
||||||
'startDate' => [
|
[
|
||||||
'$dateFromString' => [
|
'$dateDiff' => [
|
||||||
'dateString' => '$aired.from'
|
'startDate' => [
|
||||||
|
'$dateFromString' => [
|
||||||
|
'dateString' => '$aired.from'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'endDate' => new UTCDateTime($from),
|
||||||
|
'unit' => 'month'
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'endDate' => new UTCDateTime($from),
|
3 // there are 3 months in a season, so anything that started in 3 months or less will be included
|
||||||
'unit' => 'month'
|
],
|
||||||
]
|
|
||||||
],
|
],
|
||||||
3 // there are 3 months in a season, so anything that started in 3 months or less will be included
|
[
|
||||||
|
'$gt' => [
|
||||||
|
[
|
||||||
|
'$dateDiff' => [
|
||||||
|
'startDate' => [
|
||||||
|
'$dateFromString' => [
|
||||||
|
'dateString' => '$aired.from'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'endDate' => new UTCDateTime($from),
|
||||||
|
'unit' => 'month'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
0
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'aired.to' => null,
|
'aired.to' => null,
|
||||||
|
@ -114,3 +114,12 @@ if (! function_exists('cache')) {
|
|||||||
return app('cache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1] ?? null);
|
return app('cache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1] ?? null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!function_exists("ensureEnumPrimitiveValue")) {
|
||||||
|
function ensureEnumPrimitiveValue(int|string|bool|float|null|\Spatie\Enum\Laravel\Enum $value): mixed {
|
||||||
|
if ($value instanceof \Spatie\Enum\Laravel\Enum) {
|
||||||
|
return $value->value;
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"php": "^8.1",
|
"php": "^8.1",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-mongodb": "*",
|
"ext-mongodb": "*",
|
||||||
|
"ext-pcntl": "*",
|
||||||
"amphp/http-client": "^4.6",
|
"amphp/http-client": "^4.6",
|
||||||
"danielmewes/php-rql": "dev-master",
|
"danielmewes/php-rql": "dev-master",
|
||||||
"darkaonline/swagger-lume": "^9.0",
|
"darkaonline/swagger-lume": "^9.0",
|
||||||
|
14
composer.lock
generated
14
composer.lock
generated
@ -4554,16 +4554,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jikan-me/jikan",
|
"name": "jikan-me/jikan",
|
||||||
"version": "v4.0.11",
|
"version": "v4.0.12",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/jikan-me/jikan.git",
|
"url": "https://github.com/jikan-me/jikan.git",
|
||||||
"reference": "fcc8d20817ce29332b496a21652b9965c6c8196c"
|
"reference": "dcb47237a9407473f484bd28a5e44479cdd916fc"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/jikan-me/jikan/zipball/fcc8d20817ce29332b496a21652b9965c6c8196c",
|
"url": "https://api.github.com/repos/jikan-me/jikan/zipball/dcb47237a9407473f484bd28a5e44479cdd916fc",
|
||||||
"reference": "fcc8d20817ce29332b496a21652b9965c6c8196c",
|
"reference": "dcb47237a9407473f484bd28a5e44479cdd916fc",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -4602,7 +4602,7 @@
|
|||||||
"description": "Jikan is an unofficial MyAnimeList API",
|
"description": "Jikan is an unofficial MyAnimeList API",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/jikan-me/jikan/issues",
|
"issues": "https://github.com/jikan-me/jikan/issues",
|
||||||
"source": "https://github.com/jikan-me/jikan/tree/v4.0.11"
|
"source": "https://github.com/jikan-me/jikan/tree/v4.0.12"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -4610,7 +4610,7 @@
|
|||||||
"type": "patreon"
|
"type": "patreon"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-05-30T08:15:50+00:00"
|
"time": "2024-09-20T22:15:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jms/metadata",
|
"name": "jms/metadata",
|
||||||
@ -13591,5 +13591,5 @@
|
|||||||
"ext-mongodb": "*"
|
"ext-mongodb": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.3.0"
|
||||||
}
|
}
|
||||||
|
@ -260,6 +260,7 @@ return [
|
|||||||
| `BadRequestException` | `405 - Method Not Allowed` | Requested Method is not supported for resource. Only `GET` requests are allowed |
|
| `BadRequestException` | `405 - Method Not Allowed` | Requested Method is not supported for resource. Only `GET` requests are allowed |
|
||||||
| `RateLimitException` | `429 - Too Many Request` | You are being rate limited by Jikan or MyAnimeList is rate-limiting our servers (specified in the error response) |
|
| `RateLimitException` | `429 - Too Many Request` | You are being rate limited by Jikan or MyAnimeList is rate-limiting our servers (specified in the error response) |
|
||||||
| `UpstreamException`,`ParserException`,etc. | `500 - Internal Server Error` | Something didn't work. Try again later. If you see an error response with a `report_url` URL, please click on it to open an auto-generated GitHub issue |
|
| `UpstreamException`,`ParserException`,etc. | `500 - Internal Server Error` | Something didn't work. Try again later. If you see an error response with a `report_url` URL, please click on it to open an auto-generated GitHub issue |
|
||||||
|
| `ServiceUnavailableException` | `503 - Service Unavailable` | In most cases this is intentionally done if the service is down for maintenance. |
|
||||||
|
|
||||||
## JSON Error Response
|
## JSON Error Response
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ display_help() {
|
|||||||
echo "stop Stop Jikan API"
|
echo "stop Stop Jikan API"
|
||||||
echo "validate-prereqs Validate pre-reqs installed (docker, docker-compose)"
|
echo "validate-prereqs Validate pre-reqs installed (docker, docker-compose)"
|
||||||
echo "execute-indexers Execute the indexers, which will scrape and index data from MAL. (Notice: This can take days)"
|
echo "execute-indexers Execute the indexers, which will scrape and index data from MAL. (Notice: This can take days)"
|
||||||
|
echo "index-incrementally Executes the incremental indexers for each media type. (anime, manga)"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +169,11 @@ case "$1" in
|
|||||||
$DOCKER_COMPOSE_CMD -p "$DOCKER_COMPOSE_PROJECT_NAME" exec jikan_rest php /app/artisan indexer:producers
|
$DOCKER_COMPOSE_CMD -p "$DOCKER_COMPOSE_PROJECT_NAME" exec jikan_rest php /app/artisan indexer:producers
|
||||||
echo "Indexing done!"
|
echo "Indexing done!"
|
||||||
;;
|
;;
|
||||||
|
"index-incrementally")
|
||||||
|
echo "Indexing..."
|
||||||
|
$DOCKER_COMPOSE_CMD -p "$DOCKER_COMPOSE_PROJECT_NAME" exec jikan_rest php /app/artisan indexer:incremental anime manga
|
||||||
|
echo "Indexing done!"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "No command specified, displaying help"
|
echo "No command specified, displaying help"
|
||||||
display_help
|
display_help
|
||||||
|
@ -16,6 +16,9 @@ This will:
|
|||||||
> **Note**: The script supports both `docker` and `podman`. In case of `podman` please bare in mind that sometimes the container name resolution doesn't work on the container network.
|
> **Note**: The script supports both `docker` and `podman`. In case of `podman` please bare in mind that sometimes the container name resolution doesn't work on the container network.
|
||||||
> In those cases you might have to install `aardvark-dns` package. On `Arch Linux` podman uses `netavark` network by default (in 2023) so you will need to install the before mentioned package.
|
> In those cases you might have to install `aardvark-dns` package. On `Arch Linux` podman uses `netavark` network by default (in 2023) so you will need to install the before mentioned package.
|
||||||
|
|
||||||
|
> **Note 2**: The script will start the jikan API, but if you start it for the first time, it won't have any data in it!
|
||||||
|
> You will have to run the indexers through artisan to have data. See ["Running the indexer with the script"](#running-the-indexer-with-the-script) section.
|
||||||
|
|
||||||
The script has the following prerequisites and will notify you if these are not present:
|
The script has the following prerequisites and will notify you if these are not present:
|
||||||
|
|
||||||
- git
|
- git
|
||||||
@ -36,6 +39,7 @@ start Start Jikan API (mongodb, typesense, redis, jikan-api wor
|
|||||||
stop Stop Jikan API
|
stop Stop Jikan API
|
||||||
validate-prereqs Validate pre-reqs installed (docker, docker-compose)
|
validate-prereqs Validate pre-reqs installed (docker, docker-compose)
|
||||||
execute-indexers Execute the indexers, which will scrape and index data from MAL. (Notice: This can take days)
|
execute-indexers Execute the indexers, which will scrape and index data from MAL. (Notice: This can take days)
|
||||||
|
index-incrementally Executes the incremental indexers for each media type. (anime, manga)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running the indexer with the script
|
### Running the indexer with the script
|
||||||
|
@ -23,7 +23,7 @@ secrets:
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
jikan_rest:
|
jikan_rest:
|
||||||
image: "jikanme/jikan-rest:${_JIKAN_API_VERSION:-latest}"
|
image: "docker.io/jikanme/jikan-rest:${_JIKAN_API_VERSION:-latest}"
|
||||||
user: "${APP_UID:-10001}:${APP_GID:-10001}"
|
user: "${APP_UID:-10001}:${APP_GID:-10001}"
|
||||||
networks:
|
networks:
|
||||||
- jikan_network
|
- jikan_network
|
||||||
@ -116,7 +116,3 @@ services:
|
|||||||
- typesense-data:/data
|
- typesense-data:/data
|
||||||
ports:
|
ports:
|
||||||
- "8108/tcp"
|
- "8108/tcp"
|
||||||
healthcheck:
|
|
||||||
test: [ 'CMD-SHELL', '{ ! [ -f "curl_created" ] && apt -qq update -y && apt -qq install -y curl && touch curl_created && curl -s -f http://localhost:8108/health; } || { curl -s -f http://localhost:8108/health; }' ]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 2s
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.0.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Jikan API",
|
"title": "Jikan API",
|
||||||
"description": "[Jikan](https://jikan.moe) is an **Unofficial** MyAnimeList API.\nIt scrapes the website to satisfy the need for a complete API - which MyAnimeList lacks.\n\n# Information\n\n⚡ Jikan is powered by its awesome backers - 🙏 [Become a backer](https://www.patreon.com/jikan)\n\n## Rate Limiting\n\n| Duration | Requests |\n|----|----|\n| Daily | **Unlimited** |\n| Per Minute | 60 requests |\n| Per Second | 3 requests |\n\nNote: It's still possible to get rate limited from MyAnimeList.net instead.\n\n\n## JSON Notes\n- Any property (except arrays or objects) whose value does not exist or is undetermined, will be `null`.\n- Any array or object property whose value does not exist or is undetermined, will be empty.\n- Any `score` property whose value does not exist or is undetermined, will be `0`.\n- All dates and timestamps are returned in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format and in UTC timezone\n\n## Caching\nBy **CACHING**, we refer to the data parsed from MyAnimeList which is stored temporarily on our servers to provide better API performance.\n\nAll requests are cached for **24 hours**.\n\nThe following response headers will detail cache information.\n\n| Header | Remarks |\n| ---- | ---- |\n| `Expires` | Cache expiry date |\n| `Last-Modified` | Cache set date |\n| `X-Request-Fingerprint` | Unique request fingerprint (only for cachable requests, not queries) |\n\n\nNote: `X-Request-Fingerprint` will only be available on single resource requests and their child endpoints. e.g `/anime/1`, `/anime/1/relations`.\nThey won't be available on pages which perform queries, like /anime, or /top/anime, etc.\n\n## Allowed HTTP(s) requests\n\n**Jikan REST API does not provide authenticated requests for MyAnimeList.** This means you can not use it to update your anime/manga list.\nOnly GET requests are supported which return READ-ONLY data.\n\n## HTTP Responses\n\nAll error responses are accompanied by a JSON Error response.\n\n| Exception | HTTP Status | Remarks |\n| ---- | ---- | ---- |\n| N/A | `200 - OK` | The request was successful |\n| N/A | `304 - Not Modified` | You have the latest data (Cache Validation response) |\n| `BadRequestException`,`ValidationException` | `400 - Bad Request` | You've made an invalid request. Recheck documentation |\n| `BadResponseException` | `404 - Not Found` | The resource was not found or MyAnimeList responded with a `404` |\n| `BadRequestException` | `405 - Method Not Allowed` | Requested Method is not supported for resource. Only `GET` requests are allowed |\n| `RateLimitException` | `429 - Too Many Request` | You are being rate limited by Jikan or MyAnimeList is rate-limiting our servers (specified in the error response) |\n| `UpstreamException`,`ParserException`,etc. | `500 - Internal Server Error` | Something didn't work. Try again later. If you see an error response with a `report_url` URL, please click on it to open an auto-generated GitHub issue |\n\n## JSON Error Response\n\n```json\n {\n \"status\": 500,\n \"type\": \"InternalException\",\n \"message\": \"Exception Message\",\n \"error\": \"Exception Trace\",\n \"report_url\": \"https://github.com...\"\n }\n```\n\n| Property | Remarks |\n| ---- | ---- |\n| `status` | Returned HTTP Status Code |\n| `type` | Thrown Exception |\n| `message` | Human-readable error message |\n| `error` | Error response and trace from the API |\n| `report_url` | Clicking this would redirect you to a generated GitHub issue |\n\n\n## Cache Validation\n\n- All requests return a `ETag` header which is an MD5 hash of the response\n- You can use this hash to verify if there's new or updated content by suppliying it as the value for the `If-None-Match` in your next request header\n- You will get a HTTP `304 - Not Modified` response if the content has not changed\n- If the content has changed, you'll get a HTTP `200 - OK` response with the updated JSON response\n\n\n\n## Disclaimer\n\n- Jikan is not affiliated with MyAnimeList.net.\n- Jikan is a free, open-source API. Please use it responsibly.\n\n----\n\nBy using the API, you are agreeing to Jikan's [terms of use](https://jikan.moe/terms) policy.\n\n[v3 Documentation](https://jikan.docs.apiary.io/) - [Wrappers/SDKs](https://github.com/jikan-me/jikan#wrappers) - [Report an issue](https://github.com/jikan-me/jikan-rest/issues/new) - [Host your own server](https://github.com/jikan-me/jikan-rest)",
|
"description": "[Jikan](https://jikan.moe) is an **Unofficial** MyAnimeList API.\nIt scrapes the website to satisfy the need for a complete API - which MyAnimeList lacks.\n\n# Information\n\n⚡ Jikan is powered by its awesome backers - 🙏 [Become a backer](https://www.patreon.com/jikan)\n\n## Rate Limiting\n\n| Duration | Requests |\n|----|----|\n| Daily | **Unlimited** |\n| Per Minute | 60 requests |\n| Per Second | 3 requests |\n\nNote: It's still possible to get rate limited from MyAnimeList.net instead.\n\n\n## JSON Notes\n- Any property (except arrays or objects) whose value does not exist or is undetermined, will be `null`.\n- Any array or object property whose value does not exist or is undetermined, will be empty.\n- Any `score` property whose value does not exist or is undetermined, will be `0`.\n- All dates and timestamps are returned in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format and in UTC timezone\n\n## Caching\nBy **CACHING**, we refer to the data parsed from MyAnimeList which is stored temporarily on our servers to provide better API performance.\n\nAll requests are cached for **24 hours**.\n\nThe following response headers will detail cache information.\n\n| Header | Remarks |\n| ---- | ---- |\n| `Expires` | Cache expiry date |\n| `Last-Modified` | Cache set date |\n| `X-Request-Fingerprint` | Unique request fingerprint (only for cachable requests, not queries) |\n\n\nNote: `X-Request-Fingerprint` will only be available on single resource requests and their child endpoints. e.g `/anime/1`, `/anime/1/relations`.\nThey won't be available on pages which perform queries, like /anime, or /top/anime, etc.\n\n## Allowed HTTP(s) requests\n\n**Jikan REST API does not provide authenticated requests for MyAnimeList.** This means you can not use it to update your anime/manga list.\nOnly GET requests are supported which return READ-ONLY data.\n\n## HTTP Responses\n\nAll error responses are accompanied by a JSON Error response.\n\n| Exception | HTTP Status | Remarks |\n| ---- | ---- | ---- |\n| N/A | `200 - OK` | The request was successful |\n| N/A | `304 - Not Modified` | You have the latest data (Cache Validation response) |\n| `BadRequestException`,`ValidationException` | `400 - Bad Request` | You've made an invalid request. Recheck documentation |\n| `BadResponseException` | `404 - Not Found` | The resource was not found or MyAnimeList responded with a `404` |\n| `BadRequestException` | `405 - Method Not Allowed` | Requested Method is not supported for resource. Only `GET` requests are allowed |\n| `RateLimitException` | `429 - Too Many Request` | You are being rate limited by Jikan or MyAnimeList is rate-limiting our servers (specified in the error response) |\n| `UpstreamException`,`ParserException`,etc. | `500 - Internal Server Error` | Something didn't work. Try again later. If you see an error response with a `report_url` URL, please click on it to open an auto-generated GitHub issue |\n| `ServiceUnavailableException` | `503 - Service Unavailable` | In most cases this is intentionally done if the service is down for maintenance. |\n\n## JSON Error Response\n\n```json\n {\n \"status\": 500,\n \"type\": \"InternalException\",\n \"message\": \"Exception Message\",\n \"error\": \"Exception Trace\",\n \"report_url\": \"https://github.com...\"\n }\n```\n\n| Property | Remarks |\n| ---- | ---- |\n| `status` | Returned HTTP Status Code |\n| `type` | Thrown Exception |\n| `message` | Human-readable error message |\n| `error` | Error response and trace from the API |\n| `report_url` | Clicking this would redirect you to a generated GitHub issue |\n\n\n## Cache Validation\n\n- All requests return a `ETag` header which is an MD5 hash of the response\n- You can use this hash to verify if there's new or updated content by suppliying it as the value for the `If-None-Match` in your next request header\n- You will get a HTTP `304 - Not Modified` response if the content has not changed\n- If the content has changed, you'll get a HTTP `200 - OK` response with the updated JSON response\n\n\n\n## Disclaimer\n\n- Jikan is not affiliated with MyAnimeList.net.\n- Jikan is a free, open-source API. Please use it responsibly.\n\n----\n\nBy using the API, you are agreeing to Jikan's [terms of use](https://jikan.moe/terms) policy.\n\n[v3 Documentation](https://jikan.docs.apiary.io/) - [Wrappers/SDKs](https://github.com/jikan-me/jikan#wrappers) - [Report an issue](https://github.com/jikan-me/jikan-rest/issues/new) - [Host your own server](https://github.com/jikan-me/jikan-rest)",
|
||||||
"termsOfService": "https://jikan.moe/terms",
|
"termsOfService": "https://jikan.moe/terms",
|
||||||
"contact": {
|
"contact": {
|
||||||
"name": "API Support (Discord)",
|
"name": "API Support (Discord)",
|
||||||
@ -587,7 +587,7 @@
|
|||||||
"$ref": "#/components/parameters/preliminary"
|
"$ref": "#/components/parameters/preliminary"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/parameters/spoiler"
|
"$ref": "#/components/parameters/spoilers"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1605,7 +1605,7 @@
|
|||||||
"$ref": "#/components/parameters/preliminary"
|
"$ref": "#/components/parameters/preliminary"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/parameters/spoiler"
|
"$ref": "#/components/parameters/spoilers"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -2226,7 +2226,7 @@
|
|||||||
"$ref": "#/components/parameters/preliminary"
|
"$ref": "#/components/parameters/preliminary"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/parameters/spoiler"
|
"$ref": "#/components/parameters/spoilers"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -2258,7 +2258,7 @@
|
|||||||
"$ref": "#/components/parameters/preliminary"
|
"$ref": "#/components/parameters/preliminary"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/parameters/spoiler"
|
"$ref": "#/components/parameters/spoilers"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -4480,16 +4480,18 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
"duration": {
|
|
||||||
"description": "Episode duration in seconds",
|
|
||||||
"type": "integer",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"aired": {
|
"aired": {
|
||||||
"description": "Aired Date ISO8601",
|
"description": "Aired Date ISO8601",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
|
"score": {
|
||||||
|
"description": "Aggregated episode score (1.00 - 5.00) based on MyAnimeList user voting",
|
||||||
|
"type": "float",
|
||||||
|
"maximum": "5",
|
||||||
|
"minimum": "1",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
"filler": {
|
"filler": {
|
||||||
"description": "Filler episode",
|
"description": "Filler episode",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@ -9086,8 +9088,8 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spoiler": {
|
"spoilers": {
|
||||||
"name": "spoiler",
|
"name": "spoilers",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"description": "Any reviews that are tagged as a spoiler. Spoiler reviews are not returned by default. e.g usage: `?spoiler=true`",
|
"description": "Any reviews that are tagged as a spoiler. Spoiler reviews are not returned by default. e.g usage: `?spoiler=true`",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
@ -190,4 +190,39 @@ class SeasonControllerTest extends TestCase
|
|||||||
$this->assertIsArray($content["data"]);
|
$this->assertIsArray($content["data"]);
|
||||||
$this->assertCount(2, $content["data"]);
|
$this->assertCount(2, $content["data"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testShouldNotIncludeNewlyStartedSeasonOfAnimeInPreviousSeasons()
|
||||||
|
{
|
||||||
|
Carbon::setTestNow(Carbon::parse("2024-10-26"));
|
||||||
|
$f = Anime::factory(1);
|
||||||
|
$startDate = "2024-10-02";
|
||||||
|
$carbonStartDate = Carbon::parse($startDate);
|
||||||
|
$state = $f->serializeStateDefinition([
|
||||||
|
"aired" => new CarbonDateRange($carbonStartDate, null)
|
||||||
|
]);
|
||||||
|
$state["aired"]["string"] = "Oct 2, 2024 to ?";
|
||||||
|
$state["airing"] = true;
|
||||||
|
$state["status"] = "Currently Airing";
|
||||||
|
$state["premiered"] = "Fall 2024";
|
||||||
|
$state["mal_id"] = 54857;
|
||||||
|
$state["title"] = "Re:Zero kara Hajimeru Isekai Seikatsu 3rd Season";
|
||||||
|
$state["episodes"] = 16;
|
||||||
|
$state["type"] = "TV";
|
||||||
|
$state["duration"] = "23 min per ep";
|
||||||
|
$state["score"] = 8.9;
|
||||||
|
$f->create($state);
|
||||||
|
|
||||||
|
$content = $this->getJsonResponse([], "/v4/seasons/now?filter=tv&continuing&page=1");
|
||||||
|
$this->seeStatusCode(200);
|
||||||
|
$this->assertCount(1, $content["data"]);
|
||||||
|
|
||||||
|
$content = $this->getJsonResponse([], "/v4/seasons/2024/summer?filter=tv&continuing&page=1");
|
||||||
|
$this->seeStatusCode(200);
|
||||||
|
$this->assertCount(0, $content["data"]);
|
||||||
|
|
||||||
|
$content = $this->getJsonResponse([], "/v4/seasons/2024/spring?filter=tv&continuing&page=1");
|
||||||
|
$this->seeStatusCode(200);
|
||||||
|
$this->assertCount(0, $content["data"]);
|
||||||
|
Carbon::setTestNow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,11 @@ use App\Person;
|
|||||||
use App\Testing\ScoutFlush;
|
use App\Testing\ScoutFlush;
|
||||||
use App\Testing\SyntheticMongoDbTransaction;
|
use App\Testing\SyntheticMongoDbTransaction;
|
||||||
use Illuminate\Database\Eloquent\Factories\Sequence;
|
use Illuminate\Database\Eloquent\Factories\Sequence;
|
||||||
|
use Jikan\Exception\BadResponseException;
|
||||||
|
use Jikan\Exception\ParserException;
|
||||||
|
use Jikan\Model\Reviews\Reviews;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Parser\Reviews\ReviewsParser;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class TopControllerTest extends TestCase
|
class TopControllerTest extends TestCase
|
||||||
@ -14,6 +19,15 @@ class TopControllerTest extends TestCase
|
|||||||
use SyntheticMongoDbTransaction;
|
use SyntheticMongoDbTransaction;
|
||||||
use ScoutFlush;
|
use ScoutFlush;
|
||||||
|
|
||||||
|
public function topReviewTypeParametersProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"empty query string" => [[]],
|
||||||
|
"query string = `?type=anime`" => [["type" => "anime"]],
|
||||||
|
"query string = `?type=manga`" => [["type" => "manga"]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function testTopAnime()
|
public function testTopAnime()
|
||||||
{
|
{
|
||||||
Anime::factory(3)->state(new Sequence(
|
Anime::factory(3)->state(new Sequence(
|
||||||
@ -290,4 +304,27 @@ class TopControllerTest extends TestCase
|
|||||||
$this->get('/v4/top/anime/999')
|
$this->get('/v4/top/anime/999')
|
||||||
->seeStatusCode(404);
|
->seeStatusCode(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider topReviewTypeParametersProvider
|
||||||
|
* @param $params
|
||||||
|
* @return void
|
||||||
|
* @throws BadResponseException
|
||||||
|
* @throws ParserException
|
||||||
|
*/
|
||||||
|
public function testTopReviews($params)
|
||||||
|
{
|
||||||
|
$jikanParser = \Mockery::mock(MalClient::class)->makePartial();
|
||||||
|
|
||||||
|
$reviewsParser = \Mockery::mock(ReviewsParser::class)->makePartial();
|
||||||
|
$reviewsParser->allows()->getReviews()->andReturn([]);
|
||||||
|
$reviewsParser->allows()->hasNextPage()->andReturn(false);
|
||||||
|
$reviewsFacade = Reviews::fromParser($reviewsParser);
|
||||||
|
|
||||||
|
/** @noinspection PhpParamsInspection */
|
||||||
|
$jikanParser->allows()->getReviews(\Mockery::any())->andReturn($reviewsFacade);
|
||||||
|
$this->app->instance('JikanParser', $jikanParser);
|
||||||
|
$this->getJsonResponse($params,"/v4/top/reviews");
|
||||||
|
$this->seeStatusCode(200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user