API client for Kitsu.io, with optional Anime collection, and optional Anilist syncing.
anime
manga
kitsu
anilist
single-user

Index.php 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php declare(strict_types=1);
  2. /**
  3. * Hummingbird Anime List Client
  4. *
  5. * An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
  6. *
  7. * PHP version 7
  8. *
  9. * @package HummingbirdAnimeClient
  10. * @author Timothy J. Warren <tim@timshomepage.net>
  11. * @copyright 2015 - 2018 Timothy J. Warren
  12. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  13. * @version 4.0
  14. * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
  15. */
  16. namespace Aviat\AnimeClient\Controller;
  17. use function Amp\Promise\wait;
  18. use Aviat\AnimeClient\Controller as BaseController;
  19. use Aviat\AnimeClient\API\{HummingbirdClient, JsonAPI};
  20. use Aviat\Ion\View\HtmlView;
  21. /**
  22. * Controller for handling routes that don't fit elsewhere
  23. */
  24. final class Index extends BaseController {
  25. /**
  26. * Purges the API cache
  27. *
  28. * @throws \Aviat\Ion\Di\ContainerException
  29. * @throws \Aviat\Ion\Di\NotFoundException
  30. * @throws \InvalidArgumentException
  31. * @return void
  32. */
  33. public function clearCache()
  34. {
  35. $this->cache->clear();
  36. $this->outputHTML('blank', [
  37. 'title' => 'Cache cleared'
  38. ]);
  39. }
  40. /**
  41. * Show the login form
  42. *
  43. * @param string $status
  44. * @throws \Aviat\Ion\Di\ContainerException
  45. * @throws \Aviat\Ion\Di\NotFoundException
  46. * @throws \InvalidArgumentException
  47. * @return void
  48. */
  49. public function login(string $status = '')
  50. {
  51. $message = '';
  52. $view = new HtmlView($this->container);
  53. if ($status !== '')
  54. {
  55. $message = $this->showMessage($view, 'error', $status);
  56. }
  57. // Set the redirect url
  58. $this->setSessionRedirect();
  59. $this->outputHTML('login', [
  60. 'title' => 'Api login',
  61. 'message' => $message
  62. ], $view);
  63. }
  64. /**
  65. * Attempt login authentication
  66. *
  67. * @throws \Aviat\Ion\Di\ContainerException
  68. * @throws \Aviat\Ion\Di\NotFoundException
  69. * @throws \Aura\Router\Exception\RouteNotFound
  70. * @throws \InvalidArgumentException
  71. * @return void
  72. */
  73. public function loginAction()
  74. {
  75. $auth = $this->container->get('auth');
  76. $post = $this->request->getParsedBody();
  77. if ($auth->authenticate($post['password']))
  78. {
  79. $this->sessionRedirect();
  80. return;
  81. }
  82. $this->setFlashMessage('Invalid username or password.');
  83. $this->redirect($this->url->generate('login'), 303);
  84. }
  85. /**
  86. * Deauthorize the current user
  87. *
  88. * @throws \Aviat\Ion\Di\ContainerException
  89. * @throws \Aviat\Ion\Di\NotFoundException
  90. * @throws \InvalidArgumentException
  91. * @return void
  92. */
  93. public function logout()
  94. {
  95. $auth = $this->container->get('auth');
  96. $auth->logout();
  97. $this->redirectToDefaultRoute();
  98. }
  99. /**
  100. * Show the user profile page
  101. *
  102. * @throws \Aviat\Ion\Di\ContainerException
  103. * @throws \Aviat\Ion\Di\NotFoundException
  104. * @throws \InvalidArgumentException
  105. * @return void
  106. */
  107. public function me()
  108. {
  109. $username = $this->config->get(['kitsu_username']);
  110. $model = $this->container->get('kitsu-model');
  111. $data = $model->getUserData($username);
  112. $orgData = JsonAPI::organizeData($data)[0];
  113. $rels = $orgData['relationships'] ?? [];
  114. $favorites = array_key_exists('favorites', $rels) ? $rels['favorites'] : [];
  115. $this->outputHTML('me', [
  116. 'title' => 'About ' . $this->config->get('whose_list'),
  117. 'data' => $orgData,
  118. 'attributes' => $orgData['attributes'],
  119. 'relationships' => $rels,
  120. 'favorites' => $this->organizeFavorites($favorites),
  121. ]);
  122. }
  123. /**
  124. * Get image covers from kitsu
  125. *
  126. * @param string $type The category of image
  127. * @param string $file The filename to look for
  128. * @throws \Aviat\Ion\Di\ContainerException
  129. * @throws \Aviat\Ion\Di\NotFoundException
  130. * @throws \InvalidArgumentException
  131. * @throws \TypeError
  132. * @throws \Error
  133. * @throws \Throwable
  134. * @return void
  135. */
  136. public function images(string $type, string $file): void
  137. {
  138. $kitsuUrl = 'https://media.kitsu.io/';
  139. list($id, $ext) = explode('.', basename($file));
  140. switch ($type)
  141. {
  142. case 'anime':
  143. $kitsuUrl .= "anime/poster_images/{$id}/small.{$ext}";
  144. break;
  145. case 'avatars':
  146. $kitsuUrl .= "users/avatars/{$id}/original.{$ext}";
  147. break;
  148. case 'manga':
  149. $kitsuUrl .= "manga/poster_images/{$id}/small.{$ext}";
  150. break;
  151. case 'characters':
  152. $kitsuUrl .= "characters/images/{$id}/original.{$ext}";
  153. break;
  154. default:
  155. $this->notFound();
  156. return;
  157. }
  158. $promise = (new HummingbirdClient)->request($kitsuUrl);
  159. $response = wait($promise);
  160. $data = wait($response->getBody());
  161. $baseSavePath = $this->config->get('img_cache_path');
  162. file_put_contents("{$baseSavePath}/{$type}/{$id}.{$ext}", $data);
  163. header('Content-type: ' . $response->getHeader('content-type')[0]);
  164. echo $data;
  165. }
  166. /**
  167. * Reorganize favorites data to be more useful
  168. *
  169. * @param array $rawfavorites
  170. * @return array
  171. */
  172. private function organizeFavorites(array $rawfavorites): array
  173. {
  174. $output = [];
  175. unset($rawfavorites['data']);
  176. foreach($rawfavorites as $item)
  177. {
  178. $rank = $item['attributes']['favRank'];
  179. foreach($item['relationships']['item'] as $key => $fav)
  180. {
  181. $output[$key] = $output[$key] ?? [];
  182. foreach ($fav as $id => $data)
  183. {
  184. $output[$key][$rank] = array_merge(['id' => $id], $data['attributes']);
  185. }
  186. }
  187. ksort($output[$key]);
  188. }
  189. return $output;
  190. }
  191. }