Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
26 changed files with 468 additions and 475 deletions
Showing only changes of commit 0e9c257ab7 - Show all commits

View File

@ -19,17 +19,9 @@ namespace Aviat\AnimeClient;
use Aura\Html\HelperLocatorFactory;
use Aura\Router\RouterContainer;
use Aura\Session\SessionFactory;
use Aviat\AnimeClient\API\Kitsu\{
Auth as KitsuAuth,
ListItem as KitsuListItem,
KitsuRequestBuilder,
Model as KitsuModel
};
use Aviat\AnimeClient\API\MAL\{
ListItem as MALListItem,
MALRequestBuilder,
Model as MALModel
};
use Aviat\AnimeClient\API\{Kitsu, MAL};
use Aviat\AnimeClient\API\Kitsu\KitsuRequestBuilder;
use Aviat\AnimeClient\API\MAL\MALRequestBuilder;
use Aviat\AnimeClient\Model;
use Aviat\Banker\Pool;
use Aviat\Ion\Config;
@ -120,11 +112,11 @@ return function(array $config_array = []) {
$requestBuilder = new KitsuRequestBuilder();
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
$listItem = new KitsuListItem();
$listItem = new Kitsu\ListItem();
$listItem->setContainer($container);
$listItem->setRequestBuilder($requestBuilder);
$model = new KitsuModel($listItem);
$model = new Kitsu\Model($listItem);
$model->setContainer($container);
$model->setRequestBuilder($requestBuilder);
@ -136,11 +128,11 @@ return function(array $config_array = []) {
$requestBuilder = new MALRequestBuilder();
$requestBuilder->setLogger($container->getLogger('mal-request'));
$listItem = new MALListItem();
$listItem = new MAL\ListItem();
$listItem->setContainer($container);
$listItem->setRequestBuilder($requestBuilder);
$model = new MALModel($listItem);
$model = new MAL\Model($listItem);
$model->setContainer($container);
$model->setRequestBuilder($requestBuilder);
return $model;
@ -161,7 +153,7 @@ return function(array $config_array = []) {
// Miscellaneous Classes
$container->set('auth', function($container) {
return new KitsuAuth($container);
return new Kitsu\Auth($container);
});
$container->set('url-generator', function($container) {
return new UrlGenerator($container);

View File

@ -14,6 +14,11 @@
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
use const Aviat\AnimeClient\{
DEFAULT_CONTROLLER_METHOD,
DEFAULT_CONTROLLER_NAMESPACE
};
use Aviat\AnimeClient\AnimeClient;
return [
@ -148,25 +153,25 @@ return [
'cache_purge' => [
'path' => '/cache_purge',
'action' => 'clearCache',
'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE,
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
'verb' => 'get',
],
'login' => [
'path' => '/login',
'action' => 'login',
'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE,
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
'verb' => 'get',
],
'login.post' => [
'path' => '/login',
'action' => 'loginAction',
'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE,
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
'verb' => 'post',
],
'logout' => [
'path' => '/logout',
'action' => 'logout',
'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE,
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
],
'update' => [
'path' => '/{controller}/update',
@ -194,7 +199,7 @@ return [
],
'list' => [
'path' => '/{controller}/{type}{/view}',
'action' => AnimeClient::DEFAULT_CONTROLLER_METHOD,
'action' => DEFAULT_CONTROLLER_METHOD,
'tokens' => [
'type' => '[a-z_]+',
'view' => '[a-z_]+',
@ -202,7 +207,7 @@ return [
],
'index_redirect' => [
'path' => '/',
'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE,
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
'action' => 'redirectToDefaultRoute',
],
],

View File

@ -3,6 +3,9 @@
"description": "A self-hosted anime/manga client for Kitsu.",
"license":"MIT",
"autoload": {
"files": [
"src/AnimeClient.php"
],
"psr-4": {
"Aviat\\AnimeClient\\": "src/"
}
@ -20,7 +23,6 @@
"aviat/banker": "^1.0.0",
"aviat/ion": "1.0.*",
"filp/whoops": "^2.1.5",
"guzzlehttp/guzzle": "^6.0",
"monolog/monolog": "^1.0",
"psr/http-message": "~1.0",
"psr/log": "~1.0",

View File

@ -15,6 +15,8 @@
*/
namespace Aviat\AnimeClient;
use function Aviat\AnimeClient\loadToml;
use Aviat\AnimeClient\AnimeClient;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;
@ -42,7 +44,7 @@ $APP_DIR = _dir(__DIR__, 'app');
$CONF_DIR = _dir($APP_DIR, 'config');
// Load composer autoloader
require _dir(__DIR__, '/vendor/autoload.php');
require _dir(__DIR__, 'vendor/autoload.php');
// -------------------------------------------------------------------------
// Setup error handling
@ -54,10 +56,7 @@ $defaultHandler = new PrettyPageHandler();
$whoops->pushHandler($defaultHandler);
// Register as the error handler
if (array_key_exists('whoops', $_GET))
{
$whoops->register();
}
$whoops->register();
// -----------------------------------------------------------------------------
// Dependency Injection setup
@ -65,7 +64,7 @@ if (array_key_exists('whoops', $_GET))
require _dir($CONF_DIR, 'base_config.php'); // $base_config
$di = require _dir($APP_DIR, 'bootstrap.php');
$config = AnimeClient::loadToml($CONF_DIR);
$config = loadToml($CONF_DIR);
$config_array = array_merge($base_config, $config);
$container = $di($config_array);
@ -78,5 +77,3 @@ unset($CONF_DIR);
// Dispatch to the current route
// -----------------------------------------------------------------------------
$container->get('dispatcher')->__invoke();
// End of index.php

View File

@ -16,12 +16,14 @@
namespace Aviat\AnimeClient\API;
use Amp;
use Amp\Artax\{
Client,
FormBody,
Request
};
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Json;
use InvalidArgumentException;
use Psr\Log\LoggerAwareTrait;
@ -67,6 +69,21 @@ class APIRequestBuilder {
*/
protected $request;
/**
* Set an authorization header
*
* @param string $type The type of authorization, eg, basic, bearer, etc.
* @param string $value The authorization value
* @return self
*/
public function setAuth(string $type, string $value): self
{
$authString = ucfirst($type) . ' ' . $value;
$this->setHeader('Authorization', $authString);
return $this;
}
/**
* Set a basic authentication header
*
@ -76,9 +93,7 @@ class APIRequestBuilder {
*/
public function setBasicAuth(string $username, string $password): self
{
$authString = 'Basic ' . base64_encode($username . ':' . $password);
$this->setHeader('Authorization', $authString);
$this->setAuth('basic', base64_encode($username . ':' . $password));
return $this;
}
@ -139,6 +154,18 @@ class APIRequestBuilder {
return $this;
}
/**
* Set the request body
*
* @param array|FormBody|string $body
* @return self
*/
public function setJsonBody(array $body): self
{
$requestBody = Json::encode($body);
return $this->setBody($requestBody);
}
/**
* Append a query string in array format
*
@ -232,7 +259,7 @@ class APIRequestBuilder {
*/
private function fixBody(FormBody $formBody): string
{
$rawBody = \Amp\wait($formBody->getBody());
$rawBody = Amp\wait($formBody->getBody());
return html_entity_decode($rawBody, \ENT_HTML5, 'UTF-8');
}

View File

@ -16,24 +16,8 @@
namespace Aviat\AnimeClient\API;
use Amp;
use Amp\Artax\{
Client,
Response,
Request
}
use UnexpectedValueException;
class APIClient {
class FailedResponseException extends UnexpectedValueException {
/**
* Get a syncronous response for a request
*
* @param Request $request
* @return Response
*/
static public function syncResponse(Request $request): Response
{
$client = new Client();
return wait($client->request($request));
}
}

View File

@ -1,41 +0,0 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2017 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API;
/**
* Base trait for api interaction
*/
trait GuzzleTrait {
/**
* The Guzzle http client object
* @var object
*/
protected $client;
/**
* Cookie jar object for api requests
* @var object
*/
protected $cookieJar;
/**
* Set up the class properties
*
* @return void
*/
abstract protected function init();
}

View File

@ -23,6 +23,10 @@ use Aviat\AnimeClient\API\Kitsu\Enum\{
};
use DateTimeImmutable;
const AUTH_URL = 'https://kitsu.io/api/oauth/token';
const AUTH_USER_ID_KEY = 'kitsu-auth-userid';
const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token';
/**
* Data massaging helpers for the Kitsu API
*/

View File

@ -16,6 +16,9 @@
namespace Aviat\AnimeClient\API\Kitsu;
use const Aviat\AnimeClient\SESSION_SEGMENT;
use const Aviat\AnimeClient\API\Kitsu\AUTH_TOKEN_CACHE_KEY;
use Aviat\AnimeClient\AnimeClient;
use Aviat\AnimeClient\API\{
CacheTrait,
@ -55,7 +58,7 @@ class Auth {
$this->setContainer($container);
$this->setCache($container->get('cache'));
$this->segment = $container->get('session')
->getSegment(AnimeClient::SESSION_SEGMENT);
->getSegment(SESSION_SEGMENT);
$this->model = $container->get('kitsu-model');
}
@ -84,7 +87,7 @@ class Auth {
if (FALSE !== $auth)
{
// Set the token in the cache for command line operations
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_CACHE_KEY);
$cacheItem = $this->cache->getItem(AUTH_TOKEN_CACHE_KEY);
$cacheItem->set($auth['access_token']);
$cacheItem->save();

View File

@ -40,16 +40,4 @@ class KitsuRequestBuilder extends APIRequestBuilder {
'client_id' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd',
'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151',
];
/**
* Set the request body
*
* @param array|FormBody|string $body
* @return self
*/
public function setJsonBody(array $body): self
{
$requestBody = Json::encode($body);
return $this->setBody($requestBody);
}
}

View File

@ -16,13 +16,14 @@
namespace Aviat\AnimeClient\API\Kitsu;
use const Aviat\AnimeClient\SESSION_SEGMENT;
use function Amp\wait;
use Amp\Artax\Client;
use Aviat\AnimeClient\AnimeClient;
use Aviat\AnimeClient\API\GuzzleTrait;
use Aviat\AnimeClient\API\Kitsu as K;
use Aviat\Ion\Json;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Psr7\Response;
use InvalidArgumentException;
use RuntimeException;
@ -34,37 +35,6 @@ trait KitsuTrait {
*/
protected $requestBuilder;
/**
* The Guzzle http client object
* @var object
*/
protected $client;
/**
* Cookie jar object for api requests
* @var object
*/
protected $cookieJar;
/**
* The base url for api requests
* @var string $base_url
*/
protected $baseUrl = "https://kitsu.io/api/edge/";
/**
* HTTP headers to send with every request
*
* @var array
*/
protected $defaultHeaders = [
'User-Agent' => "Tim's Anime Client/4.0",
'Accept-Encoding' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json',
'client_id' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd',
'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151',
];
/**
* Set the request builder object
*
@ -78,30 +48,45 @@ trait KitsuTrait {
}
/**
* Set up the class properties
* Create a request object
*
* @return void
* @param string $type
* @param string $url
* @param array $options
* @return \Amp\Artax\Response
*/
protected function init()
public function setUpRequest(string $type, string $url, array $options = [])
{
$defaults = [
'cookies' => $this->cookieJar,
'headers' => $this->defaultHeaders,
'timeout' => 25,
'connect_timeout' => 25
];
$config = $this->container->get('config');
$this->cookieJar = new CookieJar();
$this->client = new Client([
'base_uri' => $this->baseUrl,
'cookies' => TRUE,
'http_errors' => TRUE,
'defaults' => $defaults
]);
$request = $this->requestBuilder->newRequest($type, $url);
$sessionSegment = $this->getContainer()
->get('session')
->getSegment(SESSION_SEGMENT);
if ($sessionSegment->get('auth_token') !== null && $url !== K::AUTH_URL)
{
$token = $sessionSegment->get('auth_token');
$request = $request->setAuth('bearer', $token);
// $defaultOptions['headers']['Authorization'] = "bearer {$token}";
}
if (array_key_exists('query', $options))
{
$request->setQuery($options['query']);
}
if (array_key_exists('body', $options))
{
$request->setJsonBody($options['body']);
}
return $request->getFullRequest();
}
/**
* Make a request via Guzzle
* Make a request
*
* @param string $type
* @param string $url
@ -110,48 +95,24 @@ trait KitsuTrait {
*/
private function getResponse(string $type, string $url, array $options = [])
{
$logger = null;
$validTypes = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
if ( ! in_array($type, $validTypes))
{
throw new InvalidArgumentException('Invalid http request type');
}
$defaultOptions = [
'headers' => $this->defaultHeaders
];
$request = $this->setUpRequest($type, $url, $options);
$logger = $this->container->getLogger('kitsu-request');
$sessionSegment = $this->getContainer()
->get('session')
->getSegment(AnimeClient::SESSION_SEGMENT);
if ($sessionSegment->get('auth_token') !== null && $url !== K::AUTH_URL)
{
$token = $sessionSegment->get('auth_token');
$defaultOptions['headers']['Authorization'] = "bearer {$token}";
}
$response = wait((new Client)->request($request));
$options = array_merge($defaultOptions, $options);
$response = $this->client->request($type, $url, $options);
$logger->debug('Kitsu API request', [
'requestParams' => [
'type' => $type,
'url' => $url,
],
'responseValues' => [
'status' => $response->getStatusCode()
]
]);
/* $logger->debug('Kitsu api response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getAllHeaders(),
'requestHeaders' => $request->getAllHeaders(),
]); */
return $response;
}
/**
* Make a request via Guzzle
* Make a request
*
* @param string $type
* @param string $url
@ -168,7 +129,7 @@ trait KitsuTrait {
$response = $this->getResponse($type, $url, $options);
if ((int) $response->getStatusCode() > 299 || (int) $response->getStatusCode() < 200)
if ((int) $response->getStatus() > 299 || (int) $response->getStatus() < 200)
{
if ($logger)
{
@ -218,7 +179,7 @@ trait KitsuTrait {
$response = $this->getResponse('POST', ...$args);
$validResponseCodes = [200, 201];
if ( ! in_array((int) $response->getStatusCode(), $validResponseCodes))
if ( ! in_array((int) $response->getStatus(), $validResponseCodes))
{
if ($logger)
{
@ -238,6 +199,6 @@ trait KitsuTrait {
protected function deleteRequest(...$args): bool
{
$response = $this->getResponse('DELETE', ...$args);
return ((int) $response->getStatusCode() === 204);
return ((int) $response->getStatus() === 204);
}
}

View File

@ -16,11 +16,12 @@
namespace Aviat\AnimeClient\API\Kitsu;
use const Aviat\AnimeClient\SESSION_SEGMENT;
use Amp\Artax\Request;
use Aviat\AnimeClient\API\AbstractListItem;
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Json;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Response;
use RuntimeException;
/**
@ -30,12 +31,22 @@ class ListItem extends AbstractListItem {
use ContainerAware;
use KitsuTrait;
public function __construct()
private function getAuthHeader()
{
$this->init();
$sessionSegment = $this->getContainer()
->get('session')
->getSegment(SESSION_SEGMENT);
if ($sessionSegment->get('auth_token') !== null)
{
$token = $sessionSegment->get('auth_token');
return "bearer {$token}";
}
return FALSE;
}
public function create(array $data): bool
public function create(array $data): Request
{
$body = [
'data' => [
@ -61,20 +72,34 @@ class ListItem extends AbstractListItem {
]
];
$request = $this->requestBuilder->newRequest('POST', 'library-entries')
->setJsonBody($body)
->getFullRequest();
$response = $this->getResponse('POST', 'library-entries', [
'body' => Json::encode($body)
]);
$authHeader = $this->getAuthHeader();
return ($response->getStatusCode() === 201);
$request = $this->requestBuilder->newRequest('POST', 'library-entries');
if ($authHeader !== FALSE)
{
$request = $request->setHeader('Authorization', $authHeader);
}
return $request->setJsonBody($body)
->getFullRequest();
// return ($response->getStatus() === 201);
}
public function delete(string $id): bool
public function delete(string $id): Request
{
$response = $this->getResponse('DELETE', "library-entries/{$id}");
return ($response->getStatusCode() === 204);
$authHeader = $this->getAuthHeader();
$request = $this->requestBuilder->newRequest('DELETE', "library-entries/{$id}");
if ($authHeader !== FALSE)
{
$request = $request->setHeader('Authorization', $authHeader);
}
return $request->getFullRequest();
// return ($response->getStatus() === 204);
}
public function get(string $id): array
@ -84,17 +109,12 @@ class ListItem extends AbstractListItem {
'include' => 'media,media.genres,media.mappings'
])
->getFullRequest();
/*return $this->getRequest("library-entries/{$id}", [
'query' => [
'include' => 'media,media.genres,media.mappings'
]
]);*/
$response = \Amp\wait((new \Amp\Artax\Client)->request($request));
return Json::decode($response->getBody());
}
public function update(string $id, array $data): Response
public function update(string $id, array $data): Request
{
$requestData = [
'data' => [

View File

@ -16,6 +16,7 @@
namespace Aviat\AnimeClient\API\Kitsu;
use Amp\Artax\Request;
use Aviat\AnimeClient\API\CacheTrait;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\AnimeClient\API\Kitsu as K;
@ -27,7 +28,6 @@ use Aviat\AnimeClient\API\Kitsu\Transformer\{
};
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Json;
use GuzzleHttp\Exception\ClientException;
/**
* Kitsu API Model
@ -72,9 +72,6 @@ class Model {
*/
public function __construct(ListItem $listItem)
{
// Set up Guzzle trait
$this->init();
$this->animeTransformer = new AnimeTransformer();
$this->animeListTransformer = new AnimeListTransformer();
$this->listItem = $listItem;
@ -355,9 +352,9 @@ class Model {
* Create a list item
*
* @param array $data
* @return bool
* @return Request
*/
public function createListItem(array $data): bool
public function createListItem(array $data): Request
{
$data['user_id'] = $this->getUserIdByUsername($this->getUsername());
return $this->listItem->create($data);
@ -397,22 +394,22 @@ class Model {
* Modify a list item
*
* @param array $data
* @return array
* @return Request
*/
public function updateListItem(array $data)
public function updateListItem(array $data): Request
{
try
{
$response = $this->listItem->update($data['id'], $data['data']);
return [
'statusCode' => $response->getStatusCode(),
'statusCode' => $response->getStatus(),
'body' => $response->getBody(),
];
}
catch(ClientException $e)
{
return [
'statusCode' => $e->getResponse()->getStatusCode(),
'statusCode' => $e->getResponse()->getStatus(),
'body' => Json::decode((string)$e->getResponse()->getBody())
];
}
@ -422,9 +419,9 @@ class Model {
* Remove a list item
*
* @param string $id - The id of the list item to remove
* @return bool
* @return Request
*/
public function deleteListItem(string $id): bool
public function deleteListItem(string $id): Request
{
return $this->listItem->delete($id);
}

View File

@ -16,7 +16,7 @@
namespace Aviat\AnimeClient\API;
use GuzzleHttp\Psr7\Response;
use Amp\Artax\Request;
/**
* Common interface for anime and manga list item CRUD
@ -29,7 +29,7 @@ interface ListItemInterface {
* @param array $data -
* @return bool
*/
public function create(array $data): bool;
public function create(array $data): Request;
/**
* Retrieve a list item
@ -46,7 +46,7 @@ interface ListItemInterface {
* @param array $data - The data with which to update the list item
* @return Response
*/
public function update(string $id, array $data): Response;
public function update(string $id, array $data): Request;
/**
* Delete a list item
@ -54,5 +54,5 @@ interface ListItemInterface {
* @param string $id - The id of the list item to delete
* @return bool
*/
public function delete(string $id): bool;
public function delete(string $id): Request;
}

View File

@ -16,7 +16,7 @@
namespace Aviat\AnimeClient\API\MAL;
use Amp\Artax\FormBody;
use Amp\Artax\Request;
use Aviat\AnimeClient\API\{
AbstractListItem,
XML
@ -30,7 +30,7 @@ class ListItem {
use ContainerAware;
use MALTrait;
public function create(array $data)
public function create(array $data): Request
{
$id = $data['id'];
$createData = [
@ -40,29 +40,36 @@ class ListItem {
])
];
// $config = $this->container->get('config');
$config = $this->container->get('config');
/*$request = $this->requestBuilder->newRequest('POST', "animelist/add/{$id}.xml")
return $this->requestBuilder->newRequest('POST', "animelist/add/{$id}.xml")
->setFormFields($createData)
->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
->getFullRequest();*/
->getFullRequest();
$response = $this->getResponse('POST', "animelist/add/{$id}.xml", [
/* $response = $this->getResponse('POST', "animelist/add/{$id}.xml", [
'body' => $this->fixBody((new FormBody)->addFields($createData))
]);
return $response->getBody() === 'Created';
// return $request;
return $response->getBody() === 'Created'; */
}
public function delete(string $id): bool
public function delete(string $id): Request
{
$response = $this->getResponse('DELETE', "animelist/delete/{$id}.xml", [
$config = $this->container->get('config');
return $this->requestBuilder->newRequest('DELETE', "animelist/delete/{$id}.xml")
->setFormFields([
'id' => $id
])
->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
->getFullRequest();
/*$response = $this->getResponse('DELETE', "animelist/delete/{$id}.xml", [
'body' => $this->fixBody((new FormBody)->addField('id', $id))
]);
return $response->getBody() === 'Deleted';
return $response->getBody() === 'Deleted';*/
}
public function get(string $id): array
@ -70,15 +77,25 @@ class ListItem {
return [];
}
public function update(string $id, array $data)
public function update(string $id, array $data): Request
{
$config = $this->container->get('config');
$xml = XML::toXML(['entry' => $data]);
$body = (new FormBody)
->addField('id', $id)
->addField('data', $xml);
return $this->getResponse('POST', "animelist/update/{$id}.xml", [
return $this->requestBuilder->newRequest('POST', "animelist/update/{$id}.xml")
->setFormFields([
'id' => $id,
'data' => $xml
])
->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
->getFullRequest();
/* return $this->getResponse('POST', "animelist/update/{$id}.xml", [
'body' => $this->fixBody($body)
]);
]); */
}
}

View File

@ -85,7 +85,7 @@ trait MALTrait {
* @param string $type
* @param string $url
* @param array $options
* @return \Amp\Promise
* @return \Amp\Artax\Response
*/
public function setUpRequest(string $type, string $url, array $options = [])
{

View File

@ -16,6 +16,7 @@
namespace Aviat\AnimeClient\API\MAL;
use Amp\Artax\Request;
use Aviat\AnimeClient\API\MAL as M;
use Aviat\AnimeClient\API\MAL\ListItem;
use Aviat\AnimeClient\API\MAL\Transformer\AnimeListTransformer;
@ -43,7 +44,7 @@ class Model {
$this->listItem = $listItem;
}
public function createListItem(array $data): bool
public function createListItem(array $data): Request
{
$createData = [
'id' => $data['id'],
@ -77,13 +78,13 @@ class Model {
return [];
}
public function updateListItem(array $data)
public function updateListItem(array $data): Request
{
$updateData = $this->animeListTransformer->untransform($data);
return $this->listItem->update($updateData['id'], $updateData['data']);
}
public function deleteListItem(string $id): bool
public function deleteListItem(string $id): Request
{
return $this->listItem->delete($id);
}

View File

@ -20,50 +20,43 @@ use Yosymfony\Toml\Toml;
define('SRC_DIR', realpath(__DIR__));
const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth';
const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller';
const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime';
const DEFAULT_CONTROLLER_METHOD = 'index';
const NOT_FOUND_METHOD = 'notFound';
const ERROR_MESSAGE_METHOD = 'errorPage';
const SRC_DIR = SRC_DIR;
/**
* Application constants
* Load configuration options from .toml files
*
* @param string $path - Path to load config
* @return array
*/
class AnimeClient {
function loadToml(string $path): array
{
$output = [];
$files = glob("{$path}/*.toml");
const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth';
const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller';
const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime';
const DEFAULT_CONTROLLER_METHOD = 'index';
const NOT_FOUND_METHOD = 'notFound';
const ERROR_MESSAGE_METHOD = 'errorPage';
const SRC_DIR = SRC_DIR;
/**
* Load configuration options from .toml files
*
* @param string $path - Path to load config
* @return array
*/
public static function loadToml(string $path): array
foreach ($files as $file)
{
$output = [];
$files = glob("{$path}/*.toml");
$key = str_replace('.toml', '', basename($file));
$toml = file_get_contents($file);
$config = Toml::Parse($toml);
foreach ($files as $file)
if ($key === 'config')
{
$key = str_replace('.toml', '', basename($file));
$toml = file_get_contents($file);
$config = Toml::Parse($toml);
if ($key === 'config')
foreach($config as $name => $value)
{
foreach($config as $name => $value)
{
$output[$name] = $value;
}
continue;
$output[$name] = $value;
}
$output[$key] = $config;
continue;
}
return $output;
$output[$key] = $config;
}
return $output;
}
// End of AnimeClient.php

View File

@ -16,21 +16,18 @@
namespace Aviat\AnimeClient\Command;
use function Aviat\AnimeClient\loadToml;
use Aura\Session\SessionFactory;
use Aviat\AnimeClient\{
AnimeClient,
Model,
Util
};
use Aviat\AnimeClient\API\CacheTrait;
use Aviat\AnimeClient\API\Kitsu\{
Auth as KitsuAuth,
ListItem as KitsuListItem,
Model as KitsuModel
};
use Aviat\AnimeClient\API\MAL\{
ListItem as MALListItem,
Model as MALModel
use Aviat\AnimeClient\API\{
CacheTrait,
Kitsu,
MAL
};
use Aviat\Banker\Pool;
use Aviat\Ion\Config;
@ -72,7 +69,7 @@ class BaseCommand extends Command {
$CONF_DIR = realpath("{$APP_DIR}/config/");
require_once $CONF_DIR . '/base_config.php'; // $base_config
$config = AnimeClient::loadToml($CONF_DIR);
$config = loadToml($CONF_DIR);
$config_array = array_merge($base_config, $config);
$di = function ($config_array) use ($APP_DIR) {
@ -84,10 +81,13 @@ class BaseCommand extends Command {
$app_logger = new Logger('animeclient');
$app_logger->pushHandler(new NullHandler);
$request_logger = new Logger('request');
$request_logger->pushHandler(new NullHandler);
$kitsu_request_logger = new Logger('kitsu-request');
$kitsu_request_logger->pushHandler(new NullHandler);
$mal_request_logger = new Logger('mal-request');
$mal_request_logger->pushHandler(new NullHandler);
$container->setLogger($app_logger, 'default');
$container->setLogger($request_logger, 'request');
$container->setLogger($kitsu_request_logger, 'kitsu-request');
$container->setLogger($mal_request_logger, 'mal-request');
// Create Config Object
$container->set('config', function() use ($config_array) {
@ -108,18 +108,18 @@ class BaseCommand extends Command {
// Models
$container->set('kitsu-model', function($container) {
$listItem = new KitsuListItem();
$listItem = new Kitsu\istItem();
$listItem->setContainer($container);
$model = new KitsuModel($listItem);
$model = new Kitsu\Model($listItem);
$model->setContainer($container);
$cache = $container->get('cache');
$model->setCache($cache);
return $model;
});
$container->set('mal-model', function($container) {
$listItem = new MALListItem();
$listItem = new MAL\ListItem();
$listItem->setContainer($container);
$model = new MALModel($listItem);
$model = new MAL\Model($listItem);
$model->setContainer($container);
return $model;
});

View File

@ -25,6 +25,35 @@ use Aviat\AnimeClient\API\Kitsu;
class SyncKitsuWithMal extends BaseCommand {
protected $kitsuModel;
protected $malModel;
/**
* Run the image conversion script
*
* @param array $args
* @param array $options
* @return void
* @throws \ConsoleKit\ConsoleException
*/
public function execute(array $args, array $options = [])
{
$this->setContainer($this->setupContainer());
$this->setCache($this->container->get('cache'));
$this->kitsuModel = $this->container->get('kitsu-model');
$this->malModel = $this->container->get('mal-model');
//$kitsuCount = $this->getKitsuAnimeListPageCount();
//$this->echoBox("List item count: {$kitsuCount}");
$this->MALItemCreate();
//echo json_encode($this->getMALList(), \JSON_PRETTY_PRINT);
}
public function getMALList()
{
return $this->malModel->getFullList();
}
public function getKitsuAnimeListPageCount()
{
@ -35,7 +64,7 @@ class SyncKitsuWithMal extends BaseCommand {
'user_id' => $this->kitsuModel->getUserIdByUsername(),
'media_type' => 'Anime'
],
'include' => 'anime,anime.genres,anime.mappings,anime.streamingLinks',
// 'include' => 'anime,anime.genres,anime.mappings,anime.streamingLinks',
'page' => [
'limit' => 1
],
@ -66,21 +95,47 @@ class SyncKitsuWithMal extends BaseCommand {
return $body['meta']['count'];
}
/**
* Run the image conversion script
*
* @param array $args
* @param array $options
* @return void
* @throws \ConsoleKit\ConsoleException
*/
public function execute(array $args, array $options = [])
public function MALItemCreate()
{
$this->setContainer($this->setupContainer());
$this->setCache($this->container->get('cache'));
$this->kitsuModel = $this->container->get('kitsu-model');
$input = json_decode('{
"watching_status": "current",
"user_rating": "",
"episodes_watched": "4",
"rewatched": "0",
"notes": "",
"id": "15794526",
"mal_id": "33731",
"edit": "true"
}', TRUE);
$response = $this->malModel->createListItem([
'id' => 12255,
'status' => 'planned',
'type' => 'anime'
]);
//$response = $this->malModel->updateListItem($input);
//print_r($response);
//echo $response->getBody();
$kitsuCount = $this->getKitsuAnimeListPageCount();
$this->echoBox("List item count: {$kitsuCount}");
}
public function diffLists()
{
// Get libraryEntries with media.mappings from Kitsu
// Organize mappings, and ignore entries without mappings
// Get MAL list data
// Compare each list entry
// If a list item exists only on MAL, create it on Kitsu with the existing data from MAL
// If a list item exists only on Kitsu, create it on MAL with the existing data from Kitsu
// If an item already exists on both APIS:
// Compare last updated dates, and use the later one
// Otherwise, use rewatch count, then episode progress as critera for selecting the more up
// to date entry
// Based on the 'newer' entry, update the other api list item
}
}

View File

@ -16,6 +16,8 @@
namespace Aviat\AnimeClient;
use const Aviat\AnimeClient\SESSION_SEGMENT;
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
use Aviat\Ion\View\{HtmlView, HttpView, JsonView};
use InvalidArgumentException;
@ -102,7 +104,7 @@ class Controller {
$this->urlGenerator = $urlGenerator;
$session = $container->get('session');
$this->session = $session->getSegment(AnimeClient::SESSION_SEGMENT);
$this->session = $session->getSegment(SESSION_SEGMENT);
// Set a 'previous' flash value for better redirects
$server_params = $this->request->getServerParams();

View File

@ -16,9 +16,16 @@
namespace Aviat\AnimeClient;
use const Aviat\AnimeClient\{
DEFAULT_CONTROLLER,
DEFAULT_CONTROLLER_NAMESPACE,
ERROR_MESSAGE_METHOD,
NOT_FOUND_METHOD,
SRC_DIR
};
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Friend;
use GuzzleHttp\Exception\ServerException;
/**
* Basic routing/ dispatch
@ -125,27 +132,12 @@ class Dispatcher extends RoutingBase {
// If not route was matched, return an appropriate http
// error message
$error_route = $this->getErrorParams();
$controllerName = AnimeClient::DEFAULT_CONTROLLER;
$controllerName = DEFAULT_CONTROLLER;
$actionMethod = $error_route['action_method'];
$params = $error_route['params'];
}
// Try to catch API errors in a presentable fashion
try
{
// Actually instantiate the controller
$this->call($controllerName, $actionMethod, $params);
}
catch (ServerException $e)
{
$response = $e->getResponse();
$this->call(AnimeClient::DEFAULT_CONTROLLER, AnimeClient::ERROR_MESSAGE_METHOD, [
$response->getStatusCode(),
'API Error',
'There was a problem getting data from an external source.',
(string) $response->getBody()
]);
}
$this->call($controllerName, $actionMethod, $params);
}
/**
@ -176,7 +168,7 @@ class Dispatcher extends RoutingBase {
$action_method = (array_key_exists('action', $route->attributes))
? $route->attributes['action']
: AnimeClient::NOT_FOUND_METHOD;
: NOT_FOUND_METHOD;
$params = [];
if ( ! empty($route->__get('tokens')))
@ -229,11 +221,11 @@ class Dispatcher extends RoutingBase {
*/
public function getControllerList()
{
$default_namespace = AnimeClient::DEFAULT_CONTROLLER_NAMESPACE;
$default_namespace = DEFAULT_CONTROLLER_NAMESPACE;
$path = str_replace('\\', '/', $default_namespace);
$path = str_replace('Aviat/AnimeClient/', '', $path);
$path = trim($path, '/');
$actual_path = realpath(_dir(AnimeClient::SRC_DIR, $path));
$actual_path = realpath(_dir(SRC_DIR, $path));
$class_files = glob("{$actual_path}/*.php");
$controllers = [];
@ -285,7 +277,7 @@ class Dispatcher extends RoutingBase {
$logger->info('Dispatcher - failed route');
$logger->info(print_r($failure, TRUE));
$action_method = AnimeClient::ERROR_MESSAGE_METHOD;
$action_method = ERROR_MESSAGE_METHOD;
$params = [];
@ -308,7 +300,7 @@ class Dispatcher extends RoutingBase {
default:
// Fall back to a 404 message
$action_method = AnimeClient::NOT_FOUND_METHOD;
$action_method = NOT_FOUND_METHOD;
break;
}
@ -337,7 +329,7 @@ class Dispatcher extends RoutingBase {
$controller_map = $this->getControllerList();
$controller_class = (array_key_exists($route_type, $controller_map))
? $controller_map[$route_type]
: AnimeClient::DEFAULT_CONTROLLER;
: DEFAULT_CONTROLLER;
if (array_key_exists($route_type, $controller_map))
{

View File

@ -38,12 +38,6 @@ class API extends Model {
*/
protected $cache;
/**
* Default settings for Guzzle
* @var array
*/
protected $connectionDefaults = [];
/**
* Constructor
*

View File

@ -15,6 +15,10 @@
*/
namespace Aviat\AnimeClient\Model;
use function Amp\some;
use function Amp\wait;
use Amp\Artax\Client;
use Aviat\AnimeClient\API\Kitsu\Enum\AnimeWatchingStatus;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json;
@ -91,9 +95,15 @@ class Anime extends API {
return $this->kitsuModel->getAnime($slug);
}
public function getAnimeById($anime_id)
/**
* Get anime by its kitsu id
*
* @param string $animeId
* @return array
*/
public function getAnimeById($animeId)
{
return $this->kitsuModel->getAnimeById($anime_id);
return $this->kitsuModel->getAnimeById($animeId);
}
/**
@ -104,7 +114,6 @@ class Anime extends API {
*/
public function search($name)
{
// $raw = $this->kitsuModel->search('anime', $name);
return $this->kitsuModel->search('anime', $name);
}
@ -128,6 +137,8 @@ class Anime extends API {
*/
public function createLibraryItem(array $data): bool
{
$requests = [];
if ($this->useMALAPI)
{
$malData = $data;
@ -136,11 +147,17 @@ class Anime extends API {
if ( ! is_null($malId))
{
$malData['id'] = $malId;
$this->malModel->createListItem($malData);
$requests['mal'] = $this->malModel->createListItem($malData);
}
}
return $this->kitsuModel->createListItem($data);
$requests['kitsu'] = $this->kitsuModel->createListItem($data);
$promises = (new Client)->requestMulti($requests);
$results = wait(some($promises));
return count($results[1]) > 0;
}
/**
@ -168,12 +185,18 @@ class Anime extends API {
*/
public function deleteLibraryItem(string $id, string $malId = null): bool
{
$requests = [];
if ($this->useMALAPI && ! is_null($malId))
{
$this->malModel->deleteListItem($malId);
$requests['mal'] = $this->malModel->deleteListItem($malId);
}
return $this->kitsuModel->deleteListItem($id);
$requests['kitsu'] = $this->kitsuModel->deleteListItem($id);
$results = wait(some((new Client)->requestMulti($requests)));
return count($results[1]) > 0;
}
}
// End of AnimeModel.php

View File

@ -124,7 +124,7 @@ class APIRequestBuilderTest extends TestCase {
$request = $this->builder->newRequest('PUT', 'https://httpbin.org/put')
->setHeader('Content-Type', 'application/json')
->setBody(Json::encode($data))
->setJsonBody($data)
->getFullRequest();
$response = Amp\wait((new Client)->request($request));
@ -133,4 +133,3 @@ class APIRequestBuilderTest extends TestCase {
$this->assertEquals($data, $body['json']);
}
}

View File

@ -1,12 +1,10 @@
<?php
use const Aviat\AnimeClient\SRC_DIR;
use Aura\Web\WebFactory;
use Aviat\AnimeClient\AnimeClient;
use Aviat\Ion\Json;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use Zend\Diactoros\{
Response as HttpResponse,
@ -23,7 +21,7 @@ define('TEST_VIEW_DIR', __DIR__ . '/test_views');
class AnimeClient_TestCase extends TestCase {
// Test directory constants
const ROOT_DIR = ROOT_DIR;
const SRC_DIR = AnimeClient::SRC_DIR;
const SRC_DIR = SRC_DIR;
const TEST_DATA_DIR = TEST_DATA_DIR;
const TEST_VIEW_DIR = TEST_VIEW_DIR;
@ -159,25 +157,5 @@ class AnimeClient_TestCase extends TestCase {
return Json::decode($rawData);
}
/**
* Create a mock guzzle client for testing
* api call methods
*
* @param int $code The status code
* @param array $headers
* @param string $body
* @return Client
*/
public function getMockClient($code, $headers, $body)
{
$mock = new MockHandler([
new Response($code, $headers, $body)
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
return $client;
}
}
// End of AnimeClient_TestCase.php