/** * Get the details of an anime or manga. * * @param int $id The ID of the anime or manga as assigned by MyAnimeList * @param string $apiVersion The API version of the request * @param Request $request HTTP Request object * @param string $requestType The anime or manga request string * * @return View */ public function getAction($id, $apiVersion, $requestType, Request $request) { //General information (and basic personal information) at: // http://myanimelist.net/anime/#{id} // http://myanimelist.net/manga/#{id} //Detailed personal information at: // http://myanimelist.net/ownlist/anime/{id}/edit?hideLayout // http://myanimelist.net/ownlist/manga/{id}/edit?hideLayout $usepersonal = (int) $request->query->get('mine'); $downloader = $this->get('atarashii_api.communicator'); if ($usepersonal) { //get the credentials we received $username = $this->getRequest()->server->get('PHP_AUTH_USER'); $password = $this->getRequest()->server->get('PHP_AUTH_PW'); try { if (!$downloader->cookieLogin($username, $password)) { $view = $this->view(array('error' => 'unauthorized'), 401); $view->setHeader('WWW-Authenticate', 'Basic realm="myanimelist.net"'); return $view; } } catch (Exception\CurlException $e) { return $this->view(array('error' => 'network-error'), 500); } } try { $recordDetails = $downloader->fetch('/' . $requestType . '/' . $id); } catch (Exception\CurlException $e) { return $this->view(array('error' => 'network-error'), 500); } catch (Exception\ClientErrorResponseException $e) { $recordDetails = $e->getResponse(); } if (strpos($recordDetails, 'No series found') !== false || strpos($recordDetails, 'This page doesn\'t exist') !== false) { return $this->view(array('error' => 'not-found'), 404); } else { if ($requestType === 'anime') { $record = AnimeParser::parse($recordDetails, $apiVersion); } else { $record = MangaParser::parse($recordDetails, $apiVersion); } //Parse extended personal details if API 2.0 or better and personal details are requested if ($apiVersion >= '2.0' && $usepersonal) { try { $recordDetails = $downloader->fetch('/ownlist/' . $requestType . '/' . $id . '/edit?hideLayout'); } catch (Exception\CurlException $e) { return $this->view(array('error' => 'network-error'), 500); } if (strpos($recordDetails, 'delete-form') !== false) { if ($requestType === 'anime') { $record = AnimeParser::parseExtendedPersonal($recordDetails, $record); } else { $record = MangaParser::parseExtendedPersonal($recordDetails, $record); } } } $response = new Response(); $serializationContext = SerializationContext::create(); $serializationContext->setVersion($apiVersion); //For compatibility, API 1.0 explicitly passes null parameters. if ($apiVersion == '1.0') { $serializationContext->setSerializeNull(true); } //Only include cache info if it doesn't include personal data. if (!$usepersonal) { $response->setPublic(); $response->setMaxAge(3600); //One hour $response->headers->addCacheControlDirective('must-revalidate', true); $response->setEtag($requestType . '/' . $id); //Also, set "expires" header for caches that don't understand Cache-Control $date = new \DateTime(); $date->modify('+3600 seconds'); //One hour $response->setExpires($date); } $view = $this->view($record); $view->setSerializationContext($serializationContext); $view->setResponse($response); $view->setStatusCode(200); return $view; } }
/** * @covers ::parse */ public function testParse() { $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-1887-mine.html'); $apiVersion = '2.1'; $anime = AnimeParser::parse($animeContents, $apiVersion); $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime); /* As this test is against a static downloaded copy, we know what the exact values should be. As such, rather * than testing based on type for many of the items, we're checking for exact values. Obviously, this test * will need to be updated when the source file is re-downloaded to update any values. */ $this->assertEquals('1887', $anime->getId()); $this->assertEquals('Lucky☆Star', $anime->getTitle()); $this->assertContains('youtube.com/embed/b8bSIgE8epA', $anime->getPreview()); $oTitles = $anime->getOtherTitles(); $this->assertArrayHasKey('english', $oTitles); $this->assertContains('Lucky☆Star', $oTitles['english']); $this->assertArrayHasKey('synonyms', $oTitles); $this->assertContains('Lucky Star', $oTitles['synonyms']); $this->assertArrayHasKey('japanese', $oTitles); $this->assertContains('らき☆すた', $oTitles['japanese']); $this->assertInternalType('integer', $anime->getRank()); $this->assertLessThan(800, $anime->getRank()); $this->assertGreaterThan(0, $anime->getRank()); $this->assertInternalType('integer', $anime->getPopularityRank()); $this->assertLessThan(100, $anime->getPopularityRank()); $this->assertGreaterThan(0, $anime->getPopularityRank()); $this->assertEquals('https://myanimelist.cdn-dena.com/images/anime/13/15010.jpg', $anime->getImageUrl()); $this->assertEquals('TV', $anime->getType()); $this->assertEquals(24, $anime->getEpisodes()); $this->assertEquals('finished airing', $anime->getStatus()); $this->assertEquals('2007-04-08', $anime->getStartDate()); $this->assertEquals('2007-09-17', $anime->getEndDate()); $this->assertEquals(24, $anime->getDuration()); $this->assertEquals(null, $anime->getBroadcast()); $this->assertEquals(24, $anime->getDuration()); $this->assertEquals('PG-13 - Teens 13 or older', $anime->getClassification()); $this->assertInternalType('float', $anime->getMembersScore()); $this->assertGreaterThan(6.0, $anime->getMembersScore()); $this->assertGreaterThan(230000, $anime->getMembersCount()); $this->assertGreaterThan(7200, $anime->getFavoritedCount()); $externalLinks = $anime->getExternalLinks(); $this->assertEquals('http://www.lucky-ch.com/', $externalLinks['Official Site']); $this->assertEquals('http://anidb.info/perl-bin/animedb.pl?show=anime&aid=4777', $externalLinks['AnimeDB']); $this->assertEquals('http://www.animenewsnetwork.com/encyclopedia/anime.php?id=7222', $externalLinks['AnimeNewsNetwork']); $this->assertEquals('http://en.wikipedia.org/wiki/Lucky_Star_%28manga%29', $externalLinks['Wikipedia']); $this->assertContains('the daily lives of four cute high school girls', $anime->getSynopsis()); $this->assertStringStartsWith('<i>Lucky Star</i> also has audio CDs', $anime->getBackground()); $this->assertContains('Lucky Paradise', $anime->getProducers()); $this->assertContains('Lantis', $anime->getProducers()); $this->assertContains('Comedy', $anime->getGenres()); $this->assertContains('Parody', $anime->getGenres()); $mangaAdaptations = $anime->getMangaAdaptations(); $this->assertInternalType('array', $mangaAdaptations[0]); $this->assertEquals('587', $mangaAdaptations[0]['manga_id']); $this->assertEquals('Lucky☆Star', $mangaAdaptations[0]['title']); $this->assertContains('manga/587', $mangaAdaptations[0]['url']); $this->assertEmpty($anime->getPrequels()); $sequels = $anime->getSequels(); $this->assertInternalType('array', $sequels[0]); $this->assertEquals('4472', $sequels[0]['anime_id']); $this->assertStringStartsWith('Lucky☆Star', $sequels[0]['title']); $this->assertContains('anime/4472', $sequels[0]['url']); $this->assertEmpty($anime->getSideStories()); $charAnime = $anime->getCharacterAnime(); $this->assertInternalType('array', $charAnime[0]); $this->assertEquals('3080', $charAnime[0]['anime_id']); $this->assertEquals('Anime Tenchou', $charAnime[0]['title']); $this->assertContains('anime/3080', $charAnime[0]['url']); $spinOffs = $anime->getSpinOffs(); $this->assertInternalType('array', $spinOffs[0]); $this->assertEquals('17637', $spinOffs[0]['anime_id']); $this->assertStringStartsWith('Miyakawa-ke', $spinOffs[0]['title']); $this->assertContains('anime/17637', $spinOffs[0]['url']); $this->assertEmpty($anime->getSummaries()); $this->assertEmpty($anime->getAlternativeVersions()); $this->assertEmpty($anime->getOther()); $openingTheme = $anime->getOpeningTheme(); $this->assertEquals('"Motteke! Sailor Fuku" by Aya Hirano, Emiri Katou, Kaori Fukuhara & Aya Endo', $openingTheme[0]); $endingTheme = $anime->getEndingTheme(); $this->assertEquals('#01: "Uchuu Tetsujin Kyoodain (宇宙鉄人キョーダイン)" by Aya Hirano (ep 1)', $endingTheme[0]); $this->assertEquals('#23: "Mikuru Henshin! Soshite Sentou! (ミクル変身!そして戦闘!)" by Minoru Shiraishi (ep 23)', $endingTheme[23]); $recommedations = $anime->getRecommendations(); $recommedations = $recommedations[0]; $this->assertEquals(66, $recommedations->getId()); $this->assertEquals('Azumanga Daioh', $recommedations->getTitle()); $this->assertEquals('https://myanimelist.cdn-dena.com/images/anime/1/66.jpg', $recommedations->getImageUrl()); $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-10236.html'); $anime = AnimeParser::parse($animeContents, $apiVersion); $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime); $this->assertEquals('1981', $anime->getEndDate()); $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-47.html'); $anime = AnimeParser::parse($animeContents, $apiVersion); $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime); $this->assertEquals('124', $anime->getDuration()); $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-10758.html'); $anime = AnimeParser::parse($animeContents, $apiVersion); $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime); $this->assertNull($anime->getDuration()); $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-18617.html'); $anime = AnimeParser::parse($animeContents, $apiVersion); $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime); $this->assertEquals('120', $anime->getDuration()); }
/** * @param $apiVersion The API version of the request * @param $requestType The anime or manga request string * @param Request $request HTTP Request object * * @return \FOS\RestBundle\View\View */ public function getBrowseAction($apiVersion, $requestType, Request $request) { $downloader = $this->get('atarashii_api.communicator'); $page = (int) $request->query->get('page'); if ($page <= 0) { $page = 1; } // Create URL parts supported by MAL $pagePart = '&show=' . ($page * 50 - 50); $keyword = '&q=' . $request->query->get('keyword'); $score = '&score=' . (int) $request->query->get('score'); $reverse = '&w=' . (int) $request->query->get('reverse'); $rating = '&r=' . (int) $request->query->get('rating'); $genreType = '&gx=' . (int) $request->query->get('genre_type'); $status = '&status=' . $this->getStatusId($request->query->get('status')); $endDateArray = explode('-', $request->query->get('end_date')); if (count($endDateArray) == 3) { $endDate = '&ey=' . $endDateArray[0] . '&em=' . $endDateArray[1] . '&ed=' . $endDateArray[2]; } else { $endDate = ''; } $startDateArray = explode('-', $request->query->get('start_date')); if (count($startDateArray) == 3) { $startDate = '&sy=' . $startDateArray[0] . '&sm=' . $startDateArray[1] . '&sd=' . $startDateArray[2]; } else { $startDate = ''; } if ($requestType === 'anime') { $type = '&type=' . Anime::getTypeId($request->query->get('type')); $genres = Anime::getGenresId($request->query->get('genres')); $sort = '&o=' . Anime::getColumnId($request->query->get('sort'), $requestType); } else { $type = '&type=' . Manga::getTypeId($request->query->get('type')); $genres = Manga::getGenresId($request->query->get('genres')); $sort = '&o=' . Manga::getColumnId($request->query->get('sort'), $requestType); } // Combine all URL parts for the request $url = $genres . $sort . $reverse . $endDate . $startDate . $rating . $status . $type . $keyword . $score . $genreType . $pagePart; try { $content = $downloader->fetch('/' . $requestType . '.php?c[]=a&c[]=b&c[]=c&c[]=d&c[]=e&c[]=f&c[]=g&c[]=g' . $url); } catch (Exception\CurlException $e) { return $this->view(array('error' => 'network-error'), 500); } catch (Exception\ClientErrorResponseException $e) { $content = $e->getResponse(); } $response = new Response(); $serializationContext = SerializationContext::create(); $serializationContext->setVersion($apiVersion); $response->setPublic(); $response->setMaxAge(86400); //One day $response->headers->addCacheControlDirective('must-revalidate', true); $response->setEtag($type . '/' . $requestType . '?' . $url); //Also, set "expires" header for caches that don't understand Cache-Control $date = new \DateTime(); $date->modify('+86400 seconds'); //One day $response->setExpires($date); // MAL does contain a bug where excluded genres allow the same amount of pages as normal without warning // To avoid issues we check if the page number does match the content page number. preg_match('/>\\[(\\d+?)\\]/', $content, $matches); if (strpos($content, 'No titles that matched') !== false || strpos($content, 'This page doesn\'t exist') !== false) { return $this->view(array('error' => 'not-found'), 404); } else { if (count($matches) > 1 && (int) $matches[1] !== $page) { return $this->view(array(), 200); } else { //MAL now returns 404 on a single result. Workaround if (method_exists($content, 'getStatusCode') && $content->getStatusCode() === 404) { $location = $content->getHeader('Location'); try { $content = $downloader->fetch($location); if ($type === 'anime') { $searchResult = array(AnimeParser::parse($content)); } else { $searchResult = array(MangaParser::parse($content)); } } catch (Exception\CurlException $e) { return $this->view(array('error' => 'network-error'), 500); } } else { if ($downloader->wasRedirected()) { if ($type === 'anime') { $searchResult = array(AnimeParser::parse($content)); } else { $searchResult = array(MangaParser::parse($content)); } } else { $searchResult = Upcoming::parse($content, $requestType); } } $view = $this->view($searchResult); $view->setSerializationContext($serializationContext); $view->setResponse($response); $view->setStatusCode(200); return $view; } } }
/** * Request search data using the unOfficial. * * With the unOfficial way the API will get the data from the search page instead of the official API. * * @param $type The type is a string which can be 'anime' or 'manga' * @param $page Integer which is used to get the desired page * @param $query The title or keywords to seach for the record * @param $apiVersion The API version which was used for the request * * @return \FOS\RestBundle\View\View */ private function unOfficialSearch($type, $page, $query, $apiVersion) { // http://myanimelist.net/anime.php?c[]=a&c[]=b&c[]=c&c[]=d&c[]=e&c[]=f&c[]=g&q=#{name}&show=#{page} // http://myanimelist.net/manga.php?c[]=a&c[]=b&c[]=c&c[]=d&c[]=e&c[]=f&c[]=g&q=#{name}&show=#{page} if ($page <= 0) { $page = 1; } $downloader = $this->get('atarashii_api.communicator'); try { $content = $downloader->fetch('/' . $type . '.php?c[]=a&c[]=b&c[]=c&c[]=d&c[]=e&c[]=f&c[]=g&q=' . $query . '&show=' . ($page * 50 - 50)); } catch (Exception\CurlException $e) { return $this->view(array('error' => 'network-error'), 500); } catch (Exception\ClientErrorResponseException $e) { //MAL now returns 404 on searches without results. //We still need the content for logic purposes. $content = $e->getResponse(); } $response = new Response(); $serializationContext = SerializationContext::create(); $serializationContext->setVersion($apiVersion); $response->setPublic(); $response->setMaxAge(3600); //One hour $response->headers->addCacheControlDirective('must-revalidate', true); $response->setEtag($type . '/search?q=' . urlencode($query) . 'page=' . $page); //Also, set "expires" header for caches that don't understand Cache-Control $date = new \DateTime(); $date->modify('+3600 seconds'); //One hour $response->setExpires($date); if (strpos($content, 'No titles that matched') !== false || strpos($content, 'This page doesn\'t exist') !== false) { $view = $this->view(array('error' => 'not-found')); $view->setResponse($response); $view->setStatusCode(404); return $view; } else { //For compatibility, API 1.0 explicitly passes null parameters. if ($apiVersion == '1.0') { $serializationContext->setSerializeNull(true); } //MAL now returns 404 on a single result. Workaround if (method_exists($content, 'getStatusCode') && $content->getStatusCode() === 404) { $location = $content->getHeader('Location'); try { $content = $downloader->fetch($location); if ($type === 'anime') { $searchResult = array(AnimeParser::parse($content)); } else { $searchResult = array(MangaParser::parse($content)); } } catch (Exception\CurlException $e) { return $this->view(array('error' => 'network-error'), 500); } } else { if ($downloader->wasRedirected()) { if ($type === 'anime') { $searchResult = array(AnimeParser::parse($content)); } else { $searchResult = array(MangaParser::parse($content)); } } else { $searchResult = Upcoming::parse($content, $type); } } $view = $this->view($searchResult); $view->setSerializationContext($serializationContext); $view->setResponse($response); $view->setStatusCode(200); return $view; } }