diff --git a/src/Model/News/NewsList.php b/src/Model/News/NewsList.php index 48a0cb50..bf93b024 100644 --- a/src/Model/News/NewsList.php +++ b/src/Model/News/NewsList.php @@ -8,8 +8,6 @@ /** * Class NewsList - * - * @package Jikan\Model\News\NewsList */ class NewsList extends Results implements Pagination { @@ -23,29 +21,38 @@ class NewsList extends Results implements Pagination */ private $lastVisiblePage = 1; + /** * @param Parser\News\NewsListParser $parser - * - * @return NewsList - * @throws \Exception - * @throws \RuntimeException - * @throws \InvalidArgumentException + * @return static */ public static function fromParser(Parser\News\NewsListParser $parser): self { $instance = new self(); $instance->results = $parser->getResults(); + $instance->lastVisiblePage = $parser->getLastVisiblePage(); $instance->hasNextPage = $parser->getHasNextPage(); return $instance; } + /** + * @return static + */ public static function mock() : self { return new self(); } + /** + * @return array + */ + public function getResults(): array + { + return $this->results; + } + /** * @return bool */ @@ -61,12 +68,4 @@ public function getLastVisiblePage(): int { return $this->lastVisiblePage; } - - /** - * @return array - */ - public function getResults(): array - { - return $this->results; - } } diff --git a/src/Model/News/NewsListItem.php b/src/Model/News/NewsListItem.php index bb41f537..eb1277a2 100644 --- a/src/Model/News/NewsListItem.php +++ b/src/Model/News/NewsListItem.php @@ -2,8 +2,10 @@ namespace Jikan\Model\News; +use Jikan\Model\Resource\NewsImageResource\NewsImageResource; use Jikan\Model\Resource\WrapImageResource\WrapImageResource; use Jikan\Parser\News\NewsListItemParser; +use Jikan\Parser\News\ResourceNewsListItemParser; /** * Class AnimeParser @@ -15,52 +17,57 @@ class NewsListItem /** * @var int|null */ - private $malId; + private int|null $malId; /** * @var string */ - private $url; + private string $url; /** * @var string */ - private $title; + private string $title; /** * @var \DateTimeImmutable */ - private $date; + private \DateTimeImmutable $date; /** * @var string */ - private $authorUsername; + private string $authorUsername; /** * @var string */ - private $authorUrl; + private string $authorUrl; /** * @var string */ - private $forumUrl; + private string $forumUrl; /** - * @var WrapImageResource + * @var NewsImageResource */ - private $images; + private NewsImageResource $images; /** * @var int */ - private $comments; + private int $comments; /** * @var string */ - private $excerpt; + private string $excerpt; + + /** + * @var array + */ + private array $tags; /** * @param NewsListItemParser $parser @@ -68,7 +75,7 @@ class NewsListItem * @return NewsListItem * @throws \InvalidArgumentException */ - public static function fromParser(NewsListItemParser $parser): self + public static function fromParser(NewsListItemParser $parser): NewsListItem { $instance = new self(); $instance->malId = $parser->getMalId(); @@ -78,90 +85,11 @@ public static function fromParser(NewsListItemParser $parser): self $instance->authorUsername = $parser->getAuthor()->getName(); $instance->authorUrl = $parser->getAuthor()->getUrl(); $instance->forumUrl = $parser->getDiscussionLink(); - $instance->images = WrapImageResource::factory($parser->getImage()); + $instance->images = NewsImageResource::factory($parser->getImageUrl()); $instance->comments = $parser->getComments(); - $instance->excerpt = $parser->getIntro(); + $instance->excerpt = $parser->getExcerpt(); return $instance; } - /** - * @return int|null - */ - public function getMalId(): ?int - { - return $this->malId; - } - - /** - * @return string - */ - public function getUrl(): string - { - return $this->url; - } - - /** - * @return string - */ - public function getTitle(): string - { - return $this->title; - } - - /** - * @return \DateTimeImmutable - */ - public function getDate(): \DateTimeImmutable - { - return $this->date; - } - - /** - * @return string - */ - public function getAuthorUsername(): string - { - return $this->authorUsername; - } - - /** - * @return string - */ - public function getAuthorUrl(): string - { - return $this->authorUrl; - } - - /** - * @return string - */ - public function getForumUrl(): string - { - return $this->forumUrl; - } - - /** - * @return WrapImageResource - */ - public function getImages(): WrapImageResource - { - return $this->images; - } - - /** - * @return int - */ - public function getComments(): int - { - return $this->comments; - } - - /** - * @return string - */ - public function getExcerpt(): string - { - return $this->excerpt; - } } diff --git a/src/Model/News/ResourceNewsList.php b/src/Model/News/ResourceNewsList.php new file mode 100644 index 00000000..32297135 --- /dev/null +++ b/src/Model/News/ResourceNewsList.php @@ -0,0 +1,72 @@ +results = $parser->getResults(); + $instance->hasNextPage = $parser->getHasNextPage(); + + return $instance; + } + + public static function mock() : self + { + return new self(); + } + + /** + * @return bool + */ + public function hasNextPage(): bool + { + return $this->hasNextPage; + } + + /** + * @return int + */ + public function getLastVisiblePage(): int + { + return $this->lastVisiblePage; + } + + /** + * @return array + */ + public function getResults(): array + { + return $this->results; + } +} diff --git a/src/Model/News/ResourceNewsListItem.php b/src/Model/News/ResourceNewsListItem.php new file mode 100644 index 00000000..16444b79 --- /dev/null +++ b/src/Model/News/ResourceNewsListItem.php @@ -0,0 +1,167 @@ +malId = $parser->getMalId(); + $instance->url = $parser->getUrl(); + $instance->title = $parser->getTitle(); + $instance->date = $parser->getDate(); + $instance->authorUsername = $parser->getAuthor()->getName(); + $instance->authorUrl = $parser->getAuthor()->getUrl(); + $instance->forumUrl = $parser->getDiscussionLink(); + $instance->images = WrapImageResource::factory($parser->getImage()); + $instance->comments = $parser->getComments(); + $instance->excerpt = $parser->getIntro(); + + return $instance; + } + + /** + * @return int|null + */ + public function getMalId(): ?int + { + return $this->malId; + } + + /** + * @return string + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @return \DateTimeImmutable + */ + public function getDate(): \DateTimeImmutable + { + return $this->date; + } + + /** + * @return string + */ + public function getAuthorUsername(): string + { + return $this->authorUsername; + } + + /** + * @return string + */ + public function getAuthorUrl(): string + { + return $this->authorUrl; + } + + /** + * @return string + */ + public function getForumUrl(): string + { + return $this->forumUrl; + } + + /** + * @return WrapImageResource + */ + public function getImages(): WrapImageResource + { + return $this->images; + } + + /** + * @return int + */ + public function getComments(): int + { + return $this->comments; + } + + /** + * @return string + */ + public function getExcerpt(): string + { + return $this->excerpt; + } +} diff --git a/src/Model/Resource/NewsImageResource/Jpg.php b/src/Model/Resource/NewsImageResource/Jpg.php new file mode 100644 index 00000000..93dc6580 --- /dev/null +++ b/src/Model/Resource/NewsImageResource/Jpg.php @@ -0,0 +1,71 @@ +imageUrl = $imageUrl; + + if ($instance->imageUrl === null) { + return $instance; + } + + $instance->smallImageUrl = str_replace('/s/', '/r/100x156/s/', $imageUrl); + $instance->largeImageUrl = str_replace('/s/', '/r/200x312/s/', $imageUrl); + + return $instance; + } + + /** + * @return string|null + */ + public function getImageUrl(): ?string + { + return $this->imageUrl; + } + + /** + * @return string|null + */ + public function getSmallImageUrl(): ?string + { + return $this->smallImageUrl; + } + + /** + * @return string|null + */ + public function getLargeImageUrl(): ?string + { + return $this->largeImageUrl; + } + +} diff --git a/src/Model/Resource/NewsImageResource/NewsImageResource.php b/src/Model/Resource/NewsImageResource/NewsImageResource.php new file mode 100644 index 00000000..0d183f09 --- /dev/null +++ b/src/Model/Resource/NewsImageResource/NewsImageResource.php @@ -0,0 +1,48 @@ +jpg = Jpg::factory($imageUrl); + + return $instance; + } + + /** + * @return string + */ + public function __toString() : string + { + return $this->getJpg()->getImageUrl(); + } + + /** + * @return Jpg + */ + public function getJpg(): Jpg + { + return $this->jpg; + } + +} diff --git a/src/MyAnimeList/MalClient.php b/src/MyAnimeList/MalClient.php index 42f31762..990ff83f 100644 --- a/src/MyAnimeList/MalClient.php +++ b/src/MyAnimeList/MalClient.php @@ -440,15 +440,15 @@ public function getPersonPictures(Request\Person\PersonPicturesRequest $request) /** * @param Request\RequestInterface $request * - * @return Model\News\NewsList + * @return Model\News\ResourceNewsList * @throws BadResponseException * @throws ParserException */ - public function getNewsList(Request\RequestInterface $request): Model\News\NewsList + public function getNewsList(Request\RequestInterface $request): Model\News\ResourceNewsList { $crawler = $this->ghoutte->request('GET', $request->getPath()); try { - $parser = new Parser\News\NewsListParser($crawler); + $parser = new Parser\News\ResourceNewsListParser($crawler); return $parser->getModel(); } catch (\Exception $e) { @@ -1367,4 +1367,23 @@ public function getUserClubs(Request\User\UserClubsRequest $request) : array throw ParserException::fromRequest($request, $e); } } + + + /** + * @param Request\News\RecentNewsRequest $request + * @return Model\News\NewsList + * @throws BadResponseException + * @throws ParserException + */ + public function getRecentNews(Request\News\RecentNewsRequest $request): Model\News\NewsList + { + $crawler = $this->ghoutte->request('GET', $request->getPath()); + try { + $parser = new Parser\News\NewsListParser($crawler); + + return $parser->getModel(); + } catch (\Exception $e) { + throw ParserException::fromRequest($request, $e); + } + } } diff --git a/src/Parser/News/NewsListItemParser.php b/src/Parser/News/NewsListItemParser.php index 13375950..b91e70a4 100644 --- a/src/Parser/News/NewsListItemParser.php +++ b/src/Parser/News/NewsListItemParser.php @@ -7,12 +7,14 @@ use Jikan\Helper\Parser; use Jikan\Model\Common\MalUrl; use Jikan\Model\News\NewsListItem; +use Jikan\Model\News\ResourceNewsListItem; use Jikan\Parser\Common\MalUrlParser; use Jikan\Parser\ParserInterface; +use PHPUnit\Exception; use Symfony\Component\DomCrawler\Crawler; /** - * Class NewsListParser + * Class NewsListItemParser * * @package Jikan\Parser */ @@ -21,10 +23,10 @@ class NewsListItemParser implements ParserInterface /** * @var Crawler */ - private $crawler; + private Crawler $crawler; /** - * MangaParser constructor. + * NewsListItemParser constructor. * * @param Crawler $crawler */ @@ -33,23 +35,16 @@ public function __construct(Crawler $crawler) $this->crawler = $crawler; } - /** - * @return NewsListItem - * @throws \RuntimeException - * @throws \InvalidArgumentException - */ + public function getModel(): NewsListItem { return NewsListItem::fromParser($this); } - /** - * @return string - * @throws \InvalidArgumentException - */ + public function getTitle(): string { - return $this->crawler->filterXPath('//p/a/strong')->text(); + return $this->crawler->filterXPath('//div[contains(@class,"news-unit-right")]/p/a')->text(); } /** @@ -72,23 +67,25 @@ public function getMalId() : ?int */ public function getUrl(): string { - return Constants::BASE_URL.$this->crawler->filterXPath('//p/a/strong/..')->attr('href'); + return Constants::BASE_URL.$this->crawler + ->filterXPath('//div[contains(@class,"news-unit-right")]/p/a') + ->attr('href'); } /** * @return string|null * @throws \InvalidArgumentException */ - public function getImage(): ?string + public function getImageUrl(): ?string { - $image = $this->crawler->filterXPath('//img[1]'); + $image = $this->crawler->filterXPath('//*[contains(@class, "image-link")]/img'); if (!$image->count()) { return null; } return Parser::parseImageQuality( - $image->attr('data-src') + $image->attr('src') ); } @@ -98,7 +95,14 @@ public function getImage(): ?string */ public function getDate(): ?\DateTimeImmutable { - return Parser::parseDate(explode(' by', $this->crawler->filterXPath('//p[last()]')->text())[0]); + return Parser::parseDate( + explode(' by', + Parser::removeChildNodes( + $this->crawler + ->filterXPath('//div[contains(@class,"news-unit-right")]/div[contains(@class, "information")]/p') + )->text() + )[0] + ); } /** @@ -107,7 +111,10 @@ public function getDate(): ?\DateTimeImmutable */ public function getAuthor(): MalUrl { - return (new MalUrlParser($this->crawler->filterXPath('//a[contains(@href, "profile")][1]')))->getModel(); + return (new MalUrlParser( + $this->crawler + ->filterXPath('//div[contains(@class,"news-unit-right")]/div[contains(@class, "information")]/p/a[1]') + ))->getModel(); } /** @@ -116,7 +123,9 @@ public function getAuthor(): MalUrl */ public function getDiscussionLink(): string { - return Constants::BASE_URL.$this->crawler->filterXPath('//a[last()]')->attr('href'); + return Constants::BASE_URL.$this->crawler + ->filterXPath('//div[contains(@class,"news-unit-right")]/div[contains(@class, "information")]/p/a[last()]') + ->attr('href'); } /** @@ -125,8 +134,11 @@ public function getDiscussionLink(): string */ public function getComments() : int { - $comments = $this->crawler->filterXPath('//a[last()]')->text(); - preg_match('~Discuss \((\d+) comments\)~', $comments, $comments); + $comments = $this->crawler + ->filterXPath('//div[contains(@class,"news-unit-right")]/div[contains(@class, "information")]/p/a[last()]') + ->text(); + + preg_match('~\((\d+) comments\)~', $comments, $comments); return !empty($comments) ? $comments[1] : 0; } @@ -134,10 +146,12 @@ public function getComments() : int * @return string * @throws \InvalidArgumentException */ - public function getIntro(): string + public function getExcerpt(): string { return JString::cleanse( - Parser::removeChildNodes($this->crawler->filterXPath('//p[2]'))->text() + $this->crawler + ->filterXPath('//div[contains(@class,"news-unit-right")]/div[contains(@class, "text")]') + ->text() ); } } diff --git a/src/Parser/News/NewsListParser.php b/src/Parser/News/NewsListParser.php index 9a36386f..dae7568e 100644 --- a/src/Parser/News/NewsListParser.php +++ b/src/Parser/News/NewsListParser.php @@ -3,8 +3,6 @@ namespace Jikan\Parser\News; use Jikan\Model\News\NewsList; -use Jikan\Model\News\NewsListItem; -use Jikan\Model\Search\AnimeSearch; use Jikan\Parser\ParserInterface; use Symfony\Component\DomCrawler\Crawler; @@ -18,10 +16,10 @@ class NewsListParser implements ParserInterface /** * @var Crawler */ - private $crawler; + private Crawler $crawler; /** - * MangaParser constructor. + * NewsListParser constructor. * * @param Crawler $crawler */ @@ -30,26 +28,17 @@ public function __construct(Crawler $crawler) $this->crawler = $crawler; } - /** - * @return NewsList - * @throws \Exception - * @throws \RuntimeException - * @throws \InvalidArgumentException - */ + public function getModel(): NewsList { return NewsList::fromParser($this); } - /** - * @return NewsListItem[] - * @throws \RuntimeException - * @throws \InvalidArgumentException - */ + public function getResults(): array { return $this->crawler - ->filterXPath('//div[contains(@class,"js-scrollfix-bottom-rel")]/div[@class="clearfix"]') + ->filterXPath('//*[@id="content"]/div[1]/div/div[contains(@class, "news-list")]/div[contains(@class, "news-unit") and contains(@class, "rect")]') ->each( function (Crawler $crawler) { return (new NewsListItemParser($crawler))->getModel(); @@ -62,13 +51,14 @@ function (Crawler $crawler) { */ public function getHasNextPage(): bool { - $pages = $this->crawler - ->filterXPath('//*[@id="content"]/table/tr/td[2]/div[1]/a[contains(text(), "More News")]'); - - if ($pages->count()) { - return true; - } - return false; } + + /** + * @return int + */ + public function getLastVisiblePage(): int + { + return 1; + } } diff --git a/src/Parser/News/ResourceNewsListItemParser.php b/src/Parser/News/ResourceNewsListItemParser.php new file mode 100644 index 00000000..e2bf9d19 --- /dev/null +++ b/src/Parser/News/ResourceNewsListItemParser.php @@ -0,0 +1,143 @@ +crawler = $crawler; + } + + /** + * @return ResourceNewsListItem + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getModel(): ResourceNewsListItem + { + return ResourceNewsListItem::fromParser($this); + } + + /** + * @return string + * @throws \InvalidArgumentException + */ + public function getTitle(): string + { + return $this->crawler->filterXPath('//p/a/strong')->text(); + } + + /** + * @return int|null + */ + public function getMalId() : ?int + { + preg_match('~([\d]+)$~', $this->getUrl(), $matches); + + if (!empty($matches)) { + return $matches[1]; + } + + return null; + } + + /** + * @return string + * @throws \InvalidArgumentException + */ + public function getUrl(): string + { + return Constants::BASE_URL.$this->crawler->filterXPath('//p/a/strong/..')->attr('href'); + } + + /** + * @return string|null + * @throws \InvalidArgumentException + */ + public function getImage(): ?string + { + $image = $this->crawler->filterXPath('//img[1]'); + + if (!$image->count()) { + return null; + } + + return Parser::parseImageQuality( + $image->attr('data-src') + ); + } + + /** + * @return \DateTimeImmutable + * @throws \InvalidArgumentException + */ + public function getDate(): ?\DateTimeImmutable + { + return Parser::parseDate(explode(' by', $this->crawler->filterXPath('//p[last()]')->text())[0]); + } + + /** + * @return MalUrl + * @throws \InvalidArgumentException + */ + public function getAuthor(): MalUrl + { + return (new MalUrlParser($this->crawler->filterXPath('//a[contains(@href, "profile")][1]')))->getModel(); + } + + /** + * @return string + * @throws \InvalidArgumentException + */ + public function getDiscussionLink(): string + { + return Constants::BASE_URL.$this->crawler->filterXPath('//a[last()]')->attr('href'); + } + + /** + * @return int + * @throws \InvalidArgumentException + */ + public function getComments() : int + { + $comments = $this->crawler->filterXPath('//a[last()]')->text(); + preg_match('~Discuss \((\d+) comments\)~', $comments, $comments); + return !empty($comments) ? $comments[1] : 0; + } + + /** + * @return string + * @throws \InvalidArgumentException + */ + public function getIntro(): string + { + return JString::cleanse( + Parser::removeChildNodes($this->crawler->filterXPath('//p[2]'))->text() + ); + } +} diff --git a/src/Parser/News/ResourceNewsListParser.php b/src/Parser/News/ResourceNewsListParser.php new file mode 100644 index 00000000..bd3afeb4 --- /dev/null +++ b/src/Parser/News/ResourceNewsListParser.php @@ -0,0 +1,74 @@ +crawler = $crawler; + } + + /** + * @return ResourceNewsList + * @throws \Exception + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getModel(): ResourceNewsList + { + return ResourceNewsList::fromParser($this); + } + + /** + * @return ResourceNewsListItem[] + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getResults(): array + { + return $this->crawler + ->filterXPath('//div[contains(@class,"js-scrollfix-bottom-rel")]/div[@class="clearfix"]') + ->each( + function (Crawler $crawler) { + return (new ResourceNewsListItemParser($crawler))->getModel(); + } + ); + } + + /** + * @return bool + */ + public function getHasNextPage(): bool + { + $pages = $this->crawler + ->filterXPath('//*[@id="content"]/table/tr/td[2]/div[1]/a[contains(text(), "More News")]'); + + if ($pages->count()) { + return true; + } + + return false; + } +} diff --git a/src/Request/News/RecentNewsRequest.php b/src/Request/News/RecentNewsRequest.php new file mode 100644 index 00000000..1b755a93 --- /dev/null +++ b/src/Request/News/RecentNewsRequest.php @@ -0,0 +1,45 @@ +page = $page; + } + + /** + * @return string + */ + public function getPath(): string + { + return sprintf('https://myanimelist.net/news?p=%d', $this->page); + } + + /** + * @return int + */ + public function getPage(): int + { + return $this->page; + } + +} diff --git a/test/JikanTest/Parser/News/NewsListItemParserTest.php b/test/JikanTest/Parser/News/NewsListItemParserTest.php index a1f3bb77..5127a978 100644 --- a/test/JikanTest/Parser/News/NewsListItemParserTest.php +++ b/test/JikanTest/Parser/News/NewsListItemParserTest.php @@ -2,7 +2,7 @@ namespace JikanTest\Parser\News; -use Jikan\Parser\News\NewsListItemParser; +use Jikan\Parser\News\ResourceNewsListItemParser; use JikanTest\TestCase; /** @@ -11,7 +11,7 @@ class NewsListItemParserTest extends TestCase { /** - * @var NewsListItemParser + * @var ResourceNewsListItemParser */ private $parser; @@ -21,7 +21,7 @@ public function setUp(): void $client = new \Goutte\Client($this->httpClient); $crawler = $client->request('GET', 'https://myanimelist.net/manga/2/Berserk/news'); - $this->parser = new NewsListItemParser( + $this->parser = new ResourceNewsListItemParser( $crawler->filterXPath('//div[contains(@class,"js-scrollfix-bottom-rel")]/div[@class="clearfix"]')->first() ); }