Do you wish to register an account?
API client for Kitsu.io, with optional Anime collection, and optional Anilist syncing.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
7.9KB

  1. <?php declare(strict_types=1);
  2. /**
  3. * Hummingbird Anime List Client
  4. *
  5. * An API client for Kitsu to manage anime and manga watch lists
  6. *
  7. * PHP version 7.1
  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.1
  14. * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
  15. */
  16. namespace Aviat\AnimeClient\Controller;
  17. use Aviat\AnimeClient\Controller as BaseController;
  18. use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer;
  19. use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Kitsu as KitsuWatchingStatus;
  20. use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
  21. use Aviat\AnimeClient\Types\FormItem;
  22. use Aviat\Ion\Di\ContainerInterface;
  23. use Aviat\Ion\Json;
  24. use Aviat\Ion\StringWrapper;
  25. /**
  26. * Controller for Anime-related pages
  27. */
  28. final class Anime extends BaseController {
  29. use StringWrapper;
  30. /**
  31. * The anime list model
  32. * @var \Aviat\AnimeClient\Model\Anime $model
  33. */
  34. protected $model;
  35. /**
  36. * Constructor
  37. *
  38. * @param ContainerInterface $container
  39. * @throws \Aviat\Ion\Di\ContainerException
  40. * @throws \Aviat\Ion\Di\NotFoundException
  41. */
  42. public function __construct(ContainerInterface $container)
  43. {
  44. parent::__construct($container);
  45. $this->model = $container->get('anime-model');
  46. $this->baseData = array_merge($this->baseData, [
  47. 'menu_name' => 'anime_list',
  48. 'url_type' => 'anime',
  49. 'other_type' => 'manga',
  50. 'config' => $this->config,
  51. ]);
  52. $this->cache = $container->get('cache');
  53. }
  54. /**
  55. * Show a portion, or all of the anime list
  56. *
  57. * @param string|int $type - The section of the list
  58. * @param string $view - List or cover view
  59. * @throws \Aviat\Ion\Di\ContainerException
  60. * @throws \Aviat\Ion\Di\NotFoundException
  61. * @throws \InvalidArgumentException
  62. * @return void
  63. */
  64. public function index($type = KitsuWatchingStatus::WATCHING, string $view = NULL): void
  65. {
  66. $title = array_key_exists($type, AnimeWatchingStatus::ROUTE_TO_TITLE)
  67. ? $this->formatTitle(
  68. $this->config->get('whose_list') . "'s Anime List",
  69. AnimeWatchingStatus::ROUTE_TO_TITLE[$type]
  70. )
  71. : '';
  72. $viewMap = [
  73. '' => 'cover',
  74. 'list' => 'list'
  75. ];
  76. $data = ($type !== 'all')
  77. ? $this->model->getList(AnimeWatchingStatus::ROUTE_TO_KITSU[$type])
  78. : $this->model->getAllLists();
  79. $this->outputHTML('anime/' . $viewMap[$view], [
  80. 'title' => $title,
  81. 'sections' => $data
  82. ]);
  83. }
  84. /**
  85. * Form to add an anime
  86. *
  87. * @throws \Aviat\Ion\Di\ContainerException
  88. * @throws \Aviat\Ion\Di\NotFoundException
  89. * @throws \Aura\Router\Exception\RouteNotFound
  90. * @throws \InvalidArgumentException
  91. * @return void
  92. */
  93. public function addForm(): void
  94. {
  95. $this->setSessionRedirect();
  96. $this->outputHTML('anime/add', [
  97. 'title' => $this->formatTitle(
  98. $this->config->get('whose_list') . "'s Anime List",
  99. 'Add'
  100. ),
  101. 'action_url' => $this->url->generate('anime.add.post'),
  102. 'status_list' => AnimeWatchingStatus::KITSU_TO_TITLE
  103. ]);
  104. }
  105. /**
  106. * Add an anime to the list
  107. *
  108. * @throws \Aviat\Ion\Di\ContainerException
  109. * @throws \Aviat\Ion\Di\NotFoundException
  110. * @return void
  111. */
  112. public function add(): void
  113. {
  114. $data = $this->request->getParsedBody();
  115. if (empty($data['mal_id']))
  116. {
  117. unset($data['mal_id']);
  118. }
  119. if ( ! array_key_exists('id', $data))
  120. {
  121. $this->redirect('anime/add', 303);
  122. }
  123. $result = $this->model->createLibraryItem($data);
  124. if ($result)
  125. {
  126. $this->setFlashMessage('Added new anime to list', 'success');
  127. $this->cache->clear();
  128. }
  129. else
  130. {
  131. $this->setFlashMessage('Failed to add new anime to list', 'error');
  132. }
  133. $this->sessionRedirect();
  134. }
  135. /**
  136. * Form to edit details about a series
  137. *
  138. * @param string $id
  139. * @param string $status
  140. */
  141. public function edit(string $id, $status = 'all'): void
  142. {
  143. $item = $this->model->getLibraryItem($id);
  144. $this->setSessionRedirect();
  145. $this->outputHTML('anime/edit', [
  146. 'title' => $this->formatTitle(
  147. $this->config->get('whose_list') . "'s Anime List",
  148. 'Edit'
  149. ),
  150. 'item' => $item,
  151. 'statuses' => AnimeWatchingStatus::KITSU_TO_TITLE,
  152. 'action' => $this->url->generate('update.post', [
  153. 'controller' => 'anime'
  154. ]),
  155. ]);
  156. }
  157. /**
  158. * Search for anime
  159. *
  160. * @return void
  161. */
  162. public function search(): void
  163. {
  164. $queryParams = $this->request->getQueryParams();
  165. $query = $queryParams['query'];
  166. $this->outputJSON($this->model->search($query));
  167. }
  168. /**
  169. * Update an anime item via a form submission
  170. *
  171. * @throws \Aviat\Ion\Di\ContainerException
  172. * @throws \Aviat\Ion\Di\NotFoundException
  173. * @return void
  174. */
  175. public function formUpdate(): void
  176. {
  177. $data = $this->request->getParsedBody();
  178. // Do some minor data manipulation for
  179. // large form-based updates
  180. $transformer = new AnimeListTransformer();
  181. $postData = $transformer->untransform($data);
  182. $fullResult = $this->model->updateLibraryItem(new FormItem($postData));
  183. if ($fullResult['statusCode'] === 200)
  184. {
  185. $this->setFlashMessage('Successfully updated.', 'success');
  186. $this->cache->clear();
  187. }
  188. else
  189. {
  190. $this->setFlashMessage('Failed to update anime.', 'error');
  191. }
  192. $this->sessionRedirect();
  193. }
  194. /**
  195. * Increase the watched count for an anime item
  196. *
  197. * @return void
  198. */
  199. public function increment(): void
  200. {
  201. if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
  202. {
  203. $data = Json::decode((string)$this->request->getBody());
  204. }
  205. else
  206. {
  207. $data = $this->request->getParsedBody();
  208. }
  209. $response = $this->model->incrementLibraryItem(new FormItem($data));
  210. $this->cache->clear();
  211. $this->outputJSON($response['body'], $response['statusCode']);
  212. }
  213. /**
  214. * Remove an anime from the list
  215. *
  216. * @return void
  217. */
  218. public function delete(): void
  219. {
  220. $body = $this->request->getParsedBody();
  221. $response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']);
  222. if ($response === TRUE)
  223. {
  224. $this->setFlashMessage('Successfully deleted anime.', 'success');
  225. $this->cache->clear();
  226. }
  227. else
  228. {
  229. $this->setFlashMessage('Failed to delete anime.', 'error');
  230. }
  231. $this->sessionRedirect();
  232. }
  233. /**
  234. * View details of an anime
  235. *
  236. * @param string $animeId
  237. * @throws \Aviat\Ion\Di\ContainerException
  238. * @throws \Aviat\Ion\Di\NotFoundException
  239. * @throws \InvalidArgumentException
  240. * @return void
  241. */
  242. public function details(string $animeId): void
  243. {
  244. $data = $this->model->getAnime($animeId);
  245. $characters = [];
  246. $staff = [];
  247. if ($data->title === '')
  248. {
  249. $this->notFound(
  250. $this->config->get('whose_list') .
  251. "'s Anime List &middot; Anime &middot; " .
  252. 'Anime not found',
  253. 'Anime Not Found'
  254. );
  255. return;
  256. }
  257. if (array_key_exists('characters', $data['included']))
  258. {
  259. foreach($data['included']['characters'] as $id => $character)
  260. {
  261. $characters[$id] = $character['attributes'];
  262. }
  263. }
  264. if (array_key_exists('mediaStaff', $data['included']))
  265. {
  266. foreach ($data['included']['mediaStaff'] as $id => $person)
  267. {
  268. $personDetails = [];
  269. foreach ($person['relationships']['person']['people'] as $p)
  270. {
  271. $personDetails = $p['attributes'];
  272. }
  273. $role = $person['attributes']['role'];
  274. if ( ! array_key_exists($role, $staff))
  275. {
  276. $staff[$role] = [];
  277. }
  278. $staff[$role][$id] = [
  279. 'name' => $personDetails['name'] ?? '??',
  280. 'image' => $personDetails['image'],
  281. ];
  282. }
  283. }
  284. uasort($characters, function ($a, $b) {
  285. return $a['name'] <=> $b['name'];
  286. });
  287. // dump($characters);
  288. // dump($staff);
  289. $this->outputHTML('anime/details', [
  290. 'title' => $this->formatTitle(
  291. $this->config->get('whose_list') . "'s Anime List",
  292. 'Anime',
  293. $data->title
  294. ),
  295. 'characters' => $characters,
  296. 'show_data' => $data,
  297. 'staff' => $staff,
  298. ]);
  299. }
  300. /**
  301. * Find anime matching the selected genre
  302. *
  303. * @param string $genre
  304. */
  305. public function genre(string $genre): void
  306. {
  307. // @TODO: implement
  308. }
  309. }
  310. // End of AnimeController.php