public function process(array $documents, &$context)
 {
     $document = $documents[self::URL_MEDIA];
     $dom = self::getDOM($document);
     $xpath = new DOMXPath($dom);
     if ($xpath->query('//h1[text() = \'404 Not Found\']')->length >= 1) {
         throw new BadProcessorKeyException($context->key);
     }
     $title = Strings::removeSpaces(self::getNodeValue($xpath, '//h1//span'));
     if (empty($title)) {
         throw new BadProcessorDocumentException($document, 'empty title');
     }
     $typeMal = strtolower(Strings::removeSpaces(self::getNodeValue($xpath, '//span[starts-with(text(), \'Type\')]/following-sibling::node()[self::text()]')));
     $type = Strings::makeEnum($typeMal, ['tv' => AnimeMediaType::TV, 'ova' => AnimeMediaType::OVA, 'movie' => AnimeMediaType::Movie, 'special' => AnimeMediaType::Special, 'ona' => AnimeMediaType::ONA, 'music' => AnimeMediaType::Music, 'manga' => MangaMediaType::Manga, 'novel' => MangaMediaType::Novel, 'one-shot' => MangaMediaType::Oneshot, 'doujinshi' => MangaMediaType::Doujinshi, 'manhwa' => MangaMediaType::Manhwa, 'manhua' => MangaMediaType::Manhua, 'oel' => MangaMediaType::OEL, 'unknown' => $this->media == Media::Manga ? MangaMediaType::Unknown : AnimeMediaType::Unknown], null);
     if ($type === null) {
         throw new BadProcessorDocumentException($document, 'empty sub type');
     }
     $image = self::getNodeValue($xpath, '//meta[@property = \'og:image\']', null, 'content');
     $score = Strings::makeFloat(self::getNodeValue($xpath, '//span[@itemprop = \'ratingValue\']'));
     $scoredByUsers = Strings::makeInteger(self::getNodeValue($xpath, '//span[@itemprop = \'ratingCount\']'));
     $ranked = Strings::makeInteger(self::getNodeValue($xpath, '//span[starts-with(text(), \'Ranked\')]/following-sibling::node()[self::text()]'));
     $popularity = Strings::makeInteger(self::getNodeValue($xpath, '//span[starts-with(text(), \'Popularity\')]/following-sibling::node()[self::text()]'));
     $members = Strings::makeInteger(self::getNodeValue($xpath, '//span[starts-with(text(), \'Members\')]/following-sibling::node()[self::text()]'));
     $favorites = Strings::makeInteger(self::getNodeValue($xpath, '//span[starts-with(text(), \'Favorites\')]/following-sibling::node()[self::text()]'));
     $statusMal = strtolower(Strings::removeSpaces(self::getNodeValue($xpath, '//span[starts-with(text(), \'Status\')]/following-sibling::node()[self::text()]')));
     $status = Strings::makeEnum($statusMal, ['not yet published' => MediaStatus::NotYetPublished, 'not yet aired' => MediaStatus::NotYetPublished, 'publishing' => MediaStatus::Publishing, 'currently airing' => MediaStatus::Publishing, 'finished' => MediaStatus::Finished, 'finished airing' => MediaStatus::Finished], null);
     if ($status === null) {
         throw new BadProcessorDocumentException($document, 'unknown status: ' . $malStatus);
     }
     $publishedString = Strings::removeSpaces(self::getNodeValue($xpath, '//span[starts-with(text(), \'Aired\') or starts-with(text(), \'Published\')]/following-sibling::node()[self::text()]'));
     $position = strrpos($publishedString, ' to ');
     if ($position !== false) {
         $publishedFrom = Strings::makeDate(substr($publishedString, 0, $position));
         $publishedTo = Strings::makeDate(substr($publishedString, $position + 4));
     } else {
         $publishedFrom = Strings::makeDate($publishedString);
         $publishedTo = Strings::makeDate($publishedString);
     }
     $media =& $context->media;
     $media->media = $this->media;
     $media->title = $title;
     $media->sub_type = $type;
     $media->picture_url = $image;
     $media->average_score = $score;
     $media->average_score_users = $scoredByUsers;
     $media->publishing_status = $status;
     $media->popularity = $popularity;
     $media->members = $members;
     $media->favorites = $favorites;
     $media->ranking = $ranked;
     $media->published_from = $publishedFrom;
     $media->published_to = $publishedTo;
     $media->processed = date('Y-m-d H:i:s');
     R::store($media);
 }
 public function process(array $documents, &$context)
 {
     Database::delete('usermedia', ['user_id' => $context->user->id]);
     $context->user->cool = false;
     foreach (Media::getConstList() as $media) {
         $key = $media == Media::Anime ? self::URL_ANIMELIST : self::URL_MANGALIST;
         $isPrivate = strpos($documents[$key]->content, 'This list has been made private by the owner') !== false;
         $key = $media == Media::Anime ? self::URL_ANIMEINFO : self::URL_MANGAINFO;
         $doc = $documents[$key];
         $dom = self::getDOM($doc);
         $xpath = new DOMXPath($dom);
         if ($xpath->query('//myinfo')->length == 0) {
             throw new BadProcessorDocumentException($doc, 'myinfo block is missing');
         }
         if (strpos($doc->content, '</myanimelist>') === false) {
             throw new BadProcessorDocumentException($doc, 'list is only partially downloaded');
         }
         $nodes = $xpath->query('//anime | //manga');
         $data = [];
         foreach ($nodes as $root) {
             $mediaMalId = Strings::makeInteger(self::getNodeValue($xpath, 'series_animedb_id | series_mangadb_id', $root));
             $score = Strings::makeInteger(self::getNodeValue($xpath, 'my_score', $root));
             $startDate = Strings::makeDate(self::getNodeValue($xpath, 'my_start_date', $root));
             $finishDate = Strings::makeDate(self::getNodeValue($xpath, 'my_finish_date', $root));
             $status = Strings::makeEnum(self::getNodeValue($xpath, 'my_status', $root), [1 => UserListStatus::Completing, 2 => UserListStatus::Finished, 3 => UserListStatus::OnHold, 4 => UserListStatus::Dropped, 6 => UserListStatus::Planned], UserListStatus::Unknown);
             $finishedEpisodes = null;
             $finishedChapters = null;
             $finishedVolumes = null;
             switch ($media) {
                 case Media::Anime:
                     $finishedEpisodes = Strings::makeInteger(self::getNodeValue($xpath, 'my_watched_episodes', $root));
                     break;
                 case Media::Manga:
                     $finishedChapters = Strings::makeInteger(self::getNodeValue($xpath, 'my_read_chapters', $root));
                     $finishedVolumes = Strings::makeInteger(self::getNodeValue($xpath, 'my_read_volumes', $root));
                     break;
                 default:
                     throw new BadMediaException();
             }
             $data[] = ['user_id' => $context->user->id, 'mal_id' => $mediaMalId, 'media' => $media, 'score' => $score, 'start_date' => $startDate, 'end_date' => $finishDate, 'finished_episodes' => $finishedEpisodes, 'finished_chapters' => $finishedChapters, 'finished_volumes' => $finishedVolumes, 'status' => $status];
         }
         Database::insert('usermedia', $data);
         $dist = RatingDistribution::fromEntries(ReflectionHelper::arraysToClasses($data));
         $daysSpent = Strings::makeFloat(self::getNodeValue($xpath, '//user_days_spent_watching'));
         $user =& $context->user;
         $user->{Media::toString($media) . '_days_spent'} = $daysSpent;
         $user->{Media::toString($media) . '_private'} = $isPrivate;
         $user->cool |= ($dist->getRatedCount() >= 50 and $dist->getStandardDeviation() >= 1.5);
         R::store($user);
     }
 }
 public function process(array $documents, &$context)
 {
     $doc = $documents[self::URL_MEDIA];
     $dom = self::getDOM($doc);
     $xpath = new DOMXPath($dom);
     if ($xpath->query('//div[@class = \'badresult\']')->length >= 1) {
         throw new BadProcessorKeyException($context->key);
     }
     $title = Strings::removeSpaces(self::getNodeValue($xpath, '//h1/*/following-sibling::node()[1][self::text()]'));
     if (empty($title)) {
         throw new BadProcessorDocumentException($doc, 'empty title');
     }
     //sub type
     $malSubType = strtolower(Strings::removeSpaces(self::getNodeValue($xpath, '//span[starts-with(text(), \'Type\')]/following-sibling::node()[self::text()]')));
     $subType = Strings::makeEnum($malSubType, ['tv' => AnimeMediaType::TV, 'ova' => AnimeMediaType::OVA, 'movie' => AnimeMediaType::Movie, 'special' => AnimeMediaType::Special, 'ona' => AnimeMediaType::ONA, 'music' => AnimeMediaType::Music, 'manga' => MangaMediaType::Manga, 'novel' => MangaMediaType::Novel, 'one shot' => MangaMediaType::OneShot, 'doujin' => MangaMediaType::Doujin, 'manhwa' => MangaMediaType::Manhwa, 'manhua' => MangaMediaType::Manhua, 'oel' => MangaMediaType::OEL, '' => $this->media == Media::Manga ? MangaMediaType::Unknown : AnimeMediaType::Unknown], null);
     if ($subType === null) {
         throw new BadProcessorDocumentException($doc, 'empty sub type');
     }
     //mal id
     $malId = self::getNodeValue($xpath, '//input[starts-with(@id, \'myinfo_\')]', null, 'value');
     //picture
     $pictureUrl = self::getNodeValue($xpath, '//td[@class = \'borderClass\']//img', null, 'src');
     //rank
     $averageScore = Strings::makeFloat(self::getNodeValue($xpath, '//span[starts-with(text(), \'Score\')]/following-sibling::node()[self::text()]'));
     $averageScoreUsers = Strings::extractInteger(self::getNodeValue($xpath, '//small[starts-with(text(), \'(scored by\')]'));
     $ranking = Strings::makeInteger(self::getNodeValue($xpath, '//span[starts-with(text(), \'Ranked\')]/following-sibling::node()[self::text()]'));
     $popularity = Strings::makeInteger(self::getNodeValue($xpath, '//span[starts-with(text(), \'Popularity\')]/following-sibling::node()[self::text()]'));
     $memberCount = Strings::makeInteger(self::getNodeValue($xpath, '//span[starts-with(text(), \'Members\')]/following-sibling::node()[self::text()]'));
     $favoriteCount = Strings::makeInteger(self::getNodeValue($xpath, '//span[starts-with(text(), \'Favorites\')]/following-sibling::node()[self::text()]'));
     //status
     $malStatus = strtolower(Strings::removeSpaces(self::getNodeValue($xpath, '//span[starts-with(text(), \'Status\')]/following-sibling::node()[self::text()]')));
     $status = Strings::makeEnum($malStatus, ['not yet published' => MediaStatus::NotYetPublished, 'not yet aired' => MediaStatus::NotYetPublished, 'publishing' => MediaStatus::Publishing, 'currently airing' => MediaStatus::Publishing, 'finished' => MediaStatus::Finished, 'finished airing' => MediaStatus::Finished], null);
     if ($status === null) {
         throw new BadProcessorDocumentException($doc, 'unknown status: ' . $malStatus);
     }
     //air dates
     $publishedString = Strings::removeSpaces(self::getNodeValue($xpath, '//span[starts-with(text(), \'Aired\') or starts-with(text(), \'Published\')]/following-sibling::node()[self::text()]'));
     $pos = strrpos($publishedString, ' to ');
     if ($pos !== false) {
         $publishedFrom = Strings::makeDate(substr($publishedString, 0, $pos));
         $publishedTo = Strings::makeDate(substr($publishedString, $pos + 4));
     } else {
         $publishedFrom = Strings::makeDate($publishedString);
         $publishedTo = Strings::makeDate($publishedString);
     }
     $media =& $context->media;
     $media->mal_id = $malId;
     $media->media = $this->media;
     $media->title = $title;
     $media->sub_type = $subType;
     $media->picture_url = $pictureUrl;
     $media->average_score = $averageScore;
     $media->average_score_users = $averageScoreUsers;
     $media->publishing_status = $status;
     $media->popularity = $popularity;
     $media->members = $memberCount;
     $media->favorites = $favoriteCount;
     $media->ranking = $ranking;
     $media->published_from = $publishedFrom;
     $media->published_to = $publishedTo;
     $media->processed = date('Y-m-d H:i:s');
     R::store($media);
 }