diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c237b88d..c7ea169f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -32,7 +32,7 @@ test:hhvm:
- /usr/local/bin/composer self-update
- curl -Lo /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar
- chmod +x /usr/local/bin/phpunit
- - composer install --no-dev
+ - composer install --no-dev --ignore-platform-reqs
image: 51systems/docker-gitlab-ci-runner-hhvm
script:
- hhvm -d hhvm.php7.all=true /usr/local/bin/phpunit -c build --coverage-text --colors=never
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index bd0fa78d..bcf84dd4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,11 @@
language: php
install:
- - composer install
+ - composer install --ignore-platform-reqs
php:
- 7
+ - 7.1
- hhvm
- nightly
diff --git a/app/config/base_config.php b/app/appConf/base_config.php
similarity index 100%
rename from app/config/base_config.php
rename to app/appConf/base_config.php
diff --git a/app/config/menus.php b/app/appConf/menus.php
similarity index 100%
rename from app/config/menus.php
rename to app/appConf/menus.php
diff --git a/app/config/minify_config.php b/app/appConf/minify_config.php
similarity index 100%
rename from app/config/minify_config.php
rename to app/appConf/minify_config.php
diff --git a/app/config/minify_css_groups.php b/app/appConf/minify_css_groups.php
similarity index 100%
rename from app/config/minify_css_groups.php
rename to app/appConf/minify_css_groups.php
diff --git a/app/config/minify_js_groups.php b/app/appConf/minify_js_groups.php
similarity index 100%
rename from app/config/minify_js_groups.php
rename to app/appConf/minify_js_groups.php
diff --git a/app/config/routes.php b/app/appConf/routes.php
similarity index 93%
rename from app/config/routes.php
rename to app/appConf/routes.php
index df9329ad..4ead95bf 100644
--- a/app/config/routes.php
+++ b/app/appConf/routes.php
@@ -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',
],
],
diff --git a/app/bootstrap.php b/app/bootstrap.php
index 01e06953..599a275b 100644
--- a/app/bootstrap.php
+++ b/app/bootstrap.php
@@ -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;
@@ -119,15 +111,15 @@ return function(array $config_array = []) {
$container->set('kitsu-model', function($container) {
$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);
-
+
$cache = $container->get('cache');
$model->setCache($cache);
return $model;
@@ -135,12 +127,12 @@ return function(array $config_array = []) {
$container->set('mal-model', function($container) {
$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);
diff --git a/app/views/header.php b/app/views/header.php
index 4326beb3..e4196144 100644
--- a/app/views/header.php
+++ b/app/views/header.php
@@ -6,6 +6,7 @@
+
diff --git a/composer.json b/composer.json
index c21f88ae..4b91d3b0 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
@@ -46,4 +48,4 @@
"build:css": "cd public && npm run build && cd ..",
"watch:css": "cd public && npm run watch"
}
-}
+}
\ No newline at end of file
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 00000000..c34a19de
Binary files /dev/null and b/favicon.ico differ
diff --git a/index.php b/index.php
index fff3616a..14eda8dc 100644
--- a/index.php
+++ b/index.php
@@ -15,6 +15,8 @@
*/
namespace Aviat\AnimeClient;
+use function Aviat\AnimeClient\loadToml;
+
use Aviat\AnimeClient\AnimeClient;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;
@@ -39,10 +41,11 @@ function _dir()
// Define base directories
$APP_DIR = _dir(__DIR__, 'app');
+$APPCONF_DIR = _dir($APP_DIR, 'appConf');
$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,18 +57,15 @@ $defaultHandler = new PrettyPageHandler();
$whoops->pushHandler($defaultHandler);
// Register as the error handler
-if (array_key_exists('whoops', $_GET))
-{
- $whoops->register();
-}
+$whoops->register();
// -----------------------------------------------------------------------------
// Dependency Injection setup
// -----------------------------------------------------------------------------
-require _dir($CONF_DIR, 'base_config.php'); // $base_config
+require _dir($APPCONF_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);
@@ -77,6 +77,4 @@ unset($CONF_DIR);
// -----------------------------------------------------------------------------
// Dispatch to the current route
// -----------------------------------------------------------------------------
-$container->get('dispatcher')->__invoke();
-
-// End of index.php
\ No newline at end of file
+$container->get('dispatcher')->__invoke();
\ No newline at end of file
diff --git a/public/css.php b/public/css.php
index 0f2f5a42..3253765b 100644
--- a/public/css.php
+++ b/public/css.php
@@ -160,7 +160,7 @@ class CSSMin extends BaseMin {
// --------------------------------------------------------------------------
//Get config files
-$config = require('../app/config/minify_config.php');
+$config = require('../app/appConf/minify_config.php');
$groups = require($config['css_groups_file']);
if ( ! array_key_exists($_GET['g'], $groups))
diff --git a/public/js.php b/public/js.php
index 22bc1718..e8f50232 100644
--- a/public/js.php
+++ b/public/js.php
@@ -16,8 +16,9 @@
namespace Aviat\EasyMin;
-use GuzzleHttp\Client;
-use GuzzleHttp\Psr7\Request;
+use function Amp\wait;
+use Amp\Artax\{Client, FormBody, Request};
+use Aviat\Ion\Json;
// Include guzzle
require_once('../vendor/autoload.php');
@@ -97,14 +98,21 @@ class JSMin extends BaseMin {
*/
protected function closure_call(array $options)
{
- $client = new Client();
- $response = $client->post('http://closure-compiler.appspot.com/compile', [
- 'headers' => [
+ $formFields = http_build_query($options);
+
+ $request = (new Request)
+ ->setMethod('POST')
+ ->setUri('http://closure-compiler.appspot.com/compile')
+ ->setAllHeaders([
+ 'Accept' => 'application/json',
'Accept-Encoding' => 'gzip',
'Content-type' => 'application/x-www-form-urlencoded'
- ],
- 'form_params' => $options
- ]);
+ ])
+ ->setBody($formFields);
+
+ $response = wait((new Client)->request($request, [
+ Client::OP_AUTO_ENCODING => false
+ ]));
return $response;
}
@@ -118,13 +126,14 @@ class JSMin extends BaseMin {
protected function check_minify_errors($options)
{
$error_res = $this->closure_call($options);
- $error_json = (string)$error_res->getBody();
- $error_obj = json_decode($error_json) ?: (object)[];
+ $error_json = $error_res->getBody();
+ $error_obj = Json::decode($error_json) ?: (object)[];
+
// Show error if exists
if ( ! empty($error_obj->errors) || ! empty($error_obj->serverErrors))
{
- $error_json = json_encode($error_obj, JSON_PRETTY_PRINT);
+ $error_json = Json::encode($error_obj, JSON_PRETTY_PRINT);
header('Content-type: application/javascript');
echo "console.error(${error_json});";
die();
@@ -201,10 +210,11 @@ class JSMin extends BaseMin {
// Now actually retrieve the compiled code
$options['output_info'] = 'compiled_code';
$res = $this->closure_call($options);
- $json = (string)$res->getBody();
- $obj = json_decode($json);
+ $json = $res->getBody();
+ $obj = Json::decode($json);
- return $obj->compiledCode;
+ //return $obj;
+ return $obj['compiledCode'];
}
/**
@@ -223,7 +233,7 @@ class JSMin extends BaseMin {
// ! Start Minifying
// --------------------------------------------------------------------------
-$config = require_once('../app/config/minify_config.php');
+$config = require_once('../app/appConf/minify_config.php');
$groups = require_once($config['js_groups_file']);
$cache_dir = "{$config['js_root']}cache";
diff --git a/src/API/APIRequestBuilder.php b/src/API/APIRequestBuilder.php
index 52fc91c2..4a96f63b 100644
--- a/src/API/APIRequestBuilder.php
+++ b/src/API/APIRequestBuilder.php
@@ -16,12 +16,14 @@
namespace Aviat\AnimeClient\API;
+use Amp;
use Amp\Artax\{
- Client,
- FormBody,
+ Client,
+ FormBody,
Request
};
use Aviat\Ion\Di\ContainerAware;
+use Aviat\Ion\Json;
use InvalidArgumentException;
use Psr\Log\LoggerAwareTrait;
@@ -30,56 +32,71 @@ use Psr\Log\LoggerAwareTrait;
*/
class APIRequestBuilder {
use LoggerAwareTrait;
-
+
/**
* Url prefix for making url requests
* @var string
*/
protected $baseUrl = '';
-
+
/**
* Url path of the request
* @var string
*/
protected $path = '';
-
+
/**
* Query string for the request
* @var string
*/
protected $query = '';
-
+
/**
* Default request headers
* @var array
*/
protected $defaultHeaders = [];
-
+
/**
* Valid HTTP request methos
* @var array
*/
protected $validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
-
+
/**
* The current request
* @var \Amp\Promise
*/
protected $request;
-
+
/**
- * Set body as form fields
- *
- * @param array $fields Mapping of field names to values
+ * 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 setFormFields(array $fields): self
+ public function setAuth(string $type, string $value): self
{
- $body = $this->fixBody((new FormBody)->addFields($createData));
- $this->setBody($body);
+ $authString = ucfirst($type) . ' ' . $value;
+ $this->setHeader('Authorization', $authString);
+
return $this;
}
-
+
+ /**
+ * Set a basic authentication header
+ *
+ * @param string $username
+ * @param string $password
+ * @return self
+ */
+ public function setBasicAuth(string $username, string $password): self
+ {
+ $this->setAuth('basic', base64_encode($username . ':' . $password));
+ return $this;
+ }
+
/**
* Set the request body
*
@@ -91,7 +108,21 @@ class APIRequestBuilder {
$this->request->setBody($body);
return $this;
}
-
+
+ /**
+ * Set body as form fields
+ *
+ * @param array $fields Mapping of field names to values
+ * @return self
+ */
+ public function setFormFields(array $fields): self
+ {
+ $this->setHeader("Content-Type", "application/x-www-form-urlencoded");
+ $body = (new FormBody)->addFields($fields);
+ $this->setBody($body);
+ return $this;
+ }
+
/**
* Set a request header
*
@@ -104,10 +135,10 @@ class APIRequestBuilder {
$this->request->setHeader($name, $value);
return $this;
}
-
+
/**
* Set multiple request headers
- *
+ *
* name => value
*
* @param array $headers
@@ -119,10 +150,25 @@ class APIRequestBuilder {
{
$this->setHeader($name, $value);
}
-
+
return $this;
}
-
+
+ /**
+ * Set the request body
+ *
+ * @param mixed $body
+ * @return self
+ */
+ public function setJsonBody($body): self
+ {
+ $requestBody = ( ! is_scalar($body))
+ ? Json::encode($body)
+ : $body;
+
+ return $this->setBody($requestBody);
+ }
+
/**
* Append a query string in array format
*
@@ -131,10 +177,10 @@ class APIRequestBuilder {
*/
public function setQuery(array $params): self
{
- $this->query = http_build_query($params);
+ $this->query = http_build_query($params);
return $this;
}
-
+
/**
* Return the promise for the current request
*
@@ -143,9 +189,19 @@ class APIRequestBuilder {
public function getFullRequest()
{
$this->buildUri();
+
+ if ($this->logger)
+ {
+ $this->logger->debug('API Request', [
+ 'request_url' => $this->request->getUri(),
+ 'request_headers' => $this->request->getAllHeaders(),
+ 'request_body' => $this->request->getBody()
+ ]);
+ }
+
return $this->request;
}
-
+
/**
* Create a new http request
*
@@ -159,16 +215,23 @@ class APIRequestBuilder {
{
throw new InvalidArgumentException('Invalid HTTP methods');
}
-
+
$this->resetState();
-
+
$this->request
->setMethod($type)
->setProtocol('1.1');
-
+
+ $this->path = $uri;
+
+ if ( ! empty($this->defaultHeaders))
+ {
+ $this->setHeaders($this->defaultHeaders);
+ }
+
return $this;
}
-
+
/**
* Create the full request url
*
@@ -178,31 +241,16 @@ class APIRequestBuilder {
{
$url = (strpos($this->path, '//') !== FALSE)
? $this->path
- : $this->baseUrl . $url;
+ : $this->baseUrl . $this->path;
if ( ! empty($this->query))
{
$url .= '?' . $this->query;
}
-
+
$this->request->setUri($url);
}
-
- /**
- * Unencode the dual-encoded ampersands in the body
- *
- * This is a dirty hack until I can fully track down where
- * the dual-encoding happens
- *
- * @param FormBody $formBody The form builder object to fix
- * @return string
- */
- private function fixBody(FormBody $formBody): string
- {
- $rawBody = \Amp\wait($formBody->getBody());
- return html_entity_decode($rawBody, \ENT_HTML5, 'UTF-8');
- }
-
+
/**
* Reset the class state for a new request
*
diff --git a/src/API/MAL/Transformer/MALToKitsuTransformer.php b/src/API/FailedResponseException.php
similarity index 66%
rename from src/API/MAL/Transformer/MALToKitsuTransformer.php
rename to src/API/FailedResponseException.php
index dc8fed17..648bb83d 100644
--- a/src/API/MAL/Transformer/MALToKitsuTransformer.php
+++ b/src/API/FailedResponseException.php
@@ -14,20 +14,10 @@
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
-namespace Aviat\AnimeClient\API\MAL;
+namespace Aviat\AnimeClient\API;
-use Aviat\Ion\Transformer\AbstractTransformer;
+use UnexpectedValueException;
+
+class FailedResponseException extends UnexpectedValueException {
-class MALToKitsuTransformer extends AbstractTransformer {
-
-
- public function transform($item)
- {
-
- }
-
- public function untransform($item)
- {
-
- }
}
\ No newline at end of file
diff --git a/src/API/GuzzleTrait.php b/src/API/GuzzleTrait.php
deleted file mode 100644
index 6af78cde..00000000
--- a/src/API/GuzzleTrait.php
+++ /dev/null
@@ -1,41 +0,0 @@
-
- * @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();
-}
\ No newline at end of file
diff --git a/src/API/Kitsu.php b/src/API/Kitsu.php
index 34907c90..117f7bdd 100644
--- a/src/API/Kitsu.php
+++ b/src/API/Kitsu.php
@@ -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
*/
@@ -91,7 +95,7 @@ class Kitsu {
return AnimeAiringStatus::NOT_YET_AIRED;
}
}
-
+
/**
* Get the name and logo for the streaming service of the current link
*
@@ -108,22 +112,22 @@ class Kitsu {
'link' => true,
'logo' => ''
];
-
+
case 'www.funimation.com':
return [
'name' => 'Funimation',
'link' => true,
'logo' => ''
];
-
+
case 'www.hulu.com':
return [
'name' => 'Hulu',
'link' => true,
'logo' => ''
];
-
- // Default to Netflix, because the API links are broken,
+
+ // Default to Netflix, because the API links are broken,
// and there's no other real identifier for Netflix
default:
return [
@@ -133,7 +137,7 @@ class Kitsu {
];
}
}
-
+
/**
* Reorganize streaming links
*
@@ -146,13 +150,13 @@ class Kitsu {
{
return [];
}
-
+
$links = [];
-
+
foreach ($included['streamingLinks'] as $streamingLink)
{
$host = parse_url($streamingLink['url'], \PHP_URL_HOST);
-
+
$links[] = [
'meta' => static::getServiceMetaData($host),
'link' => $streamingLink['url'],
@@ -160,10 +164,10 @@ class Kitsu {
'dubs' => $streamingLink['dubs']
];
}
-
+
return $links;
}
-
+
/**
* Reorganize streaming links for the current list item
*
@@ -192,10 +196,10 @@ class Kitsu {
];
}
}
-
+
return $links;
}
-
+
return [];
}
diff --git a/src/API/Kitsu/Auth.php b/src/API/Kitsu/Auth.php
index 70691eb1..ecee237b 100644
--- a/src/API/Kitsu/Auth.php
+++ b/src/API/Kitsu/Auth.php
@@ -16,6 +16,8 @@
namespace Aviat\AnimeClient\API\Kitsu;
+use const Aviat\AnimeClient\SESSION_SEGMENT;
+
use Aviat\AnimeClient\AnimeClient;
use Aviat\AnimeClient\API\{
CacheTrait,
@@ -55,7 +57,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');
}
@@ -70,7 +72,7 @@ class Auth {
{
$config = $this->container->get('config');
$username = $config->get(['kitsu_username']);
-
+
try
{
$auth = $this->model->authenticate($username, $password);
@@ -79,7 +81,7 @@ class Auth {
{
return FALSE;
}
-
+
if (FALSE !== $auth)
{
@@ -87,7 +89,7 @@ class Auth {
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_CACHE_KEY);
$cacheItem->set($auth['access_token']);
$cacheItem->save();
-
+
$this->segment->set('auth_token', $auth['access_token']);
return TRUE;
}
diff --git a/src/API/Kitsu/KitsuRequestBuilder.php b/src/API/Kitsu/KitsuRequestBuilder.php
index c155794a..7a3cab02 100644
--- a/src/API/Kitsu/KitsuRequestBuilder.php
+++ b/src/API/Kitsu/KitsuRequestBuilder.php
@@ -20,8 +20,8 @@ use Aviat\AnimeClient\API\APIRequestBuilder;
use Aviat\AnimeClient\API\Kitsu as K;
use Aviat\Ion\Json;
-class KitsuRequestBuilder extends APIRequestBuilder {
-
+class KitsuRequestBuilder extends APIRequestBuilder {
+
/**
* The base url for api requests
* @var string $base_url
@@ -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);
- }
-}
+}
\ No newline at end of file
diff --git a/src/API/Kitsu/KitsuTrait.php b/src/API/Kitsu/KitsuTrait.php
index 148c35c5..84bcf42c 100644
--- a/src/API/Kitsu/KitsuTrait.php
+++ b/src/API/Kitsu/KitsuTrait.php
@@ -16,55 +16,25 @@
namespace Aviat\AnimeClient\API\Kitsu;
+use const Aviat\AnimeClient\SESSION_SEGMENT;
+
+use function Amp\wait;
+
+use Amp\Artax\{Client, Request};
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;
trait KitsuTrait {
-
+
/**
* The request builder for the MAL API
* @var MALRequestBuilder
*/
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,49 @@ 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\Request
*/
- protected function init()
+ public function setUpRequest(string $type, string $url, array $options = []): Request
{
- $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);
+ }
+
+ if (array_key_exists('form_params', $options))
+ {
+ $request->setFormFields($options['form_params']);
+ }
+
+ 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 +99,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,11 +133,11 @@ 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)
{
- $logger->warning('Non 200 response for api call', $response->getBody());
+ $logger->warning('Non 200 response for api call', (array)$response->getBody());
}
}
@@ -218,7 +183,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 +203,6 @@ trait KitsuTrait {
protected function deleteRequest(...$args): bool
{
$response = $this->getResponse('DELETE', ...$args);
- return ((int) $response->getStatusCode() === 204);
+ return ((int) $response->getStatus() === 204);
}
}
\ No newline at end of file
diff --git a/src/API/Kitsu/ListItem.php b/src/API/Kitsu/ListItem.php
index 57787f15..509fab80 100644
--- a/src/API/Kitsu/ListItem.php
+++ b/src/API/Kitsu/ListItem.php
@@ -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,59 +31,100 @@ 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
{
- $response = $this->getResponse('POST', 'library-entries', [
- 'body' => Json::encode([
- 'data' => [
- 'type' => 'libraryEntries',
- 'attributes' => [
- 'status' => $data['status'],
- 'progress' => $data['progress'] ?? 0
+ $body = [
+ 'data' => [
+ 'type' => 'libraryEntries',
+ 'attributes' => [
+ 'status' => $data['status'],
+ 'progress' => $data['progress'] ?? 0
+ ],
+ 'relationships' => [
+ 'user' => [
+ 'data' => [
+ 'id' => $data['user_id'],
+ 'type' => 'users'
+ ]
],
- 'relationships' => [
- 'user' => [
- 'data' => [
- 'id' => $data['user_id'],
- 'type' => 'users'
- ]
- ],
- 'media' => [
- 'data' => [
- 'id' => $data['id'],
- 'type' => $data['type']
- ]
+ 'media' => [
+ 'data' => [
+ 'id' => $data['id'],
+ 'type' => $data['type']
]
]
]
- ])
- ]);
+ ]
+ ];
- return ($response->getStatusCode() === 201);
+ $authHeader = $this->getAuthHeader();
+
+ $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
{
- return $this->getRequest("library-entries/{$id}", [
- 'query' => [
+ $authHeader = $this->getAuthHeader();
+
+ $request = $this->requestBuilder->newRequest('GET', "library-entries/{$id}")
+ ->setQuery([
'include' => 'media,media.genres,media.mappings'
- ]
- ]);
+ ]);
+
+ if ($authHeader !== FALSE)
+ {
+ $request = $request->setHeader('Authorization', $authHeader);
+ }
+
+ $request = $request->getFullRequest();
+
+ $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
{
+ $authHeader = $this->getAuthHeader();
$requestData = [
'data' => [
'id' => $id,
@@ -90,11 +132,15 @@ class ListItem extends AbstractListItem {
'attributes' => $data
]
];
-
- $response = $this->getResponse('PATCH', "library-entries/{$id}", [
- 'body' => JSON::encode($requestData)
- ]);
-
- return $response;
+
+ $request = $this->requestBuilder->newRequest('PATCH', "library-entries/{$id}")
+ ->setJsonBody($requestData);
+
+ if ($authHeader !== FALSE)
+ {
+ $request = $request->setHeader('Authorization', $authHeader);
+ }
+
+ return $request->getFullRequest();
}
}
\ No newline at end of file
diff --git a/src/API/Kitsu/Model.php b/src/API/Kitsu/Model.php
index 7077f809..96fd54c5 100644
--- a/src/API/Kitsu/Model.php
+++ b/src/API/Kitsu/Model.php
@@ -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;
@@ -206,15 +203,56 @@ class Model {
$baseData = $this->getRawMediaData('manga', $mangaId);
return $this->mangaTransformer->transform($baseData);
}
+
+ /**
+ * Get the number of anime list items
+ *
+ * @return int
+ */
+ public function getAnimeListCount() : int
+ {
+ $options = [
+ 'query' => [
+ 'filter' => [
+ 'user_id' => $this->getUserIdByUsername(),
+ 'media_type' => 'Anime'
+ ],
+ 'page' => [
+ 'limit' => 1
+ ],
+ 'sort' => '-updated_at'
+ ]
+ ];
+
+ $response = $this->getRequest('library-entries', $options);
+
+ return $response['meta']['count'];
+
+ }
/**
* Get and transform the entirety of the user's anime list
*
- * @return array
+ * @return Request
*/
- public function getFullAnimeList(): array
+ public function getFullAnimeList(int $limit = 100, int $offset = 0): Request
{
-
+ $options = [
+ 'query' => [
+ 'filter' => [
+ 'user_id' => $this->getUserIdByUsername($this->getUsername()),
+ 'media_type' => 'Anime'
+ ],
+ 'include' => 'anime.mappings',
+ 'page' => [
+ 'offset' => $offset,
+ 'limit' => $limit
+ ],
+ 'sort' => '-updated_at'
+ ]
+ ];
+
+ return $this->setUpRequest('GET', 'library-entries', $options);
}
/**
@@ -355,9 +393,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,34 +435,20 @@ 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(),
- 'body' => $response->getBody(),
- ];
- }
- catch(ClientException $e)
- {
- return [
- 'statusCode' => $e->getResponse()->getStatusCode(),
- 'body' => Json::decode((string)$e->getResponse()->getBody())
- ];
- }
+ return $this->listItem->update($data['id'], $data['data']);
}
/**
* 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);
}
diff --git a/src/API/Kitsu/Transformer/AnimeListTransformer.php b/src/API/Kitsu/Transformer/AnimeListTransformer.php
index 2591aab0..cd87707b 100644
--- a/src/API/Kitsu/Transformer/AnimeListTransformer.php
+++ b/src/API/Kitsu/Transformer/AnimeListTransformer.php
@@ -116,7 +116,6 @@ class AnimeListTransformer extends AbstractTransformer {
'mal_id' => $item['mal_id'] ?? null,
'data' => [
'status' => $item['watching_status'],
- 'rating' => $item['user_rating'] / 2,
'reconsuming' => $rewatching,
'reconsumeCount' => $item['rewatched'],
'notes' => $item['notes'],
@@ -124,10 +123,10 @@ class AnimeListTransformer extends AbstractTransformer {
'private' => $privacy
]
];
-
- if ((int) $untransformed['data']['rating'] === 0)
+
+ if ( ! empty($item['user_rating']))
{
- unset($untransformed['data']['rating']);
+ $untransformed['data']['rating'] = $item['user_rating'] / 2;
}
return $untransformed;
diff --git a/src/API/ListItemInterface.php b/src/API/ListItemInterface.php
index b0001428..17f0cad8 100644
--- a/src/API/ListItemInterface.php
+++ b/src/API/ListItemInterface.php
@@ -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;
}
\ No newline at end of file
diff --git a/src/API/MAL.php b/src/API/MAL.php
index a346993a..972b4534 100644
--- a/src/API/MAL.php
+++ b/src/API/MAL.php
@@ -36,6 +36,14 @@ class MAL {
KAWS::DROPPED => AnimeWatchingStatus::DROPPED,
KAWS::PLAN_TO_WATCH => AnimeWatchingStatus::PLAN_TO_WATCH
];
+
+ const MAL_KITSU_WATCHING_STATUS_MAP = [
+ 1 => KAWS::WATCHING,
+ 2 => KAWS::COMPLETED,
+ 3 => KAWS::ON_HOLD,
+ 4 => KAWS::DROPPED,
+ 6 => KAWS::PLAN_TO_WATCH
+ ];
public static function getIdToWatchingStatusMap()
{
diff --git a/src/API/MAL/ListItem.php b/src/API/MAL/ListItem.php
index 715be392..686c3455 100644
--- a/src/API/MAL/ListItem.php
+++ b/src/API/MAL/ListItem.php
@@ -16,7 +16,7 @@
namespace Aviat\AnimeClient\API\MAL;
-use Amp\Artax\FormBody;
+use Amp\Artax\{FormBody, Request};
use Aviat\AnimeClient\API\{
AbstractListItem,
XML
@@ -30,7 +30,7 @@ class ListItem {
use ContainerAware;
use MALTrait;
- public function create(array $data): bool
+ public function create(array $data): Request
{
$id = $data['id'];
$createData = [
@@ -40,20 +40,26 @@ class ListItem {
])
];
- $response = $this->getResponse('POST', "animelist/add/{$id}.xml", [
- 'body' => $this->fixBody((new FormBody)->addFields($createData))
- ]);
+ $config = $this->container->get('config');
- return $response->getBody() === 'Created';
+ return $this->requestBuilder->newRequest('POST', "animelist/add/{$id}.xml")
+ ->setFormFields($createData)
+ ->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
+ ->getFullRequest();
}
- public function delete(string $id): bool
+ public function delete(string $id): Request
{
- $response = $this->getResponse('DELETE', "animelist/delete/{$id}.xml", [
- 'body' => $this->fixBody((new FormBody)->addField('id', $id))
- ]);
+ $config = $this->container->get('config');
- return $response->getBody() === 'Deleted';
+ return $this->requestBuilder->newRequest('DELETE', "animelist/delete/{$id}.xml")
+ ->setFormFields([
+ 'id' => $id
+ ])
+ ->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
+ ->getFullRequest();
+
+ // return $response->getBody() === 'Deleted'
}
public function get(string $id): array
@@ -61,15 +67,21 @@ 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", [
- 'body' => $this->fixBody($body)
- ]);
+ return $this->requestBuilder->newRequest('POST', "animelist/update/{$id}.xml")
+ ->setFormFields([
+ 'id' => $id,
+ 'data' => $xml
+ ])
+ ->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
+ ->getFullRequest();
}
}
\ No newline at end of file
diff --git a/src/API/MAL/MALRequestBuilder.php b/src/API/MAL/MALRequestBuilder.php
index ca0d88b1..326ad44a 100644
--- a/src/API/MAL/MALRequestBuilder.php
+++ b/src/API/MAL/MALRequestBuilder.php
@@ -23,7 +23,7 @@ use Aviat\AnimeClient\API\{
};
class MALRequestBuilder extends APIRequestBuilder {
-
+
/**
* The base url for api requests
* @var string $base_url
@@ -41,7 +41,7 @@ class MALRequestBuilder extends APIRequestBuilder {
'Content-type' => 'application/x-www-form-urlencoded',
'User-Agent' => "Tim's Anime Client/4.0"
];
-
+
/**
* Valid HTTP request methos
* @var array
diff --git a/src/API/MAL/MALTrait.php b/src/API/MAL/MALTrait.php
index 5d8e84dc..ba69da6e 100644
--- a/src/API/MAL/MALTrait.php
+++ b/src/API/MAL/MALTrait.php
@@ -27,7 +27,7 @@ use Aviat\Ion\Json;
use InvalidArgumentException;
trait MALTrait {
-
+
/**
* The request builder for the MAL API
* @var MALRequestBuilder
@@ -51,7 +51,7 @@ trait MALTrait {
'Content-type' => 'application/x-www-form-urlencoded',
'User-Agent' => "Tim's Anime Client/4.0"
];
-
+
/**
* Set the request builder object
*
@@ -63,7 +63,7 @@ trait MALTrait {
$this->requestBuilder = $requestBuilder;
return $this;
}
-
+
/**
* Unencode the dual-encoded ampersands in the body
*
@@ -78,58 +78,34 @@ trait MALTrait {
$rawBody = \Amp\wait($formBody->getBody());
return html_entity_decode($rawBody, \ENT_HTML5, 'UTF-8');
}
-
+
/**
* Create a request object
*
* @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 = [])
{
- $this->defaultHeaders['User-Agent'] = $_SERVER['HTTP_USER_AGENT'] ?? $this->defaultHeaders;
-
- $type = strtoupper($type);
- $validTypes = ['GET', 'POST', 'DELETE'];
-
- if ( ! in_array($type, $validTypes))
- {
- throw new InvalidArgumentException('Invalid http request type');
- }
-
$config = $this->container->get('config');
- $logger = $this->container->getLogger('mal-request');
- $headers = array_merge($this->defaultHeaders, $options['headers'] ?? [], [
- 'Authorization' => 'Basic ' .
- base64_encode($config->get(['mal','username']) . ':' .$config->get(['mal','password']))
- ]);
+ $request = $this->requestBuilder
+ ->newRequest($type, $url)
+ ->setBasicAuth($config->get(['mal','username']), $config->get(['mal','password']));
- $query = $options['query'] ?? [];
-
- $url = (strpos($url, '//') !== FALSE)
- ? $url
- : $this->baseUrl . $url;
-
- if ( ! empty($query))
+ if (array_key_exists('query', $options))
{
- $url .= '?' . http_build_query($query);
+ $request->setQuery($options['query']);
}
- $request = (new Request)
- ->setMethod($type)
- ->setUri($url)
- ->setProtocol('1.1')
- ->setAllHeaders($headers);
-
if (array_key_exists('body', $options))
{
$request->setBody($options['body']);
}
-
- return $request;
+
+ return $request->getFullRequest();
}
/**
@@ -147,19 +123,16 @@ trait MALTrait {
{
$logger = $this->container->getLogger('mal-request');
}
-
+
$request = $this->setUpRequest($type, $url, $options);
$response = \Amp\wait((new Client)->request($request));
- $logger->debug('MAL api request', [
- 'url' => $url,
+ $logger->debug('MAL api response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
+ 'body' => $response->getBody(),
'headers' => $response->getAllHeaders(),
'requestHeaders' => $request->getAllHeaders(),
- 'requestBody' => $request->hasBody() ? $request->getBody() : 'No request body',
- 'requestBodyBeforeEncode' => $request->hasBody() ? urldecode($request->getBody()) : '',
- 'body' => $response->getBody()
]);
return $response;
diff --git a/src/API/MAL/Model.php b/src/API/MAL/Model.php
index a2d0633e..46355b27 100644
--- a/src/API/MAL/Model.php
+++ b/src/API/MAL/Model.php
@@ -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;
@@ -35,15 +36,20 @@ class Model {
protected $animeListTransformer;
/**
- * KitsuModel constructor.
+ * MAL Model constructor.
*/
public function __construct(ListItem $listItem)
{
$this->animeListTransformer = new AnimeListTransformer();
$this->listItem = $listItem;
}
+
+ public function createFullListItem(array $data): Request
+ {
+ return $this->listItem->create($data);
+ }
- public function createListItem(array $data): bool
+ public function createListItem(array $data): Request
{
$createData = [
'id' => $data['id'],
@@ -69,7 +75,7 @@ class Model {
]
]);
- return $list;//['anime'];
+ return $list['myanimelist']['anime'];
}
public function getListItem(string $listId): array
@@ -77,13 +83,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);
}
diff --git a/src/API/MAL/Transformer/AnimeListTransformer.php b/src/API/MAL/Transformer/AnimeListTransformer.php
index 38cf8182..16f59d8a 100644
--- a/src/API/MAL/Transformer/AnimeListTransformer.php
+++ b/src/API/MAL/Transformer/AnimeListTransformer.php
@@ -32,6 +32,12 @@ class AnimeListTransformer extends AbstractTransformer {
AnimeWatchingStatus::PLAN_TO_WATCH => '6'
];
+ /**
+ * Transform MAL episode data to Kitsu episode data
+ *
+ * @param array $item
+ * @return array
+ */
public function transform($item)
{
$rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']);
@@ -57,28 +63,44 @@ class AnimeListTransformer extends AbstractTransformer {
*/
public function untransform(array $item): array
{
- $rewatching = (array_key_exists('reconsuming', $item['data']) && $item['data']['reconsuming']);
-
$map = [
'id' => $item['mal_id'],
'data' => [
- 'episode' => $item['data']['progress'],
- // 'enable_rewatching' => $rewatching,
- // 'times_rewatched' => $item['data']['reconsumeCount'],
- // 'comments' => $item['data']['notes'],
+ 'episode' => $item['data']['progress']
]
];
- if (array_key_exists('rating', $item['data']))
- {
- $map['data']['score'] = $item['data']['rating'] * 2;
- }
+ $data =& $item['data'];
- if (array_key_exists('status', $item['data']))
+ foreach($item['data'] as $key => $value)
{
- $map['data']['status'] = self::statusMap[$item['data']['status']];
+ switch($key)
+ {
+ case 'notes':
+ $map['data']['comments'] = $value;
+ break;
+
+ case 'rating':
+ $map['data']['score'] = $value * 2;
+ break;
+
+ case 'reconsuming':
+ $map['data']['enable_rewatching'] = (bool) $value;
+ break;
+
+ case 'reconsumeCount':
+ $map['data']['times_rewatched'] = $value;
+ break;
+
+ case 'status':
+ $map['data']['status'] = self::statusMap[$value];
+ break;
+
+ default:
+ break;
+ }
}
-
+
return $map;
}
}
\ No newline at end of file
diff --git a/src/AnimeClient.php b/src/AnimeClient.php
index 0ffaca69..b43531bd 100644
--- a/src/AnimeClient.php
+++ b/src/AnimeClient.php
@@ -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;
}
-}
-// End of AnimeClient.php
\ No newline at end of file
+
+ return $output;
+}
\ No newline at end of file
diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php
index e929578f..cff2621c 100644
--- a/src/Command/BaseCommand.php
+++ b/src/Command/BaseCommand.php
@@ -16,6 +16,8 @@
namespace Aviat\AnimeClient\Command;
+use function Aviat\AnimeClient\loadToml;
+
use Aura\Session\SessionFactory;
use Aviat\AnimeClient\{
AnimeClient,
@@ -23,15 +25,9 @@ use Aviat\AnimeClient\{
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\{Kitsu, MAL};
+use Aviat\AnimeClient\API\Kitsu\KitsuRequestBuilder;
+use Aviat\AnimeClient\API\MAL\MALRequestBuilder;
use Aviat\Banker\Pool;
use Aviat\Ion\Config;
use Aviat\Ion\Di\{Container, ContainerAware};
@@ -69,26 +65,30 @@ class BaseCommand extends Command {
protected function setupContainer()
{
$APP_DIR = realpath(__DIR__ . '/../../app');
+ $APPCONF_DIR = realpath("{$APP_DIR}/appConf/");
$CONF_DIR = realpath("{$APP_DIR}/config/");
- require_once $CONF_DIR . '/base_config.php'; // $base_config
+ require_once $APPCONF_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) {
$container = new Container();
-
+
// -------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------
$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) {
return new Config($config_array);
@@ -108,21 +108,35 @@ class BaseCommand extends Command {
// Models
$container->set('kitsu-model', function($container) {
- $listItem = new KitsuListItem();
+ $requestBuilder = new KitsuRequestBuilder();
+ $requestBuilder->setLogger($container->getLogger('kitsu-request'));
+
+ $listItem = new Kitsu\ListItem();
$listItem->setContainer($container);
- $model = new KitsuModel($listItem);
+ $listItem->setRequestBuilder($requestBuilder);
+
+ $model = new Kitsu\Model($listItem);
$model->setContainer($container);
+ $model->setRequestBuilder($requestBuilder);
+
$cache = $container->get('cache');
$model->setCache($cache);
return $model;
});
$container->set('mal-model', function($container) {
- $listItem = new MALListItem();
+ $requestBuilder = new MALRequestBuilder();
+ $requestBuilder->setLogger($container->getLogger('mal-request'));
+
+ $listItem = new MAL\ListItem();
$listItem->setContainer($container);
- $model = new MALModel($listItem);
+ $listItem->setRequestBuilder($requestBuilder);
+
+ $model = new MAL\Model($listItem);
$model->setContainer($container);
+ $model->setRequestBuilder($requestBuilder);
return $model;
});
+
$container->set('util', function($container) {
return new Util($container);
});
diff --git a/src/Command/SyncKitsuWithMal.php b/src/Command/SyncKitsuWithMal.php
index 93a714c2..c219bfff 100644
--- a/src/Command/SyncKitsuWithMal.php
+++ b/src/Command/SyncKitsuWithMal.php
@@ -16,56 +16,22 @@
namespace Aviat\AnimeClient\Command;
+use function Amp\{all, wait};
+
use Amp\Artax;
-use Aviat\AnimeClient\API\Kitsu;
+use Amp\Artax\Client;
+use Aviat\AnimeClient\API\{JsonAPI, Kitsu, MAL};
+use Aviat\AnimeClient\API\MAL\Transformer\AnimeListTransformer as ALT;
+use Aviat\Ion\Json;
/**
* Clears the API Cache
*/
class SyncKitsuWithMal extends BaseCommand {
-
+
protected $kitsuModel;
-
- public function getKitsuAnimeListPageCount()
- {
- $cacheItem = $this->cache->getItem(Kitsu::AUTH_TOKEN_CACHE_KEY);
-
- $query = http_build_query([
- 'filter' => [
- 'user_id' => $this->kitsuModel->getUserIdByUsername(),
- 'media_type' => 'Anime'
- ],
- 'include' => 'anime,anime.genres,anime.mappings,anime.streamingLinks',
- 'page' => [
- 'limit' => 1
- ],
- 'sort' => '-updated_at'
- ]);
- $request = (new Artax\Request)
- ->setUri("https://kitsu.io/api/edge/library-entries?{$query}")
- ->setProtocol('1.1')
- ->setAllHeaders([
- 'Accept' => 'application/vnd.api+json',
- 'Content-Type' => 'application/vnd.api+json',
- 'User-Agent' => "Tim's Anime Client/4.0"
- ]);
-
- if ($cacheItem->isHit())
- {
- $token = $cacheItem->get();
- $request->setHeader('Authorization', "bearer {$token}");
- }
- else
- {
- $this->echoBox("WARNING: NOT LOGGED IN\nSome data might be missing");
- }
-
- $response = \Amp\wait((new Artax\Client)->request($request));
-
- $body = json_decode($response->getBody(), TRUE);
- return $body['meta']['count'];
- }
-
+ protected $malModel;
+
/**
* Run the image conversion script
*
@@ -79,8 +45,216 @@ class SyncKitsuWithMal extends BaseCommand {
$this->setContainer($this->setupContainer());
$this->setCache($this->container->get('cache'));
$this->kitsuModel = $this->container->get('kitsu-model');
+ $this->malModel = $this->container->get('mal-model');
+ $malCount = count($this->getMALList());
$kitsuCount = $this->getKitsuAnimeListPageCount();
- $this->echoBox("List item count: {$kitsuCount}");
+
+ $this->echoBox("Number of MAL list items: {$malCount}");
+ $this->echoBox("Number of Kitsu list items: {$kitsuCount}");
+
+ $data = $this->diffLists();
+ $this->echoBox("Number of items that need to be added to MAL: " . count($data));
+
+ if (! empty($data['addToMAL']))
+ {
+ $this->echoBox("Adding missing list items to MAL");
+ $this->createMALListItems($data['addToMAL']);
+ }
+
}
-}
+
+ public function getKitsuList()
+ {
+ $count = $this->getKitsuAnimeListPageCount();
+ $size = 100;
+ $pages = ceil($count / $size);
+
+ $requests = [];
+
+ // Set up requests
+ for ($i = 0; $i < $count; $i++)
+ {
+ $offset = $i * $size;
+ $requests[] = $this->kitsuModel->getFullAnimeList($size, $offset);
+ }
+
+ $promiseArray = (new Client())->requestMulti($requests);
+
+ $responses = wait(all($promiseArray));
+ $output = [];
+
+ foreach($responses as $response)
+ {
+ $data = Json::decode($response->getBody());
+ $output = array_merge_recursive($output, $data);
+ }
+
+ return $output;
+ }
+
+ public function getMALList()
+ {
+ return $this->malModel->getFullList();
+ }
+
+ public function filterMappings(array $includes): array
+ {
+ $output = [];
+
+ foreach($includes as $id => $mapping)
+ {
+ if ($mapping['externalSite'] === 'myanimelist/anime')
+ {
+ $output[$id] = $mapping;
+ }
+ }
+
+ return $output;
+ }
+
+ // 2015-05-20T23:48:47.731Z
+
+ public function formatMALList()
+ {
+ $orig = $this->getMALList();
+ $output = [];
+
+ foreach($orig as $item)
+ {
+ $output[$item['series_animedb_id']] = [
+ 'id' => $item['series_animedb_id'],
+ 'data' => [
+ 'status' => MAL::MAL_KITSU_WATCHING_STATUS_MAP[$item['my_status']],
+ 'progress' => $item['my_watched_episodes'],
+ 'reconsuming' => (bool) $item['my_rewatching'],
+ 'reconsumeCount' => array_key_exists('times_rewatched', $item)
+ ? $item['times_rewatched']
+ : 0,
+ // 'notes' => ,
+ 'rating' => $item['my_score'],
+ 'updatedAt' => (new \DateTime())
+ ->setTimestamp((int)$item['my_last_updated'])
+ ->format(\DateTime::W3C),
+ ]
+ ];
+ }
+
+ return $output;
+ }
+
+ public function filterKitsuList()
+ {
+ $data = $this->getKitsuList();
+ $includes = JsonAPI::organizeIncludes($data['included']);
+ $includes['mappings'] = $this->filterMappings($includes['mappings']);
+
+ $output = [];
+
+ foreach($data['data'] as $listItem)
+ {
+ $animeId = $listItem['relationships']['anime']['data']['id'];
+ $potentialMappings = $includes['anime'][$animeId]['relationships']['mappings'];
+ $malId = null;
+
+ foreach ($potentialMappings as $mappingId)
+ {
+ if (array_key_exists($mappingId, $includes['mappings']))
+ {
+ $malId = $includes['mappings'][$mappingId]['externalId'];
+ }
+ }
+
+ // Skip to the next item if there isn't a MAL ID
+ if ($malId === null)
+ {
+ continue;
+ }
+
+ $output[$listItem['id']] = [
+ 'id' => $listItem['id'],
+ 'malId' => $malId,
+ 'data' => $listItem['attributes'],
+ ];
+ }
+
+ return $output;
+ }
+
+ public function getKitsuAnimeListPageCount()
+ {
+ return $this->kitsuModel->getAnimeListCount();
+ }
+
+ public function diffLists()
+ {
+ // Get libraryEntries with media.mappings from Kitsu
+ // Organize mappings, and ignore entries without mappings
+ $kitsuList = $this->filterKitsuList();
+
+ // Get MAL list data
+ $malList = $this->formatMALList();
+
+ $itemsToAddToMAL = [];
+
+ foreach($kitsuList as $kitsuItem)
+ {
+ if (array_key_exists($kitsuItem['malId'], $malList))
+ {
+ // Eventually, compare the list entries, and determine which
+ // needs to be updated
+ continue;
+ }
+
+ // Looks like this item only exists on Kitsu
+ $itemsToAddToMAL[] = [
+ 'mal_id' => $kitsuItem['malId'],
+ 'data' => $kitsuItem['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
+
+ return [
+ 'addToMAL' => $itemsToAddToMAL,
+ ];
+ }
+
+ public function createMALListItems($itemsToAdd)
+ {
+ $transformer = new ALT();
+ $requests = [];
+
+ foreach($itemsToAdd as $item)
+ {
+ $data = $transformer->untransform($item);
+ $requests[] = $this->malModel->createFullListItem($data);
+ }
+
+ $promiseArray = (new Client())->requestMulti($requests);
+
+ $responses = wait(all($promiseArray));
+
+ foreach($responses as $key => $response)
+ {
+ $id = $itemsToAdd[$key]['mal_id'];
+ if ($response->getBody() === 'Created')
+ {
+ $this->echoBox("Successfully create list item with id: {$id}");
+ }
+ else
+ {
+ $this->echoBox("Failed to create list item with id: {$id}");
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Controller.php b/src/Controller.php
index 91148358..e83d71cd 100644
--- a/src/Controller.php
+++ b/src/Controller.php
@@ -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();
diff --git a/src/Dispatcher.php b/src/Dispatcher.php
index 9b5892aa..f06a185b 100644
--- a/src/Dispatcher.php
+++ b/src/Dispatcher.php
@@ -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))
{
diff --git a/src/Model/API.php b/src/Model/API.php
index d2c423af..24879073 100644
--- a/src/Model/API.php
+++ b/src/Model/API.php
@@ -38,12 +38,6 @@ class API extends Model {
*/
protected $cache;
- /**
- * Default settings for Guzzle
- * @var array
- */
- protected $connectionDefaults = [];
-
/**
* Constructor
*
@@ -74,4 +68,4 @@ class API extends Model {
array_multisort($sort, SORT_ASC, $array);
}
-}
+}
\ No newline at end of file
diff --git a/src/Model/Anime.php b/src/Model/Anime.php
index 14bc58a1..4064bbb3 100644
--- a/src/Model/Anime.php
+++ b/src/Model/Anime.php
@@ -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;
}
/**
@@ -151,12 +168,23 @@ class Anime extends API {
*/
public function updateLibraryItem(array $data): array
{
+ $requests = [];
+
if ($this->useMALAPI)
{
- $this->malModel->updateListItem($data);
+ $requests['mal'] = $this->malModel->updateListItem($data);
}
- return $this->kitsuModel->updateListItem($data);
+ $requests['kitsu'] = $this->kitsuModel->updateListItem($data);
+
+ $promises = (new Client)->requestMulti($requests);
+
+ $results = wait(some($promises));
+
+ return [
+ 'body' => Json::decode($results[1]['kitsu']->getBody()),
+ 'statusCode' => $results[1]['kitsu']->getStatus()
+ ];
}
/**
@@ -168,12 +196,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
\ No newline at end of file
diff --git a/tests/API/APIRequestBuilderTest.php b/tests/API/APIRequestBuilderTest.php
new file mode 100644
index 00000000..f2050e86
--- /dev/null
+++ b/tests/API/APIRequestBuilderTest.php
@@ -0,0 +1,135 @@
+
+ * @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\Tests\API;
+
+use Amp;
+use Amp\Artax\Client;
+use Aviat\AnimeClient\API\APIRequestBuilder;
+use Aviat\Ion\Json;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\NullLogger;
+
+class APIRequestBuilderTest extends TestCase {
+
+ public function setUp()
+ {
+ $this->builder = new class extends APIRequestBuilder {
+ protected $baseUrl = 'https://httpbin.org/';
+
+ protected $defaultHeaders = ['User-Agent' => "Tim's Anime Client Testsuite / 4.0"];
+ };
+
+ $this->builder->setLogger(new NullLogger);
+ }
+
+ public function testGzipRequest()
+ {
+ $request = $this->builder->newRequest('GET', 'gzip')
+ ->getFullRequest();
+ $response = Amp\wait((new Client)->request($request));
+ $body = Json::decode($response->getBody());
+ $this->assertEquals(1, $body['gzipped']);
+ }
+
+ public function testInvalidRequestMethod()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->builder->newRequest('FOO', 'gzip')
+ ->getFullRequest();
+ }
+
+ public function testRequestWithBasicAuth()
+ {
+ $request = $this->builder->newRequest('GET', 'headers')
+ ->setBasicAuth('username', 'password')
+ ->getFullRequest();
+
+ $response = Amp\wait((new Client)->request($request));
+ $body = Json::decode($response->getBody());
+
+ $this->assertEquals('Basic dXNlcm5hbWU6cGFzc3dvcmQ=', $body['headers']['Authorization']);
+ }
+
+ public function testRequestWithQueryString()
+ {
+ $query = [
+ 'foo' => 'bar',
+ 'bar' => [
+ 'foo' => 'bar'
+ ],
+ 'baz' => [
+ 'bar' => 'foo'
+ ]
+ ];
+
+ $expected = [
+ 'foo' => 'bar',
+ 'bar[foo]' => 'bar',
+ 'baz[bar]' => 'foo'
+ ];
+
+ $request = $this->builder->newRequest('GET', 'get')
+ ->setQuery($query)
+ ->getFullRequest();
+
+ $response = Amp\wait((new Client)->request($request));
+ $body = Json::decode($response->getBody());
+
+ $this->assertEquals($expected, $body['args']);
+ }
+
+ public function testFormValueRequest()
+ {
+ $formValues = [
+ 'foo' => 'bar',
+ 'bar' => 'foo'
+ ];
+
+ $request = $this->builder->newRequest('POST', 'post')
+ ->setFormFields($formValues)
+ ->getFullRequest();
+
+ $response = Amp\wait((new Client)->request($request));
+ $body = Json::decode($response->getBody());
+
+ $this->assertEquals($formValues, $body['form']);
+ }
+
+ public function testFullUrlRequest()
+ {
+ $data = [
+ 'foo' => [
+ 'bar' => 1,
+ 'baz' => [2, 3, 4],
+ 'bar' => [
+ 'a' => 1,
+ 'b' => 2
+ ]
+ ]
+ ];
+
+ $request = $this->builder->newRequest('PUT', 'https://httpbin.org/put')
+ ->setHeader('Content-Type', 'application/json')
+ ->setJsonBody($data)
+ ->getFullRequest();
+
+ $response = Amp\wait((new Client)->request($request));
+ $body = Json::decode($response->getBody());
+
+ $this->assertEquals($data, $body['json']);
+ }
+}
\ No newline at end of file
diff --git a/tests/API/CacheTraitTest.php b/tests/API/CacheTraitTest.php
index 7f34b8eb..a221e4e4 100644
--- a/tests/API/CacheTraitTest.php
+++ b/tests/API/CacheTraitTest.php
@@ -1,4 +1,18 @@
+ * @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\Tests\API;
diff --git a/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php b/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php
index de87f29a..a18ace95 100644
--- a/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php
+++ b/tests/API/Kitsu/Transformer/AnimeListTransformerTest.php
@@ -1,4 +1,18 @@
+ * @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\Tests\API\Kitsu\Transformer;
diff --git a/tests/API/Kitsu/Transformer/AnimeTransformerTest.php b/tests/API/Kitsu/Transformer/AnimeTransformerTest.php
index 7d50b096..a0adae93 100644
--- a/tests/API/Kitsu/Transformer/AnimeTransformerTest.php
+++ b/tests/API/Kitsu/Transformer/AnimeTransformerTest.php
@@ -1,4 +1,18 @@
+ * @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\Tests\API\Kitsu\Transformer;
diff --git a/tests/API/Kitsu/Transformer/MangaListTransformerTest.php b/tests/API/Kitsu/Transformer/MangaListTransformerTest.php
index 658e58cf..9c229a7a 100644
--- a/tests/API/Kitsu/Transformer/MangaListTransformerTest.php
+++ b/tests/API/Kitsu/Transformer/MangaListTransformerTest.php
@@ -1,4 +1,18 @@
+ * @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\Tests\API\Kitsu\Transformer;
diff --git a/tests/API/Kitsu/Transformer/MangaTransformerTest.php b/tests/API/Kitsu/Transformer/MangaTransformerTest.php
index eb5c3f05..1f2df8cd 100644
--- a/tests/API/Kitsu/Transformer/MangaTransformerTest.php
+++ b/tests/API/Kitsu/Transformer/MangaTransformerTest.php
@@ -1,4 +1,18 @@
+ * @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\Tests\API\Kitsu\Transformer;
diff --git a/tests/API/KitsuTest.php b/tests/API/KitsuTest.php
index 2d69e8dc..6364fcc6 100644
--- a/tests/API/KitsuTest.php
+++ b/tests/API/KitsuTest.php
@@ -1,4 +1,18 @@
+ * @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\Tests\API;
diff --git a/tests/API/XMLTest.php b/tests/API/XMLTest.php
index 12a1e724..67cb861b 100644
--- a/tests/API/XMLTest.php
+++ b/tests/API/XMLTest.php
@@ -1,4 +1,18 @@
+ * @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\Tests\API;
diff --git a/tests/AnimeClient_TestCase.php b/tests/AnimeClient_TestCase.php
index 6e8ab7fa..90733e39 100644
--- a/tests/AnimeClient_TestCase.php
+++ b/tests/AnimeClient_TestCase.php
@@ -1,12 +1,10 @@
$handler]);
-
- return $client;
- }
}
// End of AnimeClient_TestCase.php
\ No newline at end of file