From: Banana Date: Sun, 30 Aug 2026 13:56:56 +0000 (+0200) Subject: remove imdb scraper and replace it with metacritic for movies for now X-Git-Tag: 1.9.0~2 X-Git-Url: http://91.132.146.200/gitweb/?a=commitdiff_plain;h=7126d517131b4971b9731eadeb0ae0638cf8acf1;p=bibliotheca-php.git remove imdb scraper and replace it with metacritic for movies for now Signed-off-by: Banana --- diff --git a/CHANGELOG b/CHANGELOG index 3caf258..331303c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ -x.x.x - Terraniux Underground +1.9.0 - Terraniux Underground 2026-08-30 + * Config Update: See upgrade/from-version-1.8.1.md + * DB Update (non breaking): See upgrade/from-version-1.8.1.md + * Removed IMDB web parser since IMDB does not like cli tools anymore. + * Added: Metacritic as a replacement for IMDB. See upgrade/from-version-1.8.1.md 1.8.1 - Harobed Village 2026-01-06 * Config Update: See upgrade/from-version-1.8.md diff --git a/documentation/tool-imdbweb.md b/documentation/tool-imdbweb.md deleted file mode 100644 index 0b8358a..0000000 --- a/documentation/tool-imdbweb.md +++ /dev/null @@ -1,19 +0,0 @@ -# IMDB web parser tool - -It provides easy searching for a movie entry within https://www.imdb.com. -Creating a new one with the selected fields or updating an existing one. - -You can search the exact imdb id, which is in tt* format of an entry url. -It searches for movies only. - -Which fields are available for you to select, are configured in the config.imdbweb.php file to make it -easier since not every field the tool provides are needed. -Follow the comments in the file for more details. - -An option to make the target fields automatically selected by default to given values is also available -in the config file. - -# Setup - -Rename the `config-imdbweb.php.default` to `config-imdbweb.php` in the config folder. -Follow the comments to update the settings. Add the tool to your collection. diff --git a/documentation/tool-metacritic.md b/documentation/tool-metacritic.md new file mode 100644 index 0000000..2779a3b --- /dev/null +++ b/documentation/tool-metacritic.md @@ -0,0 +1,18 @@ +# Metacritic web parser tool + +Since IMDB does not like terminal clients, Metacritic it is for now. + +There is no search for now. +Search manuall and then copy the part after `movie/` from the URL and use this for the data fetch. + +Which fields are available for you to select, are configured in the `config.metacritic.php` file to make it +easier since not every field the tool provides are needed. +Follow the comments in the file for more details. + +An option to make the target fields automatically selected by default to given values is also available +in the config file. + +# Setup + +Rename the `config-metacritic.php.default` to `config-metacritic.php` in the config folder. +Follow the comments to update the settings. Add the tool to your collection. diff --git a/upgrade/from-version-1.8.1.md b/upgrade/from-version-1.8.1.md new file mode 100644 index 0000000..f913205 --- /dev/null +++ b/upgrade/from-version-1.8.1.md @@ -0,0 +1,37 @@ +# Config change + +Updated `BROWSER_AGENT` string. See default config file for its new value. + +# DB changes + +Run each line against your bibliotheca DB. + +Replace `#REPLACEME#` with your table prefix. Default is bib + +``` +UPDATE `#REPLACEME#_tool` SET `name` = 'Metacritic Movie', `action` = 'metacriticMovie' WHERE `bib_tool`.`id` = 1; +``` + +``` +INSERT INTO `#REPLACEME#_sys_fields` (`id`, `identifier`, `displayname`, `type`, `searchtype`, `createstring`, `inputValidation`, `value`, `apiinfo`, `created`, `modified`, `modificationuser`, `owner`, `group`, `rights`) VALUES (NULL, 'metacritic', 'sysfield.metacritic', 'text', 'entrySingleText', '`metacritic` varchar(4) NULL DEFAULT NULL', '', '', 'string 4', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, '1', '1', 'rw-r--r--'); +``` + +# Remove not needed folders and files + +The following files and direcotries should be deleted. + +``` +config/config-imdbweb.php +config/config-imdbweb.php.default +lib/imdbweb.class.php +systemout/imdb +view/default/tool-imdbweb.html +view/default/tool-imdbweb.php +view/98/tool-imdbweb.html +view/98/tool-imdbweb.php +``` + +# Tool changes + +Since IMDB scraping is not working anymore, Metracritic does it for now. +It replaces IMDB and needs only the initial setup. See the tool documentation for that. diff --git a/upgrade/from-version-1.8.md b/upgrade/from-version-1.8.md index f774337..3d6d954 100644 --- a/upgrade/from-version-1.8.md +++ b/upgrade/from-version-1.8.md @@ -6,7 +6,7 @@ Updated BROWSER_AGENT string. See default config file for its new value. Run each line against your bibliotheca DB. -Replace #REPLACEME# with your table prefix. Default is bib +Replace `#REPLACEME#` with your table prefix. Default is bib ``` UPDATE `#REPLACEME#_sys_fields` SET `createstring` = '`imdbrating` varchar(4) NULL DEFAULT NULL', `apiinfo` = 'string 4' WHERE `#REPLACEME#_sys_fields`.`identifier` = 'imdbrating'; diff --git a/webclient/config/config-imdbweb.php.default b/webclient/config/config-imdbweb.php.default deleted file mode 100644 index 1692bda..0000000 --- a/webclient/config/config-imdbweb.php.default +++ /dev/null @@ -1,50 +0,0 @@ - bibFieldName - * TOOL_IMDBWEB_BROWSERSTRING a current browser agent string. Should be updated from time to time. See default config file. - * TOOL_IMDBWEB_BROWSER_ACCEPT_LANG should define in which language the content returns - */ -const TOOL_IMDBWEB_ATTRIBUTES = array( - 'runtime', 'rating', 'genres', 'title', 'originalTitle', 'year', 'plot', 'synopses', 'directors', - 'writers', 'cast', 'coverImage' -); -const TOOL_IMDBWEB_FIELDS_TO = array( - 'cast' => 'actors', 'synopses' => 'content', 'directors' => 'directors', 'genres' => 'genres', - 'plot' => 'description', 'rating' => 'imdbrating', 'runtime' => 'runtime', 'originalTitle' => 'title', - 'writers' => 'writers', 'year' => 'year', 'title' => 'localizedTitle', 'coverImage' => 'coverimage' -); - -// reuse existing constants but define it to the tool -const TOOL_IMDBWEB_BROWSER_AGENT = BROWSER_AGENT; -const TOOL_IMDBWEB_BROWSER_ACCEPT = BROWSER_ACCEPT; -const TOOL_IMDBWEB_BROWSER_ACCEPT_LANG = BROWSER_ACCEPT_LANG; -const TOOL_IMDBWEB_CACHE_STORAGE = PATH_SYSTEMOUT.'/imdb'; diff --git a/webclient/config/config-metacritic.php.default b/webclient/config/config-metacritic.php.default new file mode 100644 index 0000000..9ee3c6d --- /dev/null +++ b/webclient/config/config-metacritic.php.default @@ -0,0 +1,50 @@ + bibFieldName + * TOOL_METACRITIC_BROWSER_AGENT A current browser agent string. See default config file. + * TOOL_METACRITIC_BROWSER_ACCEPT Browser accept request string. See default config file. + * TOOL_METACRITIC_BROWSER_ACCEPT_LANG Defines in which language the content returns + * TOOL_METACRITIC_CACHE_STORAGE Cache storage path + */ +const TOOL_METACRITIC_ATTRIBUTES_MOVIE = array( + 'description', 'datePublished', 'ratingValue', 'genre', 'name', 'actor', 'duration', 'director' +); +const TOOL_METACRITIC_FIELDS_TO_MOVIE = array( + 'actor' => 'actors', 'director' => 'directors', 'genre' => 'genres', + 'description' => 'description', 'ratingValue' => 'metacritic', 'duration' => 'runtime', + 'name' => 'title', 'datePublished' => 'year' +); + +// reuse existing constants but define it to the tool +const TOOL_METACRITIC_BROWSER_AGENT = BROWSER_AGENT; +const TOOL_METACRITIC_BROWSER_ACCEPT = BROWSER_ACCEPT; +const TOOL_METACRITIC_BROWSER_ACCEPT_LANG = BROWSER_ACCEPT_LANG; +const TOOL_METACRITIC_CACHE_STORAGE = PATH_SYSTEMOUT.'/metacritic'; diff --git a/webclient/config/config.php.default b/webclient/config/config.php.default index 81c20cf..c7d9b5c 100644 --- a/webclient/config/config.php.default +++ b/webclient/config/config.php.default @@ -75,6 +75,6 @@ const ANON_GROUP_ID = '3'; const RESULTS_PER_PAGE = 24; # CURL browser settings -const BROWSER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:146.0) Gecko/20100101 Firefox/146.0'; +const BROWSER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:154.0) Gecko/20100101 Firefox/154.0'; const BROWSER_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'; const BROWSER_ACCEPT_LANG = 'en-US,en;q=0.5'; diff --git a/webclient/i18n/deu.ini b/webclient/i18n/deu.ini index 784ad36..873960e 100644 --- a/webclient/i18n/deu.ini +++ b/webclient/i18n/deu.ini @@ -258,6 +258,7 @@ sysfield.imdbrating = "IMDB Wertung" sysfield.isbn = "ISBN" sysfield.languages = "Sprachen" sysfield.localizedTitle = "Lokalisierter Titel" +sysfield.metacritic = "Metascore" sysfield.missingtemplate = "Fehlende Feldvorlage:" sysfield.modified = "Bearbeitet" sysfield.os = "Betriebssystem und Version" @@ -276,23 +277,26 @@ sysfield.year = "Jahr" tags.headline.tagsfor = "Tags für:" +tool.headline.using = "Verwendung von %s in Sammlung: %s" +tool.limitations = "Einschränkungen" +tool.limitations.override = "Daten werden überschrieben." +tool.message.missingFiles = "Benötige Tooldateien können nicht gefunden werden." + tool.gameinfo = "Dies ist eine Sammlung von Webseiten, die unterschiedliche Informationen über Spiele anbieten." tool.googlebooks.save = reuse.global.save tool.googlebooks.search = reuse.global.search tool.googlebooks.searchIsbn = "ISBN Suche" tool.googlebooks.select = reuse.global.none -tool.headline.using = "Verwendung von %s in Sammlung: %s" -tool.imdb.fetch = "Datenbezug" -tool.imdb.imdbid = "IMDB ID" -tool.imdb.posters = "Film Poster" -tool.imdb.save = reuse.global.save -tool.imdb.search = reuse.global.search -tool.imdb.search.hint = "Suche funktioniert mit der IMDB Film ID. Auf IMDB manuell suchen und davon die ID (tt*) aus der URL nehmen." -tool.imdb.search.missingid = "IMDB Suchinformation fehlen." -tool.imdb.select.none = reuse.global.none -tool.limitations = "Einschränkungen" -tool.limitations.override = "Daten werden überschrieben." -tool.message.missingFiles = "Benötige Tooldateien können nicht gefunden werden." + +tool.metacritic.fetch = "Datenbezug" +tool.metacritic.id = "URL Teil" +tool.metacritic.posters = "Film Poster" +tool.metacritic.save = reuse.global.save +tool.metacritic.search = reuse.global.search +tool.metacritic.search.hint = "Suche funktioniert mit dem Teil der URL nach /movie/. Auf Metacritic manuell suchen und dann diesen Teils aus der URL nehmen." +tool.metacritic.search.missingid = "Metacritic Suchinformation fehlen." +tool.metacritic.select.none = reuse.global.none + tool.music.input.albumTitle = "Album/Titel" tool.music.input.artist = "Künstler" tool.music.limitations.override = "Daten werden überschrieben; keine Platzhaltersuche oder Teilwörter." diff --git a/webclient/i18n/eng.ini b/webclient/i18n/eng.ini index 1e43e49..9bc4aa8 100644 --- a/webclient/i18n/eng.ini +++ b/webclient/i18n/eng.ini @@ -258,6 +258,7 @@ sysfield.imdbrating = "IMDB rating" sysfield.isbn = "ISBN" sysfield.languages = "Languages" sysfield.localizedTitle = "Localized title" +sysfield.metacritic = "Metascore" sysfield.missingtemplate = "Missing template for:" sysfield.modified = "Modified" sysfield.os = "Operating system and version" @@ -276,23 +277,26 @@ sysfield.year = "Year" tags.headline.tagsfor = "Tags for:" +tool.headline.using = "Using %s with collection: %s" +tool.limitations = "Limitations" +tool.limitations.override = "Data will be overwritten" +tool.message.missingFiles = "Required tool files can not be found." + tool.gameinfo = "This is a collection of websites which provide different kind of information about games." tool.googlebooks.save = reuse.global.save tool.googlebooks.search = reuse.global.search tool.googlebooks.searchIsbn = "Search ISBN" tool.googlebooks.select = reuse.global.none -tool.headline.using = "Using %s with collection: %s" -tool.imdb.fetch = "Fetch" -tool.imdb.imdbid = "IMDB ID" -tool.imdb.posters = "Movie posters" -tool.imdb.save = reuse.global.save -tool.imdb.search = reuse.global.search -tool.imdb.search.hint = "Seach works with the imdb movie ID (the tt* in the url) after searching manually on imdb itself." -tool.imdb.search.missingid = "IMDB search result information missing." -tool.imdb.select.none = reuse.global.none -tool.limitations = "Limitations" -tool.limitations.override = "Data will be overwritten" -tool.message.missingFiles = "Required tool files can not be found." + +tool.metacritic.fetch = "Fetch" +tool.metacritic.id = "Url part" +tool.metacritic.posters = "Movie posters" +tool.metacritic.save = reuse.global.save +tool.metacritic.search = reuse.global.search +tool.metacritic.search.hint = "Seach works with the url part from Metacritic URL after /movie/." +tool.metacritic.search.missingid = "Metacritic search result information missing." +tool.metacritic.select.none = reuse.global.none + tool.music.input.albumTitle = "Album/Title" tool.music.input.artist = "Artist" tool.music.limitations.override = "Data will be overwritten; No wildcardsearch or partial words." diff --git a/webclient/lib/imdbweb.class.php b/webclient/lib/imdbweb.class.php deleted file mode 100644 index 71b8700..0000000 --- a/webclient/lib/imdbweb.class.php +++ /dev/null @@ -1,413 +0,0 @@ -_DEBUG = true; - } - - $this->_CACHE_DIR = $options['storage']; - if (!is_writable($this->_CACHE_DIR) && !mkdir($this->_CACHE_DIR)) { - Summoner::sysLog("[ERROR] Missing directory or write access: ".$this->_CACHE_DIR); - } - - $this->_BROWSER_AGENT = $options['browserAgent']; - $this->_BROWSER_LANG = $options['browserLang']; - $this->_BROWSER_ACCEPT = $options['browserAccept']; - - $this->_attributes = array(); - if (isset($options['attributes']) && !empty($options['attributes'])) { - $this->_attributes = $options['attributes']; - } - } - - /** - * Not really a search. Since this does only really work with a given imdb ID an exact - * URL is called and processed. - * Decides if the data will called of cache is used. - * Does return the data based on $this->_attributes - * - * @param string $search - * @return array - */ - public function search(string $search): array { - $ret = array(); - - if (empty($search)) return $ret; - $url = sprintf($this->_FULLCREDITS_URL, $search); - if ($this->_DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." using url : " . $url); - - // Does a cache of this movie exist? - $_cacheFile = $this->_CACHE_DIR.'/'.md5($url).'.cache'; - $_htmlData = ""; - if (is_readable($_cacheFile)) { - $_timeDiff = round(abs(time() - filemtime($_cacheFile)) / 60); - if (($_timeDiff < $this->_CACHE_TIME) && !$this->_DEBUG) { - $_htmlData = file_get_contents($_cacheFile); - } else { - $_htmlData = $this->_curlCall($url); - if (!empty($_htmlData)) { - file_put_contents($_cacheFile, $_htmlData); - } - } - } else { - $_htmlData = $this->_curlCall($url); - if (!empty($_htmlData)) { - file_put_contents($_cacheFile, $_htmlData); - } - } - - if(!empty($_htmlData)) { - $jsonString = $this->_extractData($_htmlData); - if(!empty($jsonString)) { - $ret = $this->_processData($jsonString); - } - } - - return $ret; - } - - /** - * Retrieve the title cover from the given URL and return a tmp file - * - * @param string $url - * @return string - */ - public function downloadCover(string $url): string { - $ret = ""; - - $_tmpFile = tempnam(sys_get_temp_dir(), "bibliotheca-"); - $fh = fopen($_tmpFile,"w+"); - if($this->_DEBUG) { - Summoner::sysLog('[DEBUG] '.__METHOD__.' url '.Summoner::cleanForLog($url)); - } - - if($fh !== false) { - - // modified curl call for fetching an image - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_FILE, $fh); - - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_MAXREDIRS, 3); - curl_setopt($ch, CURLOPT_USERAGENT, $this->_BROWSER_AGENT); - - curl_exec($ch); - curl_close($ch); - - $ret = $_tmpFile; - } - fclose($fh); - - return $ret; - } - - /** - * The html from $this->_REFERENCE_URL should contain a script with the id __NEXT_DATA__ - * and it holds a json object with all the information we currently need. - * - * @param string $data - * @return string - */ - private function _extractData(string $data): string { - $ret = ""; - - $m = preg_match('~id="__NEXT_DATA__".*?>([^<]+)~s', $data, $matches); - if(!empty($m)) { - $ret = $matches[1]; - } elseif($this->_DEBUG) { - Summoner::sysLog("[DEBUG] ".__METHOD__." no script with id __NEXT_DATA__ found."); - } - //Summoner::sysLog("[DEBUG] ".__METHOD__." extracted json data.".Summoner::cleanForLog($ret)); - return $ret; - } - - /** - * Take the given jsonString and extract the wanted information defined by $this->_attributes - * - * @param string $jsonString - * @return array - */ - private function _processData(string $jsonString): array { - $ret = array(); - - $jsonData = json_decode($jsonString, true); - if(!empty(json_last_error())) { - Summoner::sysLog("[DEBUG] ".__METHOD__." can not decode json. ".json_last_error_msg()); - return $ret; - } - if(isset($jsonData["props"]["pageProps"]["contentData"]["data"]["title"])) { - foreach($this->_attributes as $att) { - $_m = "_get_".$att; - if(method_exists($this, $_m)) { - $ret[$att] = $this->$_m($jsonData["props"]["pageProps"]["contentData"]["data"]["title"]); - } else { - $ret[$att] = ""; - } - } - } - - return $ret; - } - - private function _get_runtime(array $data): string { - $t = $this->_retrieveDataFromArray(array("runtime","seconds"),$data); - return floor($t / 60); - } - - private function _get_rating(array $data): string { - return $this->_retrieveDataFromArray(array("ratingsSummary","aggregateRating"), $data); - } - - private function _get_title(array $data): string { - return $this->_retrieveDataFromArray(array("titleText","text"), $data); - } - - private function _get_originalTitle(array $data): string { - return $this->_retrieveDataFromArray(array("originalTitleText","text"), $data); - } - - private function _get_year(array $data): string { - return $this->_retrieveDataFromArray(array("releaseYear","year"), $data); - } - - private function _get_plot(array $data): string { - return $this->_retrieveDataFromArray(array("plot","plotText","plainText"),$data); - } - - private function _get_synopses(array $data): string { - return $this->_retrieveDataFromArray(array("synopses","edges",0,"node","plotText","plaidHtml"), $data); - } - - private function _get_coverImage(array $data): string { - return $this->_retrieveDataFromArray(array("primaryImage","url"), $data); - } - - private function _get_genres(array $data): array { - $ret = array(); - foreach($data["titleGenres"]["genres"] as $g) { - $ret[] = $g["genre"]["text"]; - } - return $ret; - } - - /** - * Looks like there is a unique id for director(s) - * amzn1.imdb.concept.name_credit_category.ace5cb4c-8708-4238-9542-04641e7c8171 - * - * @param array $data - * @return array - */ - private function _get_directors(array $data): array { - return $this->_resolveCreditGroupings($data, "amzn1.imdb.concept.name_credit_category.ace5cb4c-8708-4238-9542-04641e7c8171"); - } - - /** - * Looks like there is a unique id for writer(s) - * amzn1.imdb.concept.name_credit_category.c84ecaff-add5-4f2e-81db-102a41881fe3 - * - * @param array $data - * @return array - */ - private function _get_writers(array $data): array { - return $this->_resolveCreditGroupings($data, "amzn1.imdb.concept.name_credit_category.c84ecaff-add5-4f2e-81db-102a41881fe3"); - } - - /** - * Looks like there is a unique id for the cast - * amzn1.imdb.concept.name_credit_group.7caf7d16-5db9-4f4f-8864-d4c6e711c686 - * - * @param array $data - * @return array - */ - private function _get_cast(array $data): array { - return $this->_resolveCreditGroupings($data, "amzn1.imdb.concept.name_credit_group.7caf7d16-5db9-4f4f-8864-d4c6e711c686"); - } - - /** - * Extract the information from the creditGroupings array. - * - * @param array $data - * @param string $gId - * @return array - */ - private function _resolveCreditGroupings(array $data, string $gId): array { - $ret = array(); - foreach($data["creditGroupings"]["edges"] as $edge) { - $_gid = $this->_retrieveDataFromArray(array("node", "grouping", "groupingId"), $edge); - if($_gid == $gId) { - foreach($edge["node"]["credits"]["edges"] as $e) { - $ret[] = $this->_retrieveDataFromArray(array("node", "name", "nameText", "text"), $e); - } - break; - } - } - return $ret; - } - - /** - * execute a curl call to the given $url - * - * @param string $url The request url - * @return string - */ - private function _curlCall(string $url): string { - $ret = ''; - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_MAXREDIRS, 2); - curl_setopt($ch, CURLOPT_USERAGENT, $this->_BROWSER_AGENT); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - 'Accept: ' . $this->_BROWSER_ACCEPT, - 'Accept-Charset: utf-8, iso-8859-1;q=0.5', - 'Accept-Language: ' . $this->_BROWSER_LANG) - ); - curl_setopt($ch, CURLOPT_REFERER, 'https://www.imdb.com'); - - if ($this->_DEBUG) { - $_headers = array(); - curl_setopt($ch, CURLOPT_VERBOSE, true); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, - function ($curl, $header) use (&$_headers) { - $len = strlen($header); - $header = explode(':', $header, 2); - if (count($header) < 2) { // ignore invalid headers - return $len; - } - $_headers[strtolower(trim($header[0]))][] = trim($header[1]); - return $len; - } - ); - } - - $do = curl_exec($ch); - if (!curl_errno($ch)) { - $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if ($http_code == 200) { - if (is_string($do) === true) { - $ret = $do; - } - } - } - curl_close($ch); - - if ($this->_DEBUG) { - Summoner::sysLog('[DEBUG] ' . __METHOD__ . ' headers ' . Summoner::cleanForLog($_headers)); - } - - return $ret; - } - - /** - * Accessing a nested array can fail if a nested key is not present. - * Checks every given key, nested, for availablity - * - * @param array $toGet - * @param array $data - * @return mixed - */ - private function _retrieveDataFromArray(array $toGet, array $data): mixed { - $ret = ""; - - foreach($toGet as $key) { - if(isset($data[$key])) { - $data = $data[$key]; - $ret = $data; - } else { - $ret = ""; // reset otherwise the last found will be returned - } - } - - return $ret; - } -} diff --git a/webclient/lib/metacritic.class.php b/webclient/lib/metacritic.class.php new file mode 100644 index 0000000..dcc18e1 --- /dev/null +++ b/webclient/lib/metacritic.class.php @@ -0,0 +1,318 @@ +_DEBUG = true; + } + + $this->_CACHE_DIR = $options['storage']; + if (!is_writable($this->_CACHE_DIR) && !mkdir($this->_CACHE_DIR)) { + Summoner::sysLog("[ERROR] Missing directory or write access: ".$this->_CACHE_DIR); + } + + $this->_BROWSER_AGENT = $options['browserAgent']; + $this->_BROWSER_LANG = $options['browserLang']; + $this->_BROWSER_ACCEPT = $options['browserAccept']; + + $this->_attributes = array(); + if (isset($options['attributes']) && !empty($options['attributes'])) { + $this->_attributes = $options['attributes']; + } + } + + public function fetch(string $urlPart): array { + $ret = array(); + + if (empty($urlPart)) return $ret; + $url = sprintf($this->_BASE_URL_MOVIES, $urlPart); + if ($this->_DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." using url : " . $url); + + // Does a cache of this movie exist? + $_cacheFile = $this->_CACHE_DIR.'/'.md5($url).'.cache'; + $_htmlData = ""; + if (is_readable($_cacheFile)) { + $_timeDiff = round(abs(time() - filemtime($_cacheFile)) / 60); + if (($_timeDiff < $this->_CACHE_TIME) && !$this->_DEBUG) { + $_htmlData = file_get_contents($_cacheFile); + } else { + $_htmlData = $this->_curlCall($url); + if (!empty($_htmlData)) { + file_put_contents($_cacheFile, $_htmlData); + } + } + } else { + $_htmlData = $this->_curlCall($url); + if (!empty($_htmlData)) { + file_put_contents($_cacheFile, $_htmlData); + } + } + + if(!empty($_htmlData)) { + $jsonString = $this->_extractData($_htmlData); + if(!empty($jsonString)) { + $ret = $this->_processData($jsonString); + } + } + + return $ret; + } + + /** + * execute a curl call to the given $url + * + * @param string $url The request url + * @return string + */ + private function _curlCall(string $url): string { + $ret = ''; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 2); + curl_setopt($ch, CURLOPT_USERAGENT, $this->_BROWSER_AGENT); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Accept: ' . $this->_BROWSER_ACCEPT, + 'Accept-Charset: utf-8, iso-8859-1;q=0.5', + 'Accept-Language: ' . $this->_BROWSER_LANG) + ); + curl_setopt($ch, CURLOPT_REFERER, 'https://www.metacritic.com'); + + if ($this->_DEBUG) { + $_headers = array(); + curl_setopt($ch, CURLOPT_VERBOSE, true); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, + function ($curl, $header) use (&$_headers) { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) { // ignore invalid headers + return $len; + } + $_headers[strtolower(trim($header[0]))][] = trim($header[1]); + return $len; + } + ); + } + + $do = curl_exec($ch); + if (!curl_errno($ch)) { + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($http_code == 200) { + if (is_string($do) === true) { + $ret = $do; + } + } else { + Summoner::sysLog('[WARN] ' . __METHOD__ . ' non 200 return status ' . Summoner::cleanForLog($http_code)); + } + } + curl_close($ch); + + if ($this->_DEBUG) { + Summoner::sysLog('[DEBUG] ' . __METHOD__ . ' headers ' . Summoner::cleanForLog($_headers)); + } + + return $ret; + } + + /** + * The html from the curl call should contain application/ld+json + * and it holds a json object with all the information we currently need. + * + * @param string $data + * @return string + */ + private function _extractData(string $data): string { + $ret = ""; + + $m = preg_match('~type="application/ld\+json".*?>([^<]+)~s', $data, $matches); + if(!empty($m)) { + $ret = $matches[1]; + } elseif($this->_DEBUG) { + Summoner::sysLog("[DEBUG] ".__METHOD__." no script with type application/ld+json found."); + } + Summoner::sysLog("[DEBUG] ".__METHOD__." extracted json data.".Summoner::cleanForLog($ret)); + return $ret; + } + + /** + * Take the given jsonString and extract the wanted information defined by $this->_attributes + * + * @param string $jsonString + * @return array + */ + private function _processData(string $jsonString): array { + $ret = array(); + + $jsonData = json_decode($jsonString, true); + if(!empty(json_last_error())) { + Summoner::sysLog("[DEBUG] ".__METHOD__." can not decode json. ".json_last_error_msg()); + return $ret; + } + + if(isset($jsonData["name"])) { + foreach($this->_attributes as $att) { + $_m = "_get_".$att; + if(method_exists($this, $_m)) { + $ret[$att] = $this->$_m($jsonData); + } elseif (isset($jsonData[$att])) { + $ret[$att] = $jsonData[$att]; + } else { + $ret[$att] = ""; + } + } + } + + return $ret; + } + + private function _get_ratingValue(array $data): string { + $t = $this->_retrieveDataFromArray(array("aggregateRating","ratingValue"),$data); + return $t; + } + + private function _get_genre(array $data): array { + $ret = array(); + foreach($data['genre'] as $g) { + $ret[] = $g; + } + return $ret; + } + + private function _get_actor(array $data): array { + $ret = array(); + foreach($data['actor'] as $e) { + $ret[] = $e['name']; + } + return $ret; + } + + private function _get_director(array $data): array { + $ret = array(); + foreach($data['director'] as $e) { + $ret[] = $e['name']; + } + return $ret; + } + + private function _get_datePublished(array $data): string { + $ret = ''; + if(isset($data['datePublished'])) { + $time = strtotime($data['datePublished']); + $ret = date('Y', $time); + } + return $ret; + } + + + + /** + * The duration something like this: PT2M11S + * It is https://iso8601.com/#duration but wrong? + * The above example is from a movie which is 2h and 11m + * and NOT 2m 11s + * + * @param array $data + * @return array + */ + private function _get_duration(array $data): string { + $ret = ''; + if(isset($data['duration'])) { + try { + $interval = new DateInterval($data['duration']); + $ret = $interval->format("%i")*60; + $ret = $ret+$interval->format("%s"); // not very cool... + } catch (Exception $e) { + Summoner::sysLog("[DEBUG] ".__METHOD__." can not duration ".$data['duration']); + } + } + return $ret; + } + + /** + * Accessing a nested array can fail if a nested key is not present. + * Checks every given key, nested, for availablity + * + * @param array $toGet + * @param array $data + * @return mixed + */ + private function _retrieveDataFromArray(array $toGet, array $data): mixed { + $ret = ""; + + foreach($toGet as $key) { + if(isset($data[$key])) { + $data = $data[$key]; + $ret = $data; + } else { + $ret = ""; // reset otherwise the last found will be returned + } + } + + return $ret; + } +} diff --git a/webclient/view/98/tool/tool-imdbweb.html b/webclient/view/98/tool/tool-imdbweb.html deleted file mode 100644 index 4f9876d..0000000 --- a/webclient/view/98/tool/tool-imdbweb.html +++ /dev/null @@ -1,53 +0,0 @@ -

t('tool.limitations'); ?>

-

t('tool.limitations.override'); ?>

-

- t('tool.imdb.search.hint'); ?> - -

-

- t('tool.imdb.posters'); ?> impawards - -

-
- - -
-
- -
-
- - -
- - $v) { - ?> - -
-

-
- -

- -
- - - -
- -
- -
- diff --git a/webclient/view/98/tool/tool-metacriticMovie.html b/webclient/view/98/tool/tool-metacriticMovie.html new file mode 100644 index 0000000..e944bc1 --- /dev/null +++ b/webclient/view/98/tool/tool-metacriticMovie.html @@ -0,0 +1,53 @@ +

t('tool.limitations'); ?>

+

t('tool.limitations.override'); ?>

+

+ t('tool.metacritic.search.hint'); ?> + +

+

+ t('tool.metacritic.posters'); ?> impawards +

+ +
+
+ + +
+
+ +
+
+ + +
+ + $v) { + ?> + +
+

+
+ +

+ +
+ + + +
+ +
+ +
+ diff --git a/webclient/view/default/tool/tool-imdbweb.html b/webclient/view/default/tool/tool-imdbweb.html deleted file mode 100644 index 585bf47..0000000 --- a/webclient/view/default/tool/tool-imdbweb.html +++ /dev/null @@ -1,62 +0,0 @@ -

t('tool.limitations'); ?>

-

t('tool.limitations.override'); ?>

-

- t('tool.imdb.search.hint'); ?> - -

-

- t('tool.imdb.posters'); ?> impawards - -

-
- -
- -
-
- -
-
-
- - -
- - $v) { - ?> - -
-

-
- -

-
-
- -
- - - -
- -
- -
- diff --git a/webclient/view/default/tool/tool-imdbweb.php b/webclient/view/default/tool/tool-imdbweb.php deleted file mode 100644 index e920711..0000000 --- a/webclient/view/default/tool/tool-imdbweb.php +++ /dev/null @@ -1,223 +0,0 @@ - TOOL_IMDBWEB_SEARCH, - 'showFields' => TOOL_IMDBWEB_FIELDS, - 'storage' => PATH_SYSTEMOUT, - 'browserAgent' => TOOL_IMDBWEB_BROWSER_AGENT, - 'browserLang' => TOOL_IMDBWEB_BROWSER_ACCEPT_LANG, - 'browserAccept' => TOOL_IMDBWEB_BROWSER_ACCEPT, - 'debug' => false -)); -*/ - -require_once 'lib/imdbweb.class.php'; -if(file_exists(PATH_ABSOLUTE.'/config/config-imdbweb.php')) { - require_once 'config/config-imdbweb.php'; -} -$IMDB = new IMDBWEB(array( - 'storage' => TOOL_IMDBWEB_CACHE_STORAGE, - 'browserAgent' => TOOL_IMDBWEB_BROWSER_AGENT, - 'browserLang' => TOOL_IMDBWEB_BROWSER_ACCEPT_LANG, - 'browserAccept' => TOOL_IMDBWEB_BROWSER_ACCEPT, - 'attributes' => TOOL_IMDBWEB_ATTRIBUTES, - 'debug' => DEBUG -)); - - -$TemplateData['movieData'] = array(); -$TemplateData['saveToSelection'] = ''; -$TemplateData['showMatchingForm'] = false; - -// prepare fields to save into selection -// create one time and then reuse it -$collectionFields = $ManangeCollectionsFields->getExistingFields(false, true); -if(!empty($collectionFields)) { - foreach ($collectionFields as $k=>$v) { - $TemplateData['saveToSelection'] .= "\n"; - } -} - -if(isset($_POST['submitFormSearch'])) { - $fdata = $_POST['fdata']; - if (!empty($fdata)) { - // this is the imdb id - $search = trim($fdata['search']); - $search = Summoner::validate($search, "nospace") ? $search : false; - - if (!empty($search)) { - $mData = $IMDB->search($search); - - if (!empty($mData)) { - $TemplateData['movieData'] = $mData; - $TemplateData['movieImdbId'] = $search; - $TemplateData['showMatchingForm'] = true; - } else { - $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound'); - $TemplateData['message']['status'] = "error"; - } - } - else { - $TemplateData['message']['content'] = $I18n->t('global.message.invalidSearchTerm'); - $TemplateData['message']['status'] = "error"; - } - } -} - -if(isset($_POST['submitFormSave'])) { - $fdata = $_POST['fdata']; - if (!empty($fdata)) { - $_imdbId = $fdata['imdbId']; - $_imdbId = Summoner::validate($_imdbId,'nospace') ? $_imdbId : false; - - if(!empty($_imdbId)) { - // why search again? - // Cache is used, so not really a new search and the data stays the same without a roundtrip - // thriugh the requsts - $mData = $IMDB->search($_imdbId); - - if (!empty($mData)) { - $TemplateData['movieImdbId'] = $_imdbId; - - // build data array based on submit - // see creation log for structure - $_data = array(); - foreach($fdata['into'] as $k=>$v) { - if(!empty($v)) { - $_t = $mData[$k]; - - // multiple selections format for field type lookup_multiple - if(is_array($_t)) { - $_t = implode(",", $_t); - } - - if(isset($collectionFields[$v])) { - $_data[$v] = $collectionFields[$v]; - $_data[$v]['valueToSave'] = $_t; - - // special case for cover image since there is no POST and FILES - if($k == "coverImage") { - $fieldData = array(); - - $_f = $IMDB->downloadCover($_t); - if($_f && is_file($_f)) { - $_e = UPLOAD_ERR_OK; - // build _FILES based on regular add form - $fieldData['name'][$_data[$v]['identifier']] = 'cover.jpg'; - $fieldData['type'][$_data[$v]['identifier']] = mime_content_type($_f); - $fieldData['size'][$_data[$v]['identifier']] = filesize($_f); - $fieldData['tmp_name'][$_data[$v]['identifier']] = $_f; - $fieldData['error'][$_data[$v]['identifier']] = UPLOAD_ERR_OK; - $fieldData['rebuildUpload'][$_data[$v]['identifier']] = true; - } - - $_data[$v]['uploadData'] = $fieldData; - } - } - } - } - - $_r = $Tools->getDefaultCreationInfo(); - if(!empty($TemplateData['editEntry'])) { - // update existing one - $do = $Manageentry->create($_data, - $_r['id'], - $_r['group'], - $_r['rights'], - $TemplateData['editEntry']['id'] - ); - $TemplateData['message']['content'] = $I18n->t('global.message.dataSaved'); - } - else { - // create into loaded collection - $do = $Manageentry->create($_data, - $_r['id'], - $_r['group'], - $_r['rights'] - ); - $TemplateData['message']['content'] = $I18n->t('global.message.dataSaved')." ".$I18n->t('global.view').""; - } - - if(!empty($do)) { - $TemplateData['message']['status'] = "success"; - } - else { - $TemplateData['message']['content'] = $I18n->t('global.message.couldNotBeSaved'); - $TemplateData['message']['status'] = "error"; - } - } else { - $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound'); - $TemplateData['message']['status'] = "error"; - } - } - else { - $TemplateData['message']['content'] = $I18n->t('tool.imdb.search.missingid'); - $TemplateData['message']['status'] = "error"; - } - } -} - - -/** - * Helper function. Takes the prebuild options for the target selection field and search for a matching key. - * Since the optionString is prebuild, avoiding looping over and over again, the selection needs to be done - * by search and replace. - * Checks if TOOL_IMDBWEB_FIELDS_TO is defined and a matching key=>value pair is available - * - * @param string $optionString - * @param string $imdbKey - * @return string - */ -function toolMethod_GetTargetSelection(string $optionString, string $imdbKey): string { - if(defined('TOOL_IMDBWEB_FIELDS_TO') & !empty($imdbKey)) { - if(isset(TOOL_IMDBWEB_FIELDS_TO[$imdbKey])) { - $_k = "sel_".TOOL_IMDBWEB_FIELDS_TO[$imdbKey]; - $optionString = str_replace($_k,'selected="selected"',$optionString); - } - } - - return $optionString; -} - -/** - * Make the given mixed data displayable as a string - * - * @param mixed $value - * @return string - */ -function toolMethod_DisplayValues(mixed $value): string { - $ret = $value; - if(is_array($value)) { - $ret = implode(", ", $value); - } - return $ret; -} diff --git a/webclient/view/default/tool/tool-metacriticMovie.html b/webclient/view/default/tool/tool-metacriticMovie.html new file mode 100644 index 0000000..c58db14 --- /dev/null +++ b/webclient/view/default/tool/tool-metacriticMovie.html @@ -0,0 +1,62 @@ +

t('tool.limitations'); ?>

+

t('tool.limitations.override'); ?>

+

+ t('tool.metacritic.search.hint'); ?> + +

+

+ t('tool.metacritic.posters'); ?> impawards +

+ +
+
+ +
+ +
+
+ +
+
+
+ + +
+ + $v) { + ?> + +
+

+
+ +

+
+
+ +
+ + + +
+ +
+ +
+ diff --git a/webclient/view/default/tool/tool-metacriticMovie.php b/webclient/view/default/tool/tool-metacriticMovie.php new file mode 100644 index 0000000..393f7ca --- /dev/null +++ b/webclient/view/default/tool/tool-metacriticMovie.php @@ -0,0 +1,187 @@ + TOOL_METACRITIC_CACHE_STORAGE, + 'browserAgent' => TOOL_METACRITIC_BROWSER_AGENT, + 'browserLang' => TOOL_METACRITIC_BROWSER_ACCEPT_LANG, + 'browserAccept' => TOOL_METACRITIC_BROWSER_ACCEPT, + 'attributes' => TOOL_METACRITIC_ATTRIBUTES_MOVIE, + 'debug' => DEBUG, + 'type' => 'movie' +)); + +$TemplateData['movieData'] = array(); +$TemplateData['saveToSelection'] = ''; +$TemplateData['showMatchingForm'] = false; + +// prepare fields to save into selection +// create one time and then reuse it +$collectionFields = $ManangeCollectionsFields->getExistingFields(false, true); +if(!empty($collectionFields)) { + foreach ($collectionFields as $k=>$v) { + $TemplateData['saveToSelection'] .= "\n"; + } +} + +if(isset($_POST['submitFormSearch'])) { + $fdata = $_POST['fdata']; + if (!empty($fdata)) { + $search = trim($fdata['search']); + $search = Summoner::validate($search, "nospaceP") ? $search : false; + + if (!empty($search)) { + $mData = $METACRITIC->fetch($search); + + if (!empty($mData)) { + $TemplateData['movieData'] = $mData; + $TemplateData['urlpart'] = $search; + $TemplateData['showMatchingForm'] = true; + } else { + $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound'); + $TemplateData['message']['status'] = "error"; + } + } + else { + $TemplateData['message']['content'] = $I18n->t('global.message.invalidSearchTerm'); + $TemplateData['message']['status'] = "error"; + } + } +} + +if(isset($_POST['submitFormSave'])) { + $fdata = $_POST['fdata']; + if (!empty($fdata)) { + $_urlpart = $fdata['urlpart']; + $_urlpart = Summoner::validate($_urlpart,'nospaceP') ? $_urlpart : false; + + if(!empty($_urlpart)) { + // why search again? + // Cache is used, so not really a new search and the data stays the same without a roundtrip + // thriugh the requsts + $mData = $METACRITIC->fetch($_urlpart); + + if (!empty($mData)) { + $TemplateData['urlpart'] = $_urlpart; + + // build data array based on submit + // see creation log for structure + $_data = array(); + foreach($fdata['into'] as $k=>$v) { + if(!empty($v)) { + $_t = $mData[$k]; + + // multiple selections format for field type lookup_multiple + if(is_array($_t)) { + $_t = implode(",", $_t); + } + + if(isset($collectionFields[$v])) { + $_data[$v] = $collectionFields[$v]; + $_data[$v]['valueToSave'] = $_t; + } + } + } + + $_r = $Tools->getDefaultCreationInfo(); + if(!empty($TemplateData['editEntry'])) { + // update existing one + $do = $Manageentry->create($_data, + $_r['id'], + $_r['group'], + $_r['rights'], + $TemplateData['editEntry']['id'] + ); + $TemplateData['message']['content'] = $I18n->t('global.message.dataSaved'); + } + else { + // create into loaded collection + $do = $Manageentry->create($_data, + $_r['id'], + $_r['group'], + $_r['rights'] + ); + $TemplateData['message']['content'] = $I18n->t('global.message.dataSaved')." ".$I18n->t('global.view').""; + } + + if(!empty($do)) { + $TemplateData['message']['status'] = "success"; + } + else { + $TemplateData['message']['content'] = $I18n->t('global.message.couldNotBeSaved'); + $TemplateData['message']['status'] = "error"; + } + } else { + $TemplateData['message']['content'] = $I18n->t('global.message.nothingFound'); + $TemplateData['message']['status'] = "error"; + } + } + else { + $TemplateData['message']['content'] = $I18n->t('tool.imdb.search.missingid'); + $TemplateData['message']['status'] = "error"; + } + } +} + + +/** + * Helper function. Takes the prebuild options for the target selection field and search for a matching key. + * Since the optionString is prebuild, avoiding looping over and over again, the selection needs to be done + * by search and replace. + * Checks if TOOL_METACRITIC_FIELDS_TO_MOVIE is defined and a matching key=>value pair is available + * + * @param string $optionString + * @param string $key + * @return string + */ +function toolMethod_GetTargetSelection(string $optionString, string $key): string { + if(defined('TOOL_METACRITIC_FIELDS_TO_MOVIE') & !empty($key)) { + if(isset(TOOL_METACRITIC_FIELDS_TO_MOVIE[$key])) { + $_k = "sel_".TOOL_METACRITIC_FIELDS_TO_MOVIE[$key]; + $optionString = str_replace($_k,'selected="selected"',$optionString); + } + } + + return $optionString; +} + +/** + * Make the given mixed data displayable as a string + * + * @param mixed $value + * @return string + */ +function toolMethod_DisplayValues(mixed $value): string { + $ret = $value; + if(is_array($value)) { + $ret = implode(", ", $value); + } + return $ret; +}