Bläddra i källkod

Miscellaneous page improvements, including additional data and sorting

pull/12/head
Timothy Warren 1 år sedan
förälder
incheckning
16f62ceb8d

+ 0
- 21
.gitlab-ci.yml Visa fil

@@ -1,21 +0,0 @@
test:7.1:
stage: test
before_script:
- sh build/docker_install.sh > /dev/null
- apk add --no-cache php7-phpdbg
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install --ignore-platform-reqs
image: php:7.1-alpine
script:
- phpdbg -qrr -- ./vendor/bin/phpunit --coverage-text --colors=never

test:7.2:
stage: test
before_script:
- sh build/docker_install.sh > /dev/null
- apk add --no-cache php7-phpdbg
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install --ignore-platform-reqs
image: php:7.2-alpine
script:
- phpdbg -qrr -- ./vendor/bin/phpunit --coverage-text --colors=never

+ 2
- 0
CHANGELOG.md Visa fil

@@ -7,6 +7,8 @@
* Updated console command to sync Kitsu and Anilist data (Kitsu can sync MAL, and MAL's API broke, so MAL sync was removed)
* Added page to update settings without having to edit config files
* Defaulted to secure (HTTPS) urls
* Updated Character pages to show voice actors
* Added People pages, showing which works they contributed to, and in what role

## Version 4
* Updated to use Kitsu API after discontinuation of Hummingbird

+ 8
- 0
app/appConf/routes.php Visa fil

@@ -174,6 +174,14 @@ return [
'slug' => '[a-z0-9\-]+'
]
],
'person' => [
'path' => '/people/{id}',
'action' => 'index',
'params' => [],
'tokens' => [
'id' => '[a-z0-9\-]+'
]
],
'user_info' => [
'path' => '/me',
'action' => 'me',

+ 12
- 4
app/views/character.php Visa fil

@@ -1,4 +1,7 @@
<?php use Aviat\AnimeClient\API\Kitsu; ?>
<?php
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\API\Kitsu;
?>
<main class="details fixed">
<section class="flex flex-no-wrap">
<div>
@@ -89,14 +92,19 @@
<th>Cast Member</th>
<th>Series</th>
</tr>
<?php foreach($casting as $c):?>
<?php foreach($casting as $cid => $c):?>
<tr>
<td style="width:229px">
<article class="character">
<img src="<?= $c['person']['image'] ?>" alt="" />
<?php
$link = $url->generate('person', ['id' => $c['person']['id']]);
?>
<a href="<?= $link ?>">
<img src="<?= $urlGenerator->assetUrl(getLocalImg($c['person']['image'])) ?>" alt="" />
<div class="name">
<?= $c['person']['name'] ?>
</div>
</a>
</article>
</td>
<td>
@@ -108,7 +116,7 @@
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<img src="<?= $series['attributes']['posterImage']['small'] ?>" width="220" alt="" />
<img src="<?= $urlGenerator->assetUrl(getLocalImg($series['attributes']['posterImage']['small'])) ?>" width="220" alt="" />
</a>
<div class="name">
<a href="<?= $link ?>">

+ 74
- 0
app/views/person.php Visa fil

@@ -0,0 +1,74 @@
<?php
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\API\Kitsu;
?>
<main class="details fixed">
<section class="flex flex-no-wrap">
<div>
<picture class="cover">
<source
srcset="<?= $urlGenerator->assetUrl("images/people/{$data['id']}-original.webp") ?>"
type="image/webp"
>
<source
srcset="<?= $urlGenerator->assetUrl("images/people/{$data['id']}-original.jpg") ?>"
type="image/jpeg"
>
<img src="<?= $urlGenerator->assetUrl("images/people/{$data['id']}-original.jpg") ?>" alt="" />
</picture>
</div>
<div>
<h2><?= $data['attributes']['name'] ?></h2>
</div>
</section>

<section>
<?php if ($castCount > 0): ?>
<h3>Castings</h3>
<?php foreach ($castings as $role => $entries): ?>
<h4><?= $role ?></h4>
<?php foreach ($entries as $type => $casting): ?>
<?php if ( ! empty($entries['manga'])): ?>
<h5><?= ucfirst($type) ?></h5>
<?php endif ?>
<section class="align_left media-wrap">
<?php foreach ($casting as $sid => $series): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $series['attributes']['slug']]);
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<picture>
<source
srcset="<?= $urlGenerator->assetUrl("images/{$type}/{$sid}.webp") ?>"
type="image/webp"
/>
<source
srcset="<?= $urlGenerator->assetUrl("images/{$type}/{$sid}.jpg") ?>"
type="image/jpeg"
/>
<img
src="<?= $urlGenerator->assetUrl("images/{$type}/{$sid}.jpg") ?>"
width="220" alt=""
/>
</picture>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach; ?>
</section>
<br />
<?php endforeach ?>
<?php endforeach ?>
<?php endif ?>
</section>
</main>

+ 5
- 0
src/API/JsonAPI.php Visa fil

@@ -61,6 +61,11 @@ final class JsonAPI {
// Inline organized data
foreach($data['data'] as $i => &$item)
{
if ( ! is_array($item))
{
continue;
}

if (array_key_exists('relationships', $item))
{
foreach($item['relationships'] as $relType => $props)

+ 1
- 1
src/API/Kitsu/ListItem.php Visa fil

@@ -103,7 +103,7 @@ final class ListItem implements ListItemInterface {

$request = $this->requestBuilder->newRequest('GET', "library-entries/{$id}")
->setQuery([
'include' => 'media,media.genres,media.mappings'
'include' => 'media,media.categories,media.mappings'
]);

if ($authHeader !== FALSE)

+ 18
- 3
src/API/Kitsu/Model.php Visa fil

@@ -221,6 +221,21 @@ final class Model {
return $data;
}

/**
* Get information about a person
*
* @param string $id
* @return array
*/
public function getPerson(string $id): array
{
return $this->getRequest("people/{$id}", [
'query' => [
'include' => 'castings,castings.media,staff,staff.media,voices'
],
]);
}

/**
* Get profile information for the configured user
*
@@ -585,7 +600,7 @@ final class Model {
}

$transformed = $this->mangaTransformer->transform($baseData);
$transformed['included'] = $baseData['included'];
$transformed['included'] = JsonAPI::organizeIncluded($baseData['included']);
return $transformed;
}

@@ -936,8 +951,8 @@ final class Model {
'characters' => 'slug,name,image'
],
'include' => ($type === 'anime')
? 'categories,mappings,streamingLinks,animeCharacters.character'
: 'categories,mappings,mangaCharacters.character,castings.character',
? 'staff,staff.person,categories,mappings,streamingLinks,animeCharacters.character'
: 'staff,staff.person,categories,mappings,mangaCharacters.character,castings.character',
]
];


+ 66
- 24
src/AnimeClient.php Visa fil

@@ -19,6 +19,10 @@ namespace Aviat\AnimeClient;
use Aviat\Ion\ConfigInterface;
use Yosymfony\Toml\{Toml, TomlBuilder};

// ----------------------------------------------------------------------------
//! TOML Functions
// ----------------------------------------------------------------------------

/**
* Load configuration options from .toml files
*
@@ -67,30 +71,6 @@ function loadTomlFile(string $filename): array
return Toml::parseFile($filename);
}

/**
* Is the array sequential, not associative?
*
* @param mixed $array
* @return bool
*/
function isSequentialArray($array): bool
{
if ( ! is_array($array))
{
return FALSE;
}

$i = 0;
foreach ($array as $k => $v)
{
if ($k !== $i++)
{
return FALSE;
}
}
return TRUE;
}

function _iterateToml(TomlBuilder $builder, $data, $parentKey = NULL): void
{
foreach ($data as $key => $value)
@@ -147,6 +127,34 @@ function tomlToArray(string $toml): array
return Toml::parse($toml);
}

// ----------------------------------------------------------------------------
//! Misc Functions
// ----------------------------------------------------------------------------

/**
* Is the array sequential, not associative?
*
* @param mixed $array
* @return bool
*/
function isSequentialArray($array): bool
{
if ( ! is_array($array))
{
return FALSE;
}

$i = 0;
foreach ($array as $k => $v)
{
if ($k !== $i++)
{
return FALSE;
}
}
return TRUE;
}

/**
* Check that folder permissions are correct for proper operation
*
@@ -186,4 +194,38 @@ function checkFolderPermissions(ConfigInterface $config): array
}

return $errors;
}

/**
* Generate the path for the cached image from the original iamge
*
* @param string $kitsuUrl
* @return string
*/
function getLocalImg ($kitsuUrl): string
{
if ( ! is_string($kitsuUrl))
{
return '/404';
}

$parts = parse_url($kitsuUrl);

if ($parts === FALSE)
{
return '/404';
}

$file = basename($parts['path']);
$fileParts = explode('.', $file);
$ext = array_pop($fileParts);
$segments = explode('/', trim($parts['path'], '/'));

// dump($segments);

$type = $segments[0] === 'users' ? $segments[1] : $segments[0];

$id = $segments[count($segments) - 2];

return implode('/', ['images', $type, "{$id}.{$ext}"]);
}

+ 4
- 14
src/Command/UpdateThumbnails.php Visa fil

@@ -16,20 +16,8 @@

namespace Aviat\AnimeClient\Command;

use Aviat\AnimeClient\API\{
FailedResponseException,
JsonAPI,
ParallelAPIRequest
};
use Aviat\AnimeClient\API\Anilist\Transformer\{
AnimeListTransformer as AALT,
MangaListTransformer as AMLT
};
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\AnimeClient\Controller\Index;
use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Json;
use DateTime;

/**
* Clears out image cache directories, then re-creates the image cache
@@ -69,9 +57,11 @@ final class UpdateThumbnails extends BaseCommand {
{
$this->controller->images($type, "{$id}.jpg", FALSE);
}

$this->echoBox("Finished regenerating {$type} thumbnails");
}

$this->echoBox('Finished regenerating thumbnails');
$this->echoBox('Finished regenerating all thumbnails');
}

public function clearThumbs()

+ 41
- 6
src/Controller/Anime.php Visa fil

@@ -275,10 +275,11 @@ final class Anime extends BaseController {
*/
public function details(string $animeId): void
{
$show_data = $this->model->getAnime($animeId);
$data = $this->model->getAnime($animeId);
$characters = [];
$staff = [];

if ($show_data->title === '')
if ($data->title === '')
{
$this->notFound(
$this->config->get('whose_list') .
@@ -290,22 +291,56 @@ final class Anime extends BaseController {
return;
}

if (array_key_exists('characters', $show_data['included']))
if (array_key_exists('characters', $data['included']))
{
foreach($show_data['included']['characters'] as $id => $character)


foreach($data['included']['characters'] as $id => $character)
{
$characters[$id] = $character['attributes'];
}
}

if (array_key_exists('mediaStaff', $data['included']))
{
foreach ($data['included']['mediaStaff'] as $id => $person)
{
$personDetails = [];
foreach ($person['relationships']['person']['people'] as $p)
{
$personDetails = $p['attributes'];
}

$role = $person['attributes']['role'];

if ( ! array_key_exists($role, $staff))
{
$staff[$role] = [];
}

$staff[$role][$id] = [
'name' => $personDetails['name'] ?? '??',
'image' => $personDetails['image'],
];
}
}

uasort($characters, function ($a, $b) {
return $a['name'] <=> $b['name'];
});

// dump($characters);
// dump($staff);

$this->outputHTML('anime/details', [
'title' => $this->formatTitle(
$this->config->get('whose_list') . "'s Anime List",
'Anime',
$show_data->title
$data->title
),
'characters' => $characters,
'show_data' => $show_data,
'show_data' => $data,
'staff' => $staff,
]);
}


+ 49
- 11
src/Controller/Character.php Visa fil

@@ -16,6 +16,8 @@

namespace Aviat\AnimeClient\Controller;

use function Aviat\AnimeClient\getLocalImg;

use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\Ion\ArrayWrapper;
@@ -23,7 +25,7 @@ use Aviat\Ion\ArrayWrapper;
/**
* Controller for character description pages
*/
final class Character extends BaseController {
class Character extends BaseController {

use ArrayWrapper;

@@ -57,6 +59,23 @@ final class Character extends BaseController {

$data = JsonAPI::organizeData($rawData);

if (array_key_exists('included', $data))
{
if (array_key_exists('anime', $data['included']))
{
uasort($data['included']['anime'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
}

if (array_key_exists('manga', $data['included']))
{
uasort($data['included']['manga'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
}
}

$viewData = [
'title' => $this->formatTitle(
'Characters',
@@ -67,10 +86,13 @@ final class Character extends BaseController {
'castings' => []
];

if (array_key_exists('included', $data) && array_key_exists('castings', $data['included']))
if (array_key_exists('included', $data))
{
$viewData['castings'] = $this->organizeCast($data['included']['castings']);
$viewData['castCount'] = $this->getCastCount($viewData['castings']);
if (array_key_exists('castings', $data['included']))
{
$viewData['castings'] = $this->organizeCast($data['included']['castings']);
$viewData['castCount'] = $this->getCastCount($viewData['castings']);
}
}

$this->outputHTML('character', $viewData);
@@ -121,25 +143,26 @@ final class Character extends BaseController {
return $output;
}

private function getCastCount(array $cast): int
protected function getCastCount(array $cast): int
{
$count = 0;

foreach($cast as $role)
{
if (
$count++;
/* if (
array_key_exists('attributes', $role) &&
array_key_exists('role', $role['attributes']) &&
$role['attributes']['role'] !== NULL
) {
$count++;
}
} */
}

return $count;
}

private function organizeCast(array $cast): array
protected function organizeCast(array $cast): array
{
$cast = $this->dedupeCast($cast);
$output = [];
@@ -157,8 +180,19 @@ final class Character extends BaseController {

if ($isVA)
{
$person = current($role['relationships']['person']['people'])['attributes'];
$name = $person['name'];
foreach($role['relationships']['person']['people'] as $pid => $peoples)
{
$p = $peoples;
}

$person = $p['attributes'];
$person['id'] = $pid;
$person['image'] = $person['image']['original'];

uasort($role['relationships']['media']['anime'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});

$item = [
'person' => $person,
'series' => $role['relationships']['media']['anime']
@@ -168,7 +202,11 @@ final class Character extends BaseController {
}
else
{
$output[$roleName][] = $role['relationships']['person']['people'];
foreach($role['relationships']['person']['people'] as $pid => $person)
{
$person['id'] = $pid;
$output[$roleName][$pid] = $person;
}
}
}


+ 32
- 23
src/Controller/Index.php Visa fil

@@ -268,36 +268,45 @@ final class Index extends BaseController {
$kitsuUrl = 'https://media.kitsu.io/';
$fileName = str_replace('-original', '', $file);
[$id, $ext] = explode('.', basename($fileName));
switch ($type)

$typeMap = [
'anime' => [
'kitsuUrl' => "anime/poster_images/{$id}/medium.{$ext}",
'width' => 220,
],
'avatars' => [
'kitsuUrl' => "users/avatars/{$id}/original.{$ext}",
'width' => null,
],
'characters' => [
'kitsuUrl' => "characters/images/{$id}/original.{$ext}",
'width' => 225,
],
'manga' => [
'kitsuUrl' => "manga/poster_images/{$id}/medium.{$ext}",
'width' => 220,
],
'people' => [
'kitsuUrl' => "people/images/{$id}/original.{$ext}",
'width' => null,
],
];

if ( ! array_key_exists($type, $typeMap))
{
case 'anime':
$kitsuUrl .= "anime/poster_images/{$id}/small.jpg";
$width = 220;
break;

case 'avatars':
$kitsuUrl .= "users/avatars/{$id}/original.jpg";
break;

case 'manga':
$kitsuUrl .= "manga/poster_images/{$id}/small.jpg";
$width = 220;
break;

case 'characters':
$kitsuUrl .= "characters/images/{$id}/original.jpg";
$width = 225;
break;

default:
$this->notFound();
return;
$this->notFound();
return;
}

$kitsuUrl .= $typeMap[$type]['kitsuUrl'];
$width = $typeMap[$type]['width'];

$promise = (new HummingbirdClient)->request($kitsuUrl);
$response = wait($promise);
$data = wait($response->getBody());

// echo "Fetching {$kitsuUrl}\n";

$baseSavePath = $this->config->get('img_cache_path');
$filePrefix = "{$baseSavePath}/{$type}/{$id}";


+ 35
- 3
src/Controller/Manga.php Visa fil

@@ -280,6 +280,7 @@ final class Manga extends Controller {
public function details($manga_id): void
{
$data = $this->model->getManga($manga_id);
$staff = [];
$characters = [];

if (empty($data))
@@ -293,14 +294,44 @@ final class Manga extends Controller {
return;
}

foreach($data['included'] as $included)
if (array_key_exists('characters', $data['included']))
{
if ($included['type'] === 'characters')
foreach ($data['included']['characters'] as $id => $character)
{
$characters[$included['id']] = $included['attributes'];
$characters[$id] = $character['attributes'];
}
}

if (array_key_exists('mediaStaff', $data['included']))
{
foreach ($data['included']['mediaStaff'] as $id => $person)
{
$personDetails = [];
foreach ($person['relationships']['person']['people'] as $p)
{
$personDetails = $p['attributes'];
}

$role = $person['attributes']['role'];

if ( ! array_key_exists($role, $staff))
{
$staff[$role] = [];
}

$staff[$role][$id] = [
'name' => $personDetails['name'] ?? '??',
'image' => $personDetails['image'],
];
}
}

uasort($characters, function ($a, $b) {
return $a['name'] <=> $b['name'];
});

// dump($staff);

$this->outputHTML('manga/details', [
'title' => $this->formatTitle(
$this->config->get('whose_list') . "'s Manga List",
@@ -309,6 +340,7 @@ final class Manga extends Controller {
),
'characters' => $characters,
'data' => $data,
'staff' => $staff,
]);
}


+ 110
- 0
src/Controller/People.php Visa fil

@@ -0,0 +1,110 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/

namespace Aviat\AnimeClient\Controller;

use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\JsonAPI;

/**
* Controller for People pages
*/
final class People extends BaseController {
/**
* Show information about a person
*
* @param string $id
* @return void
*/
public function index(string $id): void
{
$model = $this->container->get('kitsu-model');

$rawData = $model->getPerson($id);

if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))
{
$this->notFound(
$this->formatTitle(
'People',
'Person not found'
),
'Person Not Found'
);

return;
}

$data = JsonAPI::organizeData($rawData);

$viewData = [
'title' => $this->formatTitle(
'People',
$data['attributes']['name']
),
'data' => $data,
'castCount' => 0,
'castings' => []
];

if (array_key_exists('included', $data) && array_key_exists('castings', $data['included']))
{
$viewData['castings'] = $this->organizeCast($data['included']['castings']);
$viewData['castCount'] = count($viewData['castings']);
}

$this->outputHTML('person', $viewData);
}

protected function organizeCast(array $cast): array
{
$output = [];

foreach ($cast as $id => $role)
{
if (empty($role['attributes']['role']))
{
continue;
}

$roleName = $role['attributes']['role'];
$media = $role['relationships']['media'];

if (array_key_exists('anime', $media))
{
foreach($media['anime'] as $sid => $series)
{
$output[$roleName]['anime'][$sid] = $series;
}
uasort($output[$roleName]['anime'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
}
else if (array_key_exists('manga', $media))
{
foreach ($media['manga'] as $sid => $series)
{
$output[$roleName]['manga'][$sid] = $series;
}
uasort($output[$roleName]['anime'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
}
}

return $output;
}
}

Laddar…
Avbryt
Spara