[ 'url_pattern' => '/(?:https?:\/\/)?(?:www\.)?imdb\.com\/title\/(tt\d+)\/?/', 'home_page' => 'https://www.imdb.com/', 'rating_average_img' => 'pic/imdb2.png', 'rating_pattern_in_desc' => "/IMDb评分.*([\d\.]+)\//iU", ], self::SITE_DOUBAN => [ 'url_pattern' => '/(?:https?:\/\/)?(?:(?:movie|www)\.)?douban\.com\/(?:subject|movie)\/(\d+)\/?/', 'home_page' => 'https://www.douban.com/', 'rating_average_img' => 'pic/douban2.png', 'rating_pattern_in_desc' => "/豆瓣评分.*([\d\.]+)\//iU", ], self::SITE_BANGUMI => [ 'url_pattern' => '/(?:https?:\/\/)?(?:bgm\.tv|bangumi\.tv|chii\.in)\/subject\/(\d+)\/?/', 'home_page' => 'https://bangumi.tv/', 'rating_average_img' => 'pic/bangumi.jpg', ], //Banned ! // self::SITE_STEAM => [ // 'url_pattern' => '/(?:https?:\/\/)?(?:store\.)?steam(?:powered|community)\.com\/app\/(\d+)\/?/', // 'home_page' => 'https://store.steampowered.com/', // 'rating_average_img' => 'pic/steam.svg', // ], self::SITE_INDIENOVA => [ 'url_pattern' => '/(?:https?:\/\/)?indienova\.com\/game\/(\S+)/', 'home_page' => 'https://indienova.com/', 'rating_average_img' => 'pic/invienova.jpg', ], //seems url_pattern has changed // self::SITE_EPIC => [ // 'url_pattern' => '/(?:https?:\/\/)?www\.epicgames\.com\/store\/[a-zA-Z-]+\/product\/(\S+)\/\S?/', // 'home_page' => 'https://store.epicgames.com/', // 'rating_average_img' => 'pic/epic_game.png', // ], ]; public function __construct() { $setting = get_setting('main'); $this->setApiPoint($setting['pt_gen_api_point'] ?? ''); } public function getApiPoint(): string { return $this->apiPoint; } public function setApiPoint(string $apiPoint) { $this->apiPoint = trim($apiPoint); } public function generate(string $url, bool $withoutCache = false): array { $parsed = $this->parse($url); $targetUrl = trim($this->apiPoint, '/'); if (Str::contains($targetUrl, '?')) { $targetUrl .= "&"; } else { $targetUrl .= "?"; } $targetUrl .= sprintf('site=%s&sid=%s&url=%s', $parsed['site'] , $parsed['id'], urlencode($parsed['url'])); return $this->request($targetUrl, $withoutCache); } public function parse(string $url): array { foreach (self::$validSites as $site => $info) { if (preg_match($info['url_pattern'], $url, $matches)) { return [ 'site' => $site, 'url' => $matches[0], 'id' => $matches[1] ]; } } throw new PTGenException("invalid url: $url"); } private function buildDetailsPageTableRow($torrentId, $ptGenArr, $site): string { global $lang_details; if ($this->isRawPTGen($ptGenArr)) { $ptGenFormatted = $ptGenArr['format']; } elseif ($this->isIyuu($ptGenArr)) { $ptGenFormatted = $ptGenArr['data']['format']; } else { do_log("Invalid pt gen data", 'error'); return ''; } $poster = ''; if (!empty($ptGenArr['poster'])) { $poster = $ptGenArr['poster']; } elseif (preg_match('/\[img\](.*)\[\/img\]/iU', $ptGenFormatted, $matches)) { $poster = $matches[1]; } if ($poster) { $prefix = sprintf("[img]%s[/img]\n", $poster); $ptGenFormatted = mb_substr($ptGenFormatted, mb_strlen($prefix, 'utf-8') + 1); } $ptGenFormatted = format_comment($ptGenFormatted); $ptGenFormatted .= sprintf( '%s %s%s%s', $lang_details['text_information_updated_at'], $ptGenArr['__updated_at'], $lang_details['text_might_be_outdated'], $torrentId, $site, $lang_details['text_here_to_update'] ); $titleShowOrHide = $lang_details['title_show_or_hide'] ?? ''; $id = 'pt-gen-' . $site; $posterHtml = ""; if ($poster) { $posterHtml = sprintf('
Show/Hide PT-Gen-{$site} $posterHtml
{$ptGenFormatted}
HTML; return $html; } private function request(string $url, bool $withoutCache = false): array { $begin = microtime(true); $logPrefix = "url: $url"; $cacheKey = $this->getApiPointResultCacheKey($url); if (!$withoutCache) { $cache = NexusDB::cache_get($cacheKey); if ($cache) { do_log("$logPrefix, from cache"); return $cache; } } do_log("$logPrefix, going to send request..."); $http = new Client(); $response = $http->get($url, ['timeout' => 10]); $statusCode = $response->getStatusCode(); if ($statusCode != 200) { $msg = "api point response http status code: $statusCode"; do_log("$logPrefix, $msg"); throw new PTGenException($msg); } $bodyString = (string)$response->getBody(); if (empty($bodyString)) { $msg = "response body empty"; do_log("$logPrefix, $msg"); throw new PTGenException($msg); } $bodyArr = json_decode($bodyString, true); if (empty($bodyArr) || !is_array($bodyArr)) { $msg = "response body error: $bodyString"; do_log("$logPrefix, $msg"); throw new PTGenException($msg); } if ($this->isRawPTGen($bodyArr) || $this->isIyuu($bodyArr)) { NexusDB::cache_put($cacheKey, $bodyArr, 24 * 3600); do_log("$logPrefix, success get from api point, use time: " . (microtime(true) - $begin)); $bodyArr['__updated_at'] = now()->toDateTimeString(); return $bodyArr; } else { $msg = "error: " . $bodyArr['error'] ?? ''; do_log("$logPrefix, response: $bodyString"); throw new PTGenException($msg); } } public function deleteApiPointResultCache($url) { NexusDB::cache_del($this->getApiPointResultCacheKey($url)); } private function getApiPointResultCacheKey($url) { return __METHOD__ . "_$url"; } public function renderUploadPageFormInput($ptGen = ''): string { $arr = json_decode($ptGen, true); $link = is_array($arr) ? $arr['__link'] : $ptGen; $y = $this->buildInput("pt_gen", $link, nexus_trans('ptgen.tooltip', ['sites' => $this->buildTooltip()]), nexus_trans('ptgen.btn_get_desc')); return tr(nexus_trans('ptgen.label'), $y, 1, '', true); } private function buildTooltip(): string { $results = []; foreach (self::$validSites as $site => $info) { $results[] = sprintf('%s', $info['home_page'], $site); } return implode(' / ', $results); } public function buildInput($name, $value, $note, $btnText): string { $btn = ''; if ($this->apiPoint != '') { $btn = '
'; } $input = <<
{$note}
$btn
HTML; return $input; } public function renderDetailsPageDescription($torrentId, $torrentPtGenArr): array { $html = ''; $jsonArr = []; $update = false; $torrentPtGenArr = (array)$torrentPtGenArr; foreach (self::$validSites as $site => $info) { if (empty($torrentPtGenArr[$site]['link'])) { continue; } $link = $torrentPtGenArr[$site]['link']; $data = $torrentPtGenArr[$site]['data'] ?? []; if (!empty($data)) { $jsonArr[$site] = [ 'link' => $link, 'data' => $data, ]; $html .= $this->buildDetailsPageTableRow($torrentId, $data, $site); } else { try { $ptGenArr = $this->generate($torrentPtGenArr[$site]['link']); } catch (\Exception $e) { $log = $e->getMessage() . ", trace: " . $e->getTraceAsString(); do_log($log,'error'); $ptGenArr = [ 'format' => $e->getMessage() ]; } $jsonArr[$site] = [ 'link' => $link, 'data' => $ptGenArr, ]; $html .= $this->buildDetailsPageTableRow($torrentId, $ptGenArr, $site); if (!$update) { $update = true; } } } return ['json_arr' => $jsonArr, 'html' => $html, 'update' => $update]; } public function buildRatingSpan(array $siteIdAndRating): string { $result = '
'; $count = 1; $ratingIcons = []; foreach (self::$validSites as $site => $info) { if (!isset($siteIdAndRating[$site])) { continue; } $rating = $siteIdAndRating[$site]; if (empty($rating)) { continue; } if ($count > 2) { //only show the first two break; } $ratingIcons[] = $this->getRatingIcon($site, $rating); $count++; } if (empty($ratingIcons)) { $ratingIcons[] = $this->getRatingIcon(self::SITE_IMDB, 'N/A'); $ratingIcons[] = $this->getRatingIcon(self::SITE_DOUBAN, 'N/A'); } $result .= implode("", $ratingIcons) . '
'; return $result; } public function getRatingIcon($siteId, $rating): string { if (is_numeric($rating)) { $rating = number_format($rating, 1); } $result = sprintf( '
%s%s
', self::$validSites[$siteId]['rating_average_img'], $siteId, $siteId, $rating ); return $result; } public function isRawPTGen(array $bodyArr): bool { return isset($bodyArr['success']) && $bodyArr['success']; } public function isIyuu(array $bodyArr): bool { return false; //Not support, due to change frequently // return isset($bodyArr['ret']) && $bodyArr['ret'] == 200; } public function listRatings(array $ptGenData, string $imdbLink, string $desc = ''): array { $results = []; $log = ""; //First, get from PTGen foreach (self::$validSites as $site => $info) { if (!isset($ptGenData[$site]['data'])) { continue; } $data = $ptGenData[$site]['data']; $log .= ", handling site: $site"; $rating = ''; if (isset($data['__rating'])) { //__rating is new add $rating = $data['__rating']; $log .= ", from __rating"; } else { // from original structure fetch if ($this->isRawPTGen($data)) { $log .= ", isRawPTGen"; $rating = $this->getRawPTGenRating($data, $site); } elseif ($this->isIyuu($data)) { $log .= ", isIyuu"; $pattern = $info['rating_pattern_in_desc'] ?? null; if ($pattern && preg_match($pattern,$data['data']['format'], $matches)) { $rating = $matches[1]; } } } if (!empty($rating)) { $results[$site] = $rating; $log .= ", get rating: $rating"; } else { $log .= ", can't get rating"; } } //Second, imdb can get from imdb api if (!isset($results[self::SITE_IMDB]) && !empty($imdbLink)) { $imdb = new Imdb(); $imdbRating = $imdb->getRating($imdbLink); $results[self::SITE_IMDB] = $imdbRating; $log .= ", again 'imdb' from: $imdbLink -> $imdbRating"; } //Otherwise, get from desc if (!empty($desc)) { foreach (self::$validSites as $site => $info) { if (isset($results[$site])) { continue; } if (empty($info['rating_pattern_in_desc'])) { continue; } $pattern = $info['rating_pattern_in_desc']; $log .= ", at last, trying to get '$site' from desc with pattern: $pattern"; if (preg_match($pattern, $desc, $matches)) { $log .= ", get " . $matches[1]; $results[$site] = $matches[1]; } else { $log .= ", not match"; } } } do_log($log); return $results; } public function updateTorrentPtGen(int $id): bool|array { $now = Carbon::now(); $log = "updateTorrentPtGen, torrent: " . $id; $torrent = Torrent::query()->find($id); if (empty($torrent)) { do_log("$log, Torrent not found"); return false; } $extra = $torrent->extra; $arr = $extra->pt_gen; if (is_array($arr)) { if (!empty($arr['__updated_at'])) { $log .= ", updated_at: " . $arr['__updated_at']; $updatedAt = Carbon::parse($arr['__updated_at']); $diffInDays = $now->diffInDays($updatedAt); $log .= ", diffInDays: $diffInDays"; if ($diffInDays < 30) { do_log("$log, less 30 days, don't update"); return false; } } $link = $this->getLink($arr); } else { $link = $arr; } if (empty($link)) { do_log("$log, no link..."); return false; } $ptGenInfo = []; foreach (self::$validSites as $site => $siteConfig) { if (!preg_match($siteConfig['url_pattern'], $link, $matches)) { continue; } try { $response = $this->generate($matches[0], true); $ptGenInfo[$site]['data'] = $response; } catch (\Exception $exception) { do_log("$log, site: $site can not be updated: " . $exception->getMessage(), 'error'); } } $siteIdAndRating = $this->listRatings($ptGenInfo, $torrent->url, $extra->descr); foreach ($siteIdAndRating as $key => $value) { $ptGenInfo[$key]['data']["__rating"] = $value; } $ptGenInfo['__link'] = $link; $ptGenInfo['__updated_at'] = $now->toDateTimeString(); TorrentExtra::query()->where('torrent_id', $id)->update(['pt_gen' => $ptGenInfo]); do_log("$log, success update"); return $ptGenInfo; } public function getLink(array $ptGenInfo) { if (isset($ptGenInfo['__link'])) { //new return $ptGenInfo['__link']; } $result = ''; foreach ($ptGenInfo as $item) { if (!empty($item['link'])) { //old, use the last one $result = $item['link']; } } return $result; } private function getRawPTGenRating(array $ptGenInfo, $site) { $key = $site . "_rating_average"; if (isset($ptGenInfo[$key])) { return $ptGenInfo[$key]; } if ($site == self::SITE_INDIENOVA) { $parts = preg_split('/[\s:]+/', $ptGenInfo['rate']); return Arr::last($parts); } return ''; } }