4.1 Beta...ish #12
@ -279,7 +279,7 @@ final class Anime extends BaseController {
|
|||||||
$characters = [];
|
$characters = [];
|
||||||
$staff = [];
|
$staff = [];
|
||||||
|
|
||||||
if ($data->title === '')
|
if (empty($data))
|
||||||
{
|
{
|
||||||
$this->notFound(
|
$this->notFound(
|
||||||
$this->config->get('whose_list') .
|
$this->config->get('whose_list') .
|
||||||
@ -326,6 +326,10 @@ final class Anime extends BaseController {
|
|||||||
'name' => $personDetails['name'] ?? '??',
|
'name' => $personDetails['name'] ?? '??',
|
||||||
'image' => $personDetails['image'],
|
'image' => $personDetails['image'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
usort($staff[$role], function ($a, $b) {
|
||||||
|
return $a['name'] <=> $b['name'];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ class Character extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->outputHTML('character', $viewData);
|
$this->outputHTML('character/details', $viewData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
198
src/Controller/Images.php
Normal file
198
src/Controller/Images.php
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<?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 function Aviat\AnimeClient\createPlaceholderImage;
|
||||||
|
use function Amp\Promise\wait;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
|
use Aviat\AnimeClient\API\{HummingbirdClient, JsonAPI};
|
||||||
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
|
use Aviat\Ion\View\HtmlView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for handling routes that don't fit elsewhere
|
||||||
|
*/
|
||||||
|
final class Images extends BaseController {
|
||||||
|
/**
|
||||||
|
* Get image covers from kitsu
|
||||||
|
*
|
||||||
|
* @param string $type The category of image
|
||||||
|
* @param string $file The filename to look for
|
||||||
|
* @param bool $display Whether to output the image to the server
|
||||||
|
* @throws \Aviat\Ion\Di\ContainerException
|
||||||
|
* @throws \Aviat\Ion\Di\NotFoundException
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
* @throws \TypeError
|
||||||
|
* @throws \Error
|
||||||
|
* @throws \Throwable
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function cache(string $type, string $file, $display = TRUE): void
|
||||||
|
{
|
||||||
|
$currentUrl = $this->request->getUri()->__toString();
|
||||||
|
|
||||||
|
$kitsuUrl = 'https://media.kitsu.io/';
|
||||||
|
$fileName = str_replace('-original', '', $file);
|
||||||
|
[$id, $ext] = explode('.', basename($fileName));
|
||||||
|
|
||||||
|
$baseSavePath = $this->config->get('img_cache_path');
|
||||||
|
|
||||||
|
// Kitsu doesn't serve webp, but for most use cases,
|
||||||
|
// jpg is a safe assumption
|
||||||
|
$tryJpg = ['anime','characters','manga','people'];
|
||||||
|
if ($ext === 'webp' && in_array($type, $tryJpg, TRUE))
|
||||||
|
{
|
||||||
|
$ext = 'jpg';
|
||||||
|
$currentUrl = str_replace('webp', 'jpg', $currentUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
$typeMap = [
|
||||||
|
'anime' => [
|
||||||
|
'kitsuUrl' => "anime/poster_images/{$id}/medium.{$ext}",
|
||||||
|
'width' => 220,
|
||||||
|
'height' => 312,
|
||||||
|
],
|
||||||
|
'avatars' => [
|
||||||
|
'kitsuUrl' => "users/avatars/{$id}/original.{$ext}",
|
||||||
|
'width' => null,
|
||||||
|
'height' => null,
|
||||||
|
],
|
||||||
|
'characters' => [
|
||||||
|
'kitsuUrl' => "characters/images/{$id}/original.{$ext}",
|
||||||
|
'width' => 225,
|
||||||
|
'height' => 350,
|
||||||
|
],
|
||||||
|
'manga' => [
|
||||||
|
'kitsuUrl' => "manga/poster_images/{$id}/medium.{$ext}",
|
||||||
|
'width' => 220,
|
||||||
|
'height' => 312,
|
||||||
|
],
|
||||||
|
'people' => [
|
||||||
|
'kitsuUrl' => "people/images/{$id}/original.{$ext}",
|
||||||
|
'width' => null,
|
||||||
|
'height' => null,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$imageType = $typeMap[$type] ?? NULL;
|
||||||
|
|
||||||
|
if (NULL === $imageType)
|
||||||
|
{
|
||||||
|
$this->getPlaceholder($baseSavePath, 200, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$kitsuUrl .= $imageType['kitsuUrl'];
|
||||||
|
$width = $imageType['width'];
|
||||||
|
$height = $imageType['height'];
|
||||||
|
$filePrefix = "{$baseSavePath}/{$type}/{$id}";
|
||||||
|
|
||||||
|
$promise = (new HummingbirdClient)->request($kitsuUrl);
|
||||||
|
$response = wait($promise);
|
||||||
|
|
||||||
|
if ($response->getStatus() !== 200)
|
||||||
|
{
|
||||||
|
// Try a few different file types before giving up
|
||||||
|
// webm => jpg => png => gif
|
||||||
|
$nextType = [
|
||||||
|
'jpg' => 'png',
|
||||||
|
'png' => 'gif',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (array_key_exists($ext, $nextType))
|
||||||
|
{
|
||||||
|
$newUrl = str_replace($ext, $nextType[$ext], $currentUrl);
|
||||||
|
$this->redirect($newUrl, 303);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($display)
|
||||||
|
{
|
||||||
|
$this->getPlaceholder("{$baseSavePath}/{$type}", $width, $height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
createPlaceholderImage("{$baseSavePath}/{$type}", $width, $height);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = wait($response->getBody());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[$origWidth] = getimagesizefromstring($data);
|
||||||
|
$gdImg = imagecreatefromstring($data);
|
||||||
|
$resizedImg = imagescale($gdImg, $width ?? $origWidth);
|
||||||
|
|
||||||
|
if ($ext === 'gif')
|
||||||
|
{
|
||||||
|
file_put_contents("{$filePrefix}.gif", $data);
|
||||||
|
imagepalletetotruecolor($gdImg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the webp versions
|
||||||
|
imagewebp($gdImg, "{$filePrefix}-original.webp");
|
||||||
|
imagewebp($resizedImg, "{$filePrefix}.webp");
|
||||||
|
|
||||||
|
// save the scaled jpeg file
|
||||||
|
imagejpeg($resizedImg, "{$filePrefix}.jpg");
|
||||||
|
|
||||||
|
// And the original
|
||||||
|
file_put_contents("{$filePrefix}-original.jpg", $data);
|
||||||
|
|
||||||
|
imagedestroy($gdImg);
|
||||||
|
imagedestroy($resizedImg);
|
||||||
|
|
||||||
|
if ($display)
|
||||||
|
{
|
||||||
|
$contentType = ($ext === 'webp')
|
||||||
|
? "image/webp"
|
||||||
|
: $response->getHeader('content-type')[0];
|
||||||
|
|
||||||
|
$outputFile = (strpos($file, '-original') !== FALSE)
|
||||||
|
? "{$filePrefix}-original.{$ext}"
|
||||||
|
: "{$filePrefix}.{$ext}";
|
||||||
|
|
||||||
|
header("Content-Type: {$contentType}");
|
||||||
|
echo file_get_contents($outputFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a placeholder for a missing image
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param int|null $width
|
||||||
|
* @param int|null $height
|
||||||
|
*/
|
||||||
|
private function getPlaceholder (string $path, ?int $width = 200, ?int $height = NULL): void
|
||||||
|
{
|
||||||
|
$height = $height ?? $width;
|
||||||
|
|
||||||
|
$filename = $path . '/placeholder.png';
|
||||||
|
|
||||||
|
if ( ! file_exists($path . '/placeholder.png'))
|
||||||
|
{
|
||||||
|
createPlaceholderImage($path, $width, $height);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: image/png');
|
||||||
|
echo file_get_contents($filename);
|
||||||
|
}
|
||||||
|
}
|
@ -1,464 +0,0 @@
|
|||||||
<?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 function Aviat\AnimeClient\createPlaceholderImage;
|
|
||||||
use function Amp\Promise\wait;
|
|
||||||
|
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
|
||||||
use Aviat\AnimeClient\API\{HummingbirdClient, JsonAPI};
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
|
||||||
use Aviat\Ion\View\HtmlView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for handling routes that don't fit elsewhere
|
|
||||||
*/
|
|
||||||
final class Index extends BaseController {
|
|
||||||
/**
|
|
||||||
* @var \Aviat\API\Anilist\Model
|
|
||||||
*/
|
|
||||||
private $anilistModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Aviat\AnimeClient\Model\Settings
|
|
||||||
*/
|
|
||||||
private $settingsModel;
|
|
||||||
|
|
||||||
public function __construct(ContainerInterface $container)
|
|
||||||
{
|
|
||||||
parent::__construct($container);
|
|
||||||
|
|
||||||
$this->anilistModel = $container->get('anilist-model');
|
|
||||||
$this->settingsModel = $container->get('settings-model');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Purges the API cache
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function clearCache()
|
|
||||||
{
|
|
||||||
$this->cache->clear();
|
|
||||||
$this->outputHTML('blank', [
|
|
||||||
'title' => 'Cache cleared'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the login form
|
|
||||||
*
|
|
||||||
* @param string $status
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function login(string $status = '')
|
|
||||||
{
|
|
||||||
$message = '';
|
|
||||||
|
|
||||||
$view = new HtmlView($this->container);
|
|
||||||
|
|
||||||
if ($status !== '')
|
|
||||||
{
|
|
||||||
$message = $this->showMessage($view, 'error', $status);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the redirect url
|
|
||||||
$this->setSessionRedirect();
|
|
||||||
|
|
||||||
$this->outputHTML('login', [
|
|
||||||
'title' => 'Api login',
|
|
||||||
'message' => $message
|
|
||||||
], $view);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to Anilist to start Oauth flow
|
|
||||||
*/
|
|
||||||
public function anilistRedirect()
|
|
||||||
{
|
|
||||||
$redirectUrl = 'https://anilist.co/api/v2/oauth/authorize?' .
|
|
||||||
http_build_query([
|
|
||||||
'client_id' => $this->config->get(['anilist', 'client_id']),
|
|
||||||
'redirect_uri' => $this->urlGenerator->url('/anilist-oauth'),
|
|
||||||
'response_type' => 'code',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->redirect($redirectUrl, 303);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Oauth callback for Anilist API
|
|
||||||
*/
|
|
||||||
public function anilistCallback()
|
|
||||||
{
|
|
||||||
$query = $this->request->getQueryParams();
|
|
||||||
$authCode = $query['code'];
|
|
||||||
$uri = $this->urlGenerator->url('/anilist-oauth');
|
|
||||||
|
|
||||||
$authData = $this->anilistModel->authenticate($authCode, $uri);
|
|
||||||
$settings = $this->settingsModel->getSettings();
|
|
||||||
|
|
||||||
if (array_key_exists('error', $authData))
|
|
||||||
{
|
|
||||||
$this->errorPage(400, 'Error Linking Account', $authData['hint']);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the override config file
|
|
||||||
$anilistSettings = [
|
|
||||||
'access_token' => $authData['access_token'],
|
|
||||||
'access_token_expires' => (time() - 10) + $authData['expires_in'],
|
|
||||||
'refresh_token' => $authData['refresh_token'],
|
|
||||||
];
|
|
||||||
|
|
||||||
$newSettings = $settings;
|
|
||||||
$newSettings['anilist'] = array_merge($settings['anilist'], $anilistSettings);
|
|
||||||
|
|
||||||
foreach($newSettings['config'] as $key => $value)
|
|
||||||
{
|
|
||||||
$newSettings[$key] = $value;
|
|
||||||
}
|
|
||||||
unset($newSettings['config']);
|
|
||||||
|
|
||||||
$saved = $this->settingsModel->saveSettingsFile($newSettings);
|
|
||||||
|
|
||||||
if ($saved)
|
|
||||||
{
|
|
||||||
$this->setFlashMessage('Linked Anilist Account', 'success');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->setFlashMessage('Error Linking Anilist Account', 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->redirect($this->url->generate('settings'), 303);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt login authentication
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function loginAction()
|
|
||||||
{
|
|
||||||
$auth = $this->container->get('auth');
|
|
||||||
$post = $this->request->getParsedBody();
|
|
||||||
|
|
||||||
if ($auth->authenticate($post['password']))
|
|
||||||
{
|
|
||||||
$this->sessionRedirect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setFlashMessage('Invalid username or password.');
|
|
||||||
$this->redirect($this->url->generate('login'), 303);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deauthorize the current user
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function logout()
|
|
||||||
{
|
|
||||||
$auth = $this->container->get('auth');
|
|
||||||
$auth->logout();
|
|
||||||
|
|
||||||
$this->redirectToDefaultRoute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the user profile page
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function about($username = 'me')
|
|
||||||
{
|
|
||||||
$isMainUser = $username === 'me';
|
|
||||||
|
|
||||||
$username = $isMainUser
|
|
||||||
? $this->config->get(['kitsu_username'])
|
|
||||||
: $username;
|
|
||||||
$model = $this->container->get('kitsu-model');
|
|
||||||
$data = $model->getUserData($username);
|
|
||||||
$orgData = JsonAPI::organizeData($data)[0];
|
|
||||||
$rels = $orgData['relationships'] ?? [];
|
|
||||||
$favorites = array_key_exists('favorites', $rels) ? $rels['favorites'] : [];
|
|
||||||
|
|
||||||
$timeOnAnime = $this->formatAnimeTime($orgData['attributes']['lifeSpentOnAnime']);
|
|
||||||
|
|
||||||
$whom = $isMainUser
|
|
||||||
? $this->config->get('whose_list')
|
|
||||||
: $username;
|
|
||||||
|
|
||||||
$this->outputHTML('me', [
|
|
||||||
'title' => 'About ' . $whom,
|
|
||||||
'data' => $orgData,
|
|
||||||
'attributes' => $orgData['attributes'],
|
|
||||||
'relationships' => $rels,
|
|
||||||
'favorites' => $this->organizeFavorites($favorites),
|
|
||||||
'timeOnAnime' => $timeOnAnime,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the user settings, if logged in
|
|
||||||
*/
|
|
||||||
public function settings()
|
|
||||||
{
|
|
||||||
$auth = $this->container->get('auth');
|
|
||||||
$form = $this->settingsModel->getSettingsForm();
|
|
||||||
|
|
||||||
$hasAnilistLogin = $this->config->has(['anilist','access_token']);
|
|
||||||
|
|
||||||
$this->outputHTML('settings', [
|
|
||||||
'anilistModel' => $this->anilistModel,
|
|
||||||
'auth' => $auth,
|
|
||||||
'form' => $form,
|
|
||||||
'hasAnilistLogin' => $hasAnilistLogin,
|
|
||||||
'config' => $this->config,
|
|
||||||
'title' => $this->config->get('whose_list') . "'s Settings",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to save the user's settings
|
|
||||||
*
|
|
||||||
* @throws \Aura\Router\Exception\RouteNotFound
|
|
||||||
*/
|
|
||||||
public function settings_post()
|
|
||||||
{
|
|
||||||
$post = $this->request->getParsedBody();
|
|
||||||
unset($post['settings-tabs']);
|
|
||||||
|
|
||||||
// dump($post);
|
|
||||||
$saved = $this->settingsModel->saveSettingsFile($post);
|
|
||||||
|
|
||||||
if ($saved)
|
|
||||||
{
|
|
||||||
$this->setFlashMessage('Saved config settings.', 'success');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->setFlashMessage('Failed to save config file.', 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->redirect($this->url->generate('settings'), 303);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get image covers from kitsu
|
|
||||||
*
|
|
||||||
* @param string $type The category of image
|
|
||||||
* @param string $file The filename to look for
|
|
||||||
* @param bool $display Whether to output the image to the server
|
|
||||||
* @throws \Aviat\Ion\Di\ContainerException
|
|
||||||
* @throws \Aviat\Ion\Di\NotFoundException
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
* @throws \TypeError
|
|
||||||
* @throws \Error
|
|
||||||
* @throws \Throwable
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function images(string $type, string $file, $display = TRUE): void
|
|
||||||
{
|
|
||||||
$kitsuUrl = 'https://media.kitsu.io/';
|
|
||||||
$fileName = str_replace('-original', '', $file);
|
|
||||||
[$id, $ext] = explode('.', basename($fileName));
|
|
||||||
|
|
||||||
$baseSavePath = $this->config->get('img_cache_path');
|
|
||||||
|
|
||||||
$typeMap = [
|
|
||||||
'anime' => [
|
|
||||||
'kitsuUrl' => "anime/poster_images/{$id}/medium.{$ext}",
|
|
||||||
'width' => 220,
|
|
||||||
'height' => 312,
|
|
||||||
],
|
|
||||||
'avatars' => [
|
|
||||||
'kitsuUrl' => "users/avatars/{$id}/original.{$ext}",
|
|
||||||
'width' => null,
|
|
||||||
'height' => null,
|
|
||||||
],
|
|
||||||
'characters' => [
|
|
||||||
'kitsuUrl' => "characters/images/{$id}/original.{$ext}",
|
|
||||||
'width' => 225,
|
|
||||||
'height' => 350,
|
|
||||||
],
|
|
||||||
'manga' => [
|
|
||||||
'kitsuUrl' => "manga/poster_images/{$id}/medium.{$ext}",
|
|
||||||
'width' => 220,
|
|
||||||
'height' => 312,
|
|
||||||
],
|
|
||||||
'people' => [
|
|
||||||
'kitsuUrl' => "people/images/{$id}/original.{$ext}",
|
|
||||||
'width' => null,
|
|
||||||
'height' => null,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
if ( ! array_key_exists($type, $typeMap))
|
|
||||||
{
|
|
||||||
$this->getPlaceholder($baseSavePath, 100, 100);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$kitsuUrl .= $typeMap[$type]['kitsuUrl'];
|
|
||||||
$width = $typeMap[$type]['width'];
|
|
||||||
$height = $typeMap[$type]['height'];
|
|
||||||
|
|
||||||
$promise = (new HummingbirdClient)->request($kitsuUrl);
|
|
||||||
$response = wait($promise);
|
|
||||||
|
|
||||||
if ($response->getStatus() !== 200)
|
|
||||||
{
|
|
||||||
if ($display)
|
|
||||||
{
|
|
||||||
$this->getPlaceholder("{$baseSavePath}/{$type}", $width, $height);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = wait($response->getBody());
|
|
||||||
|
|
||||||
$filePrefix = "{$baseSavePath}/{$type}/{$id}";
|
|
||||||
|
|
||||||
[$origWidth] = getimagesizefromstring($data);
|
|
||||||
$gdImg = imagecreatefromstring($data);
|
|
||||||
$resizedImg = imagescale($gdImg, $width ?? $origWidth);
|
|
||||||
|
|
||||||
if ($ext === 'gif')
|
|
||||||
{
|
|
||||||
file_put_contents("{$filePrefix}.gif", $data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// save the webp versions
|
|
||||||
imagewebp($gdImg, "{$filePrefix}-original.webp");
|
|
||||||
imagewebp($resizedImg, "{$filePrefix}.webp");
|
|
||||||
|
|
||||||
// save the scaled jpeg file
|
|
||||||
imagejpeg($resizedImg, "{$filePrefix}.jpg");
|
|
||||||
|
|
||||||
// And the original
|
|
||||||
file_put_contents("{$filePrefix}-original.jpg", $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
imagedestroy($gdImg);
|
|
||||||
imagedestroy($resizedImg);
|
|
||||||
|
|
||||||
if ($display)
|
|
||||||
{
|
|
||||||
$contentType = ($ext === 'webp')
|
|
||||||
? "image/webp"
|
|
||||||
: $response->getHeader('content-type')[0];
|
|
||||||
|
|
||||||
$outputFile = (strpos($file, '-original') !== FALSE)
|
|
||||||
? "{$filePrefix}-original.{$ext}"
|
|
||||||
: "{$filePrefix}.{$ext}";
|
|
||||||
|
|
||||||
header("Content-Type: {$contentType}");
|
|
||||||
echo file_get_contents($outputFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reorganize favorites data to be more useful
|
|
||||||
*
|
|
||||||
* @param array $rawfavorites
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function organizeFavorites(array $rawfavorites): array
|
|
||||||
{
|
|
||||||
$output = [];
|
|
||||||
|
|
||||||
unset($rawfavorites['data']);
|
|
||||||
|
|
||||||
foreach($rawfavorites as $item)
|
|
||||||
{
|
|
||||||
$rank = $item['attributes']['favRank'];
|
|
||||||
foreach($item['relationships']['item'] as $key => $fav)
|
|
||||||
{
|
|
||||||
$output[$key] = $output[$key] ?? [];
|
|
||||||
foreach ($fav as $id => $data)
|
|
||||||
{
|
|
||||||
$output[$key][$rank] = array_merge(['id' => $id], $data['attributes']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ksort($output[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a placeholder for a missing image
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @param int|null $width
|
|
||||||
* @param int|null $height
|
|
||||||
*/
|
|
||||||
private function getPlaceholder (string $path, ?int $width = 200, ?int $height = NULL): void
|
|
||||||
{
|
|
||||||
$height = $height ?? $width;
|
|
||||||
|
|
||||||
$filename = $path . '/placeholder.png';
|
|
||||||
|
|
||||||
if ( ! file_exists($path . '/placeholder.png'))
|
|
||||||
{
|
|
||||||
createPlaceholderImage($path, $width, $height);
|
|
||||||
}
|
|
||||||
|
|
||||||
header('Content-Type: image/png');
|
|
||||||
echo file_get_contents($filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the time spent on anime in a more readable format
|
|
||||||
*
|
|
||||||
* @param int $minutes
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function formatAnimeTime (int $minutes): string
|
|
||||||
{
|
|
||||||
$minutesPerDay = 1440;
|
|
||||||
$minutesPerYear = $minutesPerDay * 365;
|
|
||||||
|
|
||||||
// Minutes short of a year
|
|
||||||
$years = (int)floor($minutes / $minutesPerYear);
|
|
||||||
$minutes %= $minutesPerYear;
|
|
||||||
|
|
||||||
// Minutes short of a day
|
|
||||||
$extraMinutes = $minutes % $minutesPerDay;
|
|
||||||
|
|
||||||
$days = ($minutes - $extraMinutes) / $minutesPerDay;
|
|
||||||
|
|
||||||
// Minutes short of an hour
|
|
||||||
$remMinutes = $extraMinutes % 60;
|
|
||||||
|
|
||||||
$hours = ($extraMinutes - $remMinutes) / 60;
|
|
||||||
|
|
||||||
$output = "{$days} days, {$hours} hours, and {$remMinutes} minutes.";
|
|
||||||
|
|
||||||
if ($years > 0)
|
|
||||||
{
|
|
||||||
$output = "{$years} year(s),{$output}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
}
|
|
98
src/Controller/Misc.php
Normal file
98
src/Controller/Misc.php
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<?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\Ion\Di\ContainerInterface;
|
||||||
|
use Aviat\Ion\View\HtmlView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for handling routes that don't fit elsewhere
|
||||||
|
*/
|
||||||
|
final class Misc extends BaseController {
|
||||||
|
/**
|
||||||
|
* Purges the API cache
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function clearCache()
|
||||||
|
{
|
||||||
|
$this->cache->clear();
|
||||||
|
$this->outputHTML('blank', [
|
||||||
|
'title' => 'Cache cleared'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the login form
|
||||||
|
*
|
||||||
|
* @param string $status
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function login(string $status = '')
|
||||||
|
{
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
$view = new HtmlView($this->container);
|
||||||
|
|
||||||
|
if ($status !== '')
|
||||||
|
{
|
||||||
|
$message = $this->showMessage($view, 'error', $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the redirect url
|
||||||
|
$this->setSessionRedirect();
|
||||||
|
|
||||||
|
$this->outputHTML('login', [
|
||||||
|
'title' => 'Api login',
|
||||||
|
'message' => $message
|
||||||
|
], $view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt login authentication
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function loginAction()
|
||||||
|
{
|
||||||
|
$auth = $this->container->get('auth');
|
||||||
|
$post = $this->request->getParsedBody();
|
||||||
|
|
||||||
|
if ($auth->authenticate($post['password']))
|
||||||
|
{
|
||||||
|
$this->sessionRedirect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setFlashMessage('Invalid username or password.');
|
||||||
|
$this->redirect($this->url->generate('login'), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deauthorize the current user
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function logout()
|
||||||
|
{
|
||||||
|
$auth = $this->container->get('auth');
|
||||||
|
$auth->logout();
|
||||||
|
|
||||||
|
$this->redirectToDefaultRoute();
|
||||||
|
}
|
||||||
|
}
|
@ -49,105 +49,109 @@ final class People extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$data = JsonAPI::organizeData($rawData);
|
$data = JsonAPI::organizeData($rawData);
|
||||||
|
$included = JsonAPI::organizeIncludes($rawData['included']);
|
||||||
|
|
||||||
|
$orgData = $this->organizeData($included);
|
||||||
|
|
||||||
$viewData = [
|
$viewData = [
|
||||||
|
'included' => $included,
|
||||||
'title' => $this->formatTitle(
|
'title' => $this->formatTitle(
|
||||||
'People',
|
'People',
|
||||||
$data['attributes']['name']
|
$data['attributes']['name']
|
||||||
),
|
),
|
||||||
'data' => $data,
|
'data' => $data,
|
||||||
'castCount' => 0,
|
'castCount' => 0,
|
||||||
'castings' => []
|
'castings' => [],
|
||||||
|
'characters' => $orgData['characters'],
|
||||||
|
'staff' => $orgData['staff'],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (array_key_exists('included', $data) && array_key_exists('castings', $data['included']))
|
$this->outputHTML('person/details', $viewData);
|
||||||
{
|
|
||||||
$viewData['included'] = $data['included'];
|
|
||||||
$viewData['castings'] = $this->organizeCast($data['included']['castings']);
|
|
||||||
$viewData['castCount'] = count($viewData['castings']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->outputHTML('person/index', $viewData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function organizeCast(array $cast): array
|
protected function organizeData(array $data): array
|
||||||
{
|
{
|
||||||
$output = [];
|
$output = [
|
||||||
|
'characters' => [
|
||||||
|
'main' => [],
|
||||||
|
'supporting' => [],
|
||||||
|
],
|
||||||
|
'staff' => [],
|
||||||
|
];
|
||||||
|
|
||||||
foreach ($cast as $id => $role)
|
if (array_key_exists('characterVoices', $data))
|
||||||
{
|
{
|
||||||
if (empty($role['attributes']['role']))
|
foreach ($data['characterVoices'] as $cv)
|
||||||
{
|
{
|
||||||
continue;
|
$mcId = $cv['relationships']['mediaCharacter']['data']['id'];
|
||||||
}
|
|
||||||
|
|
||||||
$roleName = $role['attributes']['role'];
|
if ( ! array_key_exists($mcId, $data['mediaCharacters']))
|
||||||
$media = $role['relationships']['media'];
|
{
|
||||||
$chars = $role['relationships']['character']['characters'] ?? [];
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! array_key_exists($roleName, $output))
|
$mc = $data['mediaCharacters'][$mcId];
|
||||||
{
|
|
||||||
$output[$roleName] = [
|
$role = $mc['role'];
|
||||||
'characters' => [],
|
|
||||||
|
$charId = $mc['relationships']['character']['data']['id'];
|
||||||
|
$mediaId = $mc['relationships']['media']['data']['id'];
|
||||||
|
|
||||||
|
$existingMedia = array_key_exists($charId, $output['characters'][$role])
|
||||||
|
? $output['characters'][$role][$charId]['media']
|
||||||
|
: [];
|
||||||
|
|
||||||
|
$relatedMedia = [
|
||||||
|
$mediaId => $data['anime'][$mediaId],
|
||||||
|
];
|
||||||
|
|
||||||
|
$includedMedia = array_replace_recursive($existingMedia, $relatedMedia);
|
||||||
|
|
||||||
|
uasort($includedMedia, function ($a, $b) {
|
||||||
|
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
|
||||||
|
});
|
||||||
|
|
||||||
|
$character = $data['characters'][$charId];
|
||||||
|
|
||||||
|
$output['characters'][$role][$charId] = [
|
||||||
|
'character' => $character,
|
||||||
|
'media' => $includedMedia,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! empty($chars))
|
if (array_key_exists('mediaStaff', $data))
|
||||||
|
{
|
||||||
|
foreach($data['mediaStaff'] as $rid => $role)
|
||||||
{
|
{
|
||||||
$relatedMedia = [];
|
$roleName = $role['role'];
|
||||||
|
$mediaType = $role['relationships']['media']['data']['type'];
|
||||||
if (array_key_exists('anime', $media))
|
$mediaId = $role['relationships']['media']['data']['id'];
|
||||||
{
|
$media = $data[$mediaType][$mediaId];
|
||||||
foreach($media['anime'] as $sid => $series)
|
$output['staff'][$roleName][$mediaType][$mediaId] = $media;
|
||||||
{
|
|
||||||
$relatedMedia[$sid] = $series['attributes'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($chars as $cid => $character)
|
|
||||||
{
|
|
||||||
// To make sure all the media are properly associated,
|
|
||||||
// merge the found media for this iteration with
|
|
||||||
// existing media, making sure to preserve array keys
|
|
||||||
$existingMedia = array_key_exists($cid, $output[$roleName]['characters'])
|
|
||||||
? $output[$roleName]['characters'][$cid]['media']
|
|
||||||
: [];
|
|
||||||
|
|
||||||
$includedMedia = array_replace_recursive($existingMedia, $relatedMedia);
|
|
||||||
|
|
||||||
uasort($includedMedia, function ($a, $b) {
|
|
||||||
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
|
|
||||||
});
|
|
||||||
|
|
||||||
$output[$roleName]['characters'][$cid] = [
|
|
||||||
'character' => $character['attributes'],
|
|
||||||
'media' => $includedMedia,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
uasort($output[$roleName]['characters'], function ($a, $b) {
|
|
||||||
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uasort($output['characters']['main'], function ($a, $b) {
|
||||||
|
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
|
||||||
|
});
|
||||||
|
uasort($output['characters']['supporting'], function ($a, $b) {
|
||||||
|
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
|
||||||
|
});
|
||||||
|
ksort($output['staff']);
|
||||||
|
foreach($output['staff'] as $role => &$media)
|
||||||
|
{
|
||||||
if (array_key_exists('anime', $media))
|
if (array_key_exists('anime', $media))
|
||||||
{
|
{
|
||||||
foreach($media['anime'] as $sid => $series)
|
uasort($media['anime'], function ($a, $b) {
|
||||||
{
|
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
|
||||||
$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))
|
|
||||||
|
if (array_key_exists('manga', $media))
|
||||||
{
|
{
|
||||||
foreach ($media['manga'] as $sid => $series)
|
uasort($media['manga'], function ($a, $b) {
|
||||||
{
|
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
|
||||||
$output[$roleName]['manga'][$sid] = $series;
|
|
||||||
}
|
|
||||||
uasort($output[$roleName]['manga'], function ($a, $b) {
|
|
||||||
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
87
src/Controller/Settings.php
Normal file
87
src/Controller/Settings.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?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\Ion\Di\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for user settings
|
||||||
|
*/
|
||||||
|
final class Settings extends BaseController {
|
||||||
|
/**
|
||||||
|
* @var \Aviat\API\Anilist\Model
|
||||||
|
*/
|
||||||
|
private $anilistModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Aviat\AnimeClient\Model\Settings
|
||||||
|
*/
|
||||||
|
private $settingsModel;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
|
||||||
|
$this->anilistModel = $container->get('anilist-model');
|
||||||
|
$this->settingsModel = $container->get('settings-model');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the user settings, if logged in
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$auth = $this->container->get('auth');
|
||||||
|
$form = $this->settingsModel->getSettingsForm();
|
||||||
|
|
||||||
|
$hasAnilistLogin = $this->config->has(['anilist', 'access_token']);
|
||||||
|
|
||||||
|
$this->outputHTML('settings/settings', [
|
||||||
|
'anilistModel' => $this->anilistModel,
|
||||||
|
'auth' => $auth,
|
||||||
|
'form' => $form,
|
||||||
|
'hasAnilistLogin' => $hasAnilistLogin,
|
||||||
|
'config' => $this->config,
|
||||||
|
'title' => $this->config->get('whose_list') . "'s Settings",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to save the user's settings
|
||||||
|
*
|
||||||
|
* @throws \Aura\Router\Exception\RouteNotFound
|
||||||
|
*/
|
||||||
|
public function update()
|
||||||
|
{
|
||||||
|
$post = $this->request->getParsedBody();
|
||||||
|
unset($post['settings-tabs']);
|
||||||
|
|
||||||
|
// dump($post);
|
||||||
|
$saved = $this->settingsModel->saveSettingsFile($post);
|
||||||
|
|
||||||
|
if ($saved)
|
||||||
|
{
|
||||||
|
$this->setFlashMessage('Saved config settings.', 'success');
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
$this->setFlashMessage('Failed to save config file.', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect($this->url->generate('settings'), 303);
|
||||||
|
}
|
||||||
|
}
|
222
src/Controller/User.php
Normal file
222
src/Controller/User.php
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
<?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;
|
||||||
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for handling routes that don't fit elsewhere
|
||||||
|
*/
|
||||||
|
final class User extends BaseController {
|
||||||
|
|
||||||
|
private $kitsuModel;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
|
||||||
|
$this->kitsuModel = $container->get('kitsu-model');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the user profile page for the configured user
|
||||||
|
*/
|
||||||
|
public function me(): void
|
||||||
|
{
|
||||||
|
$this->about('me');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the user profile page
|
||||||
|
*
|
||||||
|
* @param string $username
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function about(string $username): void
|
||||||
|
{
|
||||||
|
$isMainUser = $username === 'me';
|
||||||
|
|
||||||
|
$username = $isMainUser
|
||||||
|
? $this->config->get(['kitsu_username'])
|
||||||
|
: $username;
|
||||||
|
|
||||||
|
$data = $this->kitsuModel->getUserData($username);
|
||||||
|
$orgData = JsonAPI::organizeData($data)[0];
|
||||||
|
$rels = $orgData['relationships'] ?? [];
|
||||||
|
$favorites = array_key_exists('favorites', $rels) ? $rels['favorites'] : [];
|
||||||
|
|
||||||
|
$stats = [];
|
||||||
|
foreach ($rels['stats'] as $sid => &$item)
|
||||||
|
{
|
||||||
|
$key = $item['attributes']['kind'];
|
||||||
|
$stats[$key] = $item['attributes']['statsData'];
|
||||||
|
unset($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
//dump($orgData);
|
||||||
|
// dump($stats);
|
||||||
|
|
||||||
|
// $timeOnAnime = $this->formatAnimeTime($orgData['attributes']['lifeSpentOnAnime']);
|
||||||
|
$timeOnAnime = $this->formatAnimeTime($stats['anime-amount-consumed']['time']);
|
||||||
|
|
||||||
|
|
||||||
|
$whom = $isMainUser
|
||||||
|
? $this->config->get('whose_list')
|
||||||
|
: $username;
|
||||||
|
|
||||||
|
$this->outputHTML('user/details', [
|
||||||
|
'title' => 'About ' . $whom,
|
||||||
|
'data' => $orgData,
|
||||||
|
'attributes' => $orgData['attributes'],
|
||||||
|
'relationships' => $rels,
|
||||||
|
'favorites' => $this->organizeFavorites($favorites),
|
||||||
|
'stats' => $stats,
|
||||||
|
'timeOnAnime' => $timeOnAnime,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to Anilist to start Oauth flow
|
||||||
|
*/
|
||||||
|
public function anilistRedirect()
|
||||||
|
{
|
||||||
|
$redirectUrl = 'https://anilist.co/api/v2/oauth/authorize?' .
|
||||||
|
http_build_query([
|
||||||
|
'client_id' => $this->config->get(['anilist', 'client_id']),
|
||||||
|
'redirect_uri' => $this->urlGenerator->url('/anilist-oauth'),
|
||||||
|
'response_type' => 'code',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->redirect($redirectUrl, 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oauth callback for Anilist API
|
||||||
|
*/
|
||||||
|
public function anilistCallback()
|
||||||
|
{
|
||||||
|
$query = $this->request->getQueryParams();
|
||||||
|
$authCode = $query['code'];
|
||||||
|
$uri = $this->urlGenerator->url('/anilist-oauth');
|
||||||
|
|
||||||
|
$authData = $this->anilistModel->authenticate($authCode, $uri);
|
||||||
|
$settings = $this->settingsModel->getSettings();
|
||||||
|
|
||||||
|
if (array_key_exists('error', $authData))
|
||||||
|
{
|
||||||
|
$this->errorPage(400, 'Error Linking Account', $authData['hint']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the override config file
|
||||||
|
$anilistSettings = [
|
||||||
|
'access_token' => $authData['access_token'],
|
||||||
|
'access_token_expires' => (time() - 10) + $authData['expires_in'],
|
||||||
|
'refresh_token' => $authData['refresh_token'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$newSettings = $settings;
|
||||||
|
$newSettings['anilist'] = array_merge($settings['anilist'], $anilistSettings);
|
||||||
|
|
||||||
|
foreach($newSettings['config'] as $key => $value)
|
||||||
|
{
|
||||||
|
$newSettings[$key] = $value;
|
||||||
|
}
|
||||||
|
unset($newSettings['config']);
|
||||||
|
|
||||||
|
$saved = $this->settingsModel->saveSettingsFile($newSettings);
|
||||||
|
|
||||||
|
if ($saved)
|
||||||
|
{
|
||||||
|
$this->setFlashMessage('Linked Anilist Account', 'success');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->setFlashMessage('Error Linking Anilist Account', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect($this->url->generate('settings'), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorganize favorites data to be more useful
|
||||||
|
*
|
||||||
|
* @param array $rawfavorites
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function organizeFavorites(array $rawfavorites): array
|
||||||
|
{
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
unset($rawfavorites['data']);
|
||||||
|
|
||||||
|
foreach ($rawfavorites as $item)
|
||||||
|
{
|
||||||
|
$rank = $item['attributes']['favRank'];
|
||||||
|
foreach ($item['relationships']['item'] as $key => $fav)
|
||||||
|
{
|
||||||
|
$output[$key] = $output[$key] ?? [];
|
||||||
|
foreach ($fav as $id => $data)
|
||||||
|
{
|
||||||
|
$output[$key][$rank] = array_merge(['id' => $id], $data['attributes']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($output[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the time spent on anime in a more readable format
|
||||||
|
*
|
||||||
|
* @param int $minutes
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function formatAnimeTime(int $minutes): string
|
||||||
|
{
|
||||||
|
$minutesPerDay = 1440;
|
||||||
|
$minutesPerYear = $minutesPerDay * 365;
|
||||||
|
|
||||||
|
// Minutes short of a year
|
||||||
|
$years = (int)floor($minutes / $minutesPerYear);
|
||||||
|
$minutes %= $minutesPerYear;
|
||||||
|
|
||||||
|
// Minutes short of a day
|
||||||
|
$extraMinutes = $minutes % $minutesPerDay;
|
||||||
|
|
||||||
|
$days = ($minutes - $extraMinutes) / $minutesPerDay;
|
||||||
|
|
||||||
|
// Minutes short of an hour
|
||||||
|
$remMinutes = $extraMinutes % 60;
|
||||||
|
|
||||||
|
$hours = ($extraMinutes - $remMinutes) / 60;
|
||||||
|
|
||||||
|
$output = "{$days} days, {$hours} hours, and {$remMinutes} minutes.";
|
||||||
|
|
||||||
|
if ($years > 0)
|
||||||
|
{
|
||||||
|
$output = "{$years} year(s),{$output}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user