diff --git a/bridges/BooruprojectBridge.php b/bridges/BooruprojectBridge.php index a37a25a6..9917da7e 100644 --- a/bridges/BooruprojectBridge.php +++ b/bridges/BooruprojectBridge.php @@ -1,5 +1,4 @@ queriedContext) { - case $this->i8n('context-keyword'): - return $this->collectDataKeywords(); - break; - case $this->i8n('context-group'): - return $this->collectDataGroup(); - break; - case $this->i8n('context-talk'): - return $this->collectDataTalk(); - break; - } - } - - /** - * Get the Deal data from the choosen group in the choosed order - */ - protected function collectDataGroup() - { - $url = $this->getGroupURI(); - $this->collectDeals($url); - } - - /** - * Get the Deal data from the choosen keywords and parameters - */ - protected function collectDataKeywords() - { - /* Even if the original website uses POST with the search page, GET works too */ - $url = $this->getSearchURI(); - $this->collectDeals($url); - } - - /** - * Get the Deal data using the given URL - */ - protected function collectDeals($url){ - $html = getSimpleHTMLDOM($url); - $list = $html->find('article[id]'); - - // Deal Image Link CSS Selector - $selectorImageLink = implode( - ' ', /* Notice this is a space! */ - array( - 'cept-thread-image-link', - 'imgFrame', - 'imgFrame--noBorder', - 'thread-listImgCell', - ) - ); - - // Deal Link CSS Selector - $selectorLink = implode( - ' ', /* Notice this is a space! */ - array( - 'cept-tt', - 'thread-link', - 'linkPlain', - ) - ); - - // Deal Hotness CSS Selector - $selectorHot = implode( - ' ', /* Notice this is a space! */ - array( - 'cept-vote-box', - 'vote-box' - ) - ); - - // Deal Description CSS Selector - $selectorDescription = implode( - ' ', /* Notice this is a space! */ - array( - 'overflow--wrap-break' - ) - ); - - // Deal Date CSS Selector - $selectorDate = implode( - ' ', /* Notice this is a space! */ - array( - 'size--all-s', - 'flex', - 'boxAlign-jc--all-fe' - ) - ); - - // If there is no results, we don't parse the content because it display some random deals - $noresult = $html->find('h3[class=size--all-l size--fromW2-xl size--fromW3-xxl]', 0); - if ($noresult != null && strpos($noresult->plaintext, $this->i8n('no-results')) !== false) { - $this->items = array(); - } else { - foreach ($list as $deal) { - $item = array(); - $item['uri'] = $this->getDealURI($deal); - $item['title'] = $this->getTitle($deal); - $item['author'] = $deal->find('span.thread-username', 0)->plaintext; - - $item['content'] = '
' - . $this->getHTMLTitle($item) - . $this->getPrice($deal) - . $this->getDiscount($deal) - . $this->getShipsFrom($deal) - . $this->getShippingCost($deal) - . $this->getSource($deal) - . $deal->find('div[class*=' . $selectorDescription . ']', 0)->innertext - . '' - . $deal->find('div[class*=' . $selectorHot . ']', 0) - ->find('span', 1)->outertext - . '
'; - $dealDateDiv = $deal->find('div[class*=' . $selectorDate . ']', 0) - ->find('span[class=hide--toW3]'); - $itemDate = end($dealDateDiv)->plaintext; - // In case of a Local deal, there is no date, but we can use - // this case for other reason (like date not in the last field) - if ($this->contains($itemDate, $this->i8n('localdeal'))) { - $item['timestamp'] = time(); - } else if ($this->contains($itemDate, $this->i8n('relative-date-indicator'))) { - $item['timestamp'] = $this->relativeDateToTimestamp($itemDate); - } else { - $item['timestamp'] = $this->parseDate($itemDate); - } - $this->items[] = $item; - } - } - } - - /** - * Get the Talk lastest comments - */ - protected function collectDataTalk(){ - $threadURL = $this->getInput('url'); - $onlyWithUrl = $this->getInput('only_with_url'); - - // Get Thread ID from url passed in parameter - $threadSearch = preg_match('/-([0-9]{1,20})$/', $threadURL, $matches); - - // Show an error message if we can't find the thread ID in the URL sent by the user - if($threadSearch !== 1) { - returnClientError($this->i8n('thread-error')); - } - $threadID = $matches[1]; - - $url = $this->i8n('bridge-uri') . 'graphql'; - - // Get Cookies header to do the query - $cookies = $this->getCookies($url); - - // GraphQL String - // This was extracted from https://www.dealabs.com/assets/js/modern/common_211b99.js - // This string was extracted during a Website visit, and minified using this neat tool : - // https://codepen.io/dangodev/pen/Baoqmoy - $graphqlString = <<<'HEREDOC' -query comments($filter:CommentFilter!,$limit:Int,$page:Int){comments(filter:$filter,limit:$limit,page:$page){ -items{...commentFields}pagination{...paginationFields}}}fragment commentFields on Comment{commentId threadId url -preparedHtmlContent user{...userMediumAvatarFields...userNameFields...userPersonaFields bestBadge{...badgeFields}} -reactionCounts{type count}deletable currentUserReaction{type}reported reportable source status createdAt updatedAt -ignored popular deletedBy{username}notes{content createdAt user{username}}lastEdit{reason timeAgo userId}}fragment -userMediumAvatarFields on User{userId isDeletedOrPendingDeletion imageUrls(slot:"default",variations: -["user_small_avatar"])}fragment userNameFields on User{userId username isUserProfileHidden isDeletedOrPendingDeletion} -fragment userPersonaFields on User{persona{type text}}fragment badgeFields on Badge{badgeId level{...badgeLevelFields}} -fragment badgeLevelFields on BadgeLevel{key name description}fragment paginationFields on Pagination{count current last - next previous size order} -HEREDOC; - - // Construct the JSON object to send to the Website - $queryArray = array ( - 'query' => $graphqlString, - 'variables' => array ( - 'filter' => array ( - 'threadId' => array ( - 'eq' => $threadID, - ), - 'order' => array ( - 'direction' => 'Descending', - ), - - ), - 'page' => 1, - ), - ); - $queryJSON = json_encode($queryArray); - - // HTTP headers - $header = array( - 'Content-Type: application/json', - 'Accept: application/json, text/plain, */*', - 'X-Pepper-Txn: threads.show', - 'X-Request-Type: application/vnd.pepper.v1+json', - 'X-Requested-With: XMLHttpRequest', - $cookies, - ); - // CURL Options - $opts = array( - CURLOPT_POST => 1, - CURLOPT_POSTFIELDS => $queryJSON - ); - $json = getContents($url, $header, $opts); - $objects = json_decode($json); - foreach($objects->data->comments->items as $comment) { - $item = array(); - $item['uri'] = $comment->url; - $item['title'] = $comment->user->username . ' - ' . $comment->createdAt; - $item['author'] = $comment->user->username; - $item['content'] = $comment->preparedHtmlContent; - $item['uid'] = $comment->commentId; - // Timestamp handling needs a new parsing function - if($onlyWithUrl == true) { - // Count Links and Quote Links - $content = str_get_html($item['content']); - $countLinks = count($content->find('a[href]')); - $countQuoteLinks = count($content->find('a[href][class=userHtml-quote-source]')); - // Only add element if there are Links ans more links tant Quote links - if($countLinks > 0 && $countLinks > $countQuoteLinks) { - $this->items[] = $item; - } - } else { - $this->items[] = $item; - } - } - } - - /** - * Extract the cookies obtained from the URL - * @return array the array containing the cookies set by the URL - */ - private function getCookies($url) - { - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - // get headers too with this line - curl_setopt($ch, CURLOPT_HEADER, 1); - $result = curl_exec($ch); - // get cookie - // multi-cookie variant contributed by @Combuster in comments - preg_match_all('/^Set-Cookie:\s*([^;]*)/mi', $result, $matches); - $cookies = array(); - foreach($matches[1] as $item) { - parse_str($item, $cookie); - $cookies = array_merge($cookies, $cookie); - } - $header = 'Cookie: '; - foreach($cookies as $name => $content) { - $header .= $name . '=' . $content . '; '; - } - return $header; - } - - /** - * Check if the string $str contains any of the string of the array $arr - * @return boolean true if the string matched anything otherwise false - */ - private function contains($str, array $arr) - { - foreach ($arr as $a) { - if (stripos($str, $a) !== false) { - return true; - } - } - return false; - } - - /** - * Get the Price from a Deal if it exists - * @return string String of the deal price - */ - private function getPrice($deal) - { - if ($deal->find( - 'span[class*=thread-price]', 0) != null) { - return '
' . $this->i8n('price') . ' : ' - . $deal->find( - 'span[class*=thread-price]', 0 - )->plaintext - . '
'; - } else { - return ''; - } - } - - /** - * Get the Title from a Deal if it exists - * @return string String of the deal title - */ - private function getTitle($deal) - { - - $titleRoot = $deal->find('div[class*=threadGrid-title]', 0); - $titleA = $titleRoot->find('a[class*=thread-link]', 0); - $titleFirstChild = $titleRoot->first_child(); - if($titleA !== null) { - $title = $titleA->plaintext; - } else { - // In some case, expired deals have a different format - $title = $titleRoot->find('span', 0)->plaintext; - } - - return $title; - - } - - /** - * Get the Title from a Talk if it exists - * @return string String of the Talk title - */ - private function getTalkTitle() - { - $html = getSimpleHTMLDOMCached($this->getInput('url')); - $title = $html->find('h1[class=thread-title]', 0)->plaintext; - return $title; - - } - - /** - * Get the HTML Title code from an item - * @return string String of the deal title - */ - private function getHTMLTitle($item) - { - if($item['uri'] == '') { - $html = '

' . $item['title'] . '

'; - } else { - $html = '

' - . $item['title'] . '

'; - } - - return $html; - - } - - /** - * Get the URI from a Deal if it exists - * @return string String of the deal URI - */ - private function getDealURI($deal) - { - - $uriA = $deal->find('div[class*=threadGrid-title]', 0)->find('a[class*=thread-link]', 0); - if($uriA === null) { - $uri = ''; - } else { - $uri = $uriA->href; - } - - return $uri; - - } - - /** - * Get the Shipping costs from a Deal if it exists - * @return string String of the deal shipping Cost - */ - private function getShippingCost($deal) - { - if ($deal->find('span[class*=space--ml-2 size--all-s overflow--wrap-off]', 0) != null) { - if ($deal->find('span[class*=space--ml-2 size--all-s overflow--wrap-off]', 0)->children(1) != null) { - return '
' . $this->i8n('shipping') . ' : ' - . $deal->find('span[class*=space--ml-2 size--all-s overflow--wrap-off]', 0)->children(1)->innertext - . '
'; - } else { - return '
' . $this->i8n('shipping') . ' : ' - . $deal->find('span[class*=text--color-greyShade flex--inline]', 0)->innertext - . '
'; - } - } else { - return ''; - } - } - - /** - * Get the source of a Deal if it exists - * @return string String of the deal source - */ - private function getSource($deal) - { - if ($deal->find('a[class*=text--color-greyShade]', 0) != null) { - return '
' . $this->i8n('origin') . ' : ' - . $deal->find('a[class*=text--color-greyShade]', 0)->outertext - . '
'; - } else { - return ''; - } - } - - /** - * Get the original Price and discout from a Deal if it exists - * @return string String of the deal original price and discount - */ - private function getDiscount($deal) - { - if ($deal->find('span[class*=mute--text text--lineThrough]', 0) != null) { - $discountHtml = $deal->find('span[class=space--ml-1 size--all-l size--fromW3-xl]', 0); - if ($discountHtml != null) { - $discount = $discountHtml->plaintext; - } else { - $discount = ''; - } - return '
' . $this->i8n('discount') . ' : ' - . $deal->find( - 'span[class*=mute--text text--lineThrough]', 0 - )->plaintext - . ' ' - . $discount - . '
'; - } else { - return ''; - } - } - - /** - * Get the Picture URL from a Deal if it exists - * @return string String of the deal Picture URL - */ - private function getImage($deal) - { - $selectorLazy = implode( - ' ', /* Notice this is a space! */ - array( - 'thread-image', - 'width--all-auto', - 'height--all-auto', - 'imgFrame-img', - 'img--dummy', - 'js-lazy-img' - ) - ); - - $selectorPlain = implode( - ' ', /* Notice this is a space! */ - array( - 'thread-image', - 'width--all-auto', - 'height--all-auto', - 'imgFrame-img', - ) - ); - if ($deal->find('img[class=' . $selectorLazy . ']', 0) != null) { - return json_decode( - html_entity_decode( - $deal->find('img[class=' . $selectorLazy . ']', 0) - ->getAttribute('data-lazy-img')))->{'src'}; - } else { - return $deal->find('img[class*=' . $selectorPlain . ']', 0 )->src; - } - } - - /** - * Get the originating country from a Deal if it exists - * @return string String of the deal originating country - */ - private function getShipsFrom($deal) - { - $selector = implode( - ' ', /* Notice this is a space! */ - array( - 'hide--toW2', - 'metaRibbon', - ) - ); - if ($deal->find('span[class*=' . $selector . ']', 0) != null) { - return '
' - . $deal->find('span[class*=' . $selector . ']', 0)->children(2)->plaintext - . '
'; - } else { - return ''; - } - } - - /** - * Transforms a local date into a timestamp - * @return int timestamp of the input date - */ - private function parseDate($string) - { - $month_local = $this->i8n('local-months'); - $month_en = array( - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' - ); - - // A date can be prfixed with some words, we remove theme - $string = $this->removeDatePrefixes($string); - // We translate the local months name in the english one - $date_str = trim(str_replace($month_local, $month_en, $string)); - - // If the date does not contain any year, we add the current year - if (!preg_match('/[0-9]{4}/', $string)) { - $date_str .= ' ' . date('Y'); - } - - // Add the Hour and minutes - $date_str .= ' 00:00'; - $date = DateTime::createFromFormat('j F Y H:i', $date_str); - // In some case, the date is not recognized : as a workaround the actual date is taken - if($date === false) { - $date = new DateTime(); - } - return $date->getTimestamp(); - } - - /** - * Remove the prefix of a date if it has one - * @return the date without prefiux - */ - private function removeDatePrefixes($string) - { - $string = str_replace($this->i8n('date-prefixes'), array(), $string); - return $string; - } - - /** - * Remove the suffix of a relative date if it has one - * @return the relative date without suffixes - */ - private function removeRelativeDateSuffixes($string) - { - if (count($this->i8n('relative-date-ignore-suffix')) > 0) { - $string = preg_replace($this->i8n('relative-date-ignore-suffix'), '', $string); - } - return $string; - } - - /** - * Transforms a relative local date into a timestamp - * @return int timestamp of the input date - */ - private function relativeDateToTimestamp($str) { - $date = new DateTime(); - - // In case of update date, replace it by the regular relative date first word - $str = str_replace($this->i8n('relative-date-alt-prefixes'), $this->i8n('local-time-relative')[0], $str); - - $str = $this->removeRelativeDateSuffixes($str); - - $search = $this->i8n('local-time-relative'); - - $replace = array( - '-', - 'minute', - 'hour', - 'day', - 'month', - 'year', - '' - ); - - $date->modify(str_replace($search, $replace, $str)); - return $date->getTimestamp(); - } - - /** - * Returns the RSS Feed title according to the parameters - * @return string the RSS feed Tiyle - */ - public function getName(){ - switch($this->queriedContext) { - case $this->i8n('context-keyword'): - return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-keyword') . ' : ' . $this->getInput('q'); - break; - case $this->i8n('context-group'): - $values = $this->getParameters()[$this->i8n('context-group')]['group']['values']; - $group = array_search($this->getInput('group'), $values); - return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-group') . ' : ' . $group; - break; - case $this->i8n('context-talk'): - return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-talk') . ' : ' . $this->getTalkTitle(); - break; - default: // Return default value - return static::NAME; - } - } - - /** - * Returns the RSS Feed URI according to the parameters - * @return string the RSS feed Title - */ - public function getURI(){ - switch($this->queriedContext) { - case $this->i8n('context-keyword'): - return $this->getSearchURI(); - break; - case $this->i8n('context-group'): - return $this->getGroupURI(); - break; - case $this->i8n('context-talk'): - return $this->getTalkURI(); - break; - default: // Return default value - return static::URI; - } - } - - /** - * Returns the RSS Feed URI for a keyword Feed - * @return string the RSS feed URI - */ - private function getSearchURI(){ - $q = $this->getInput('q'); - $hide_expired = $this->getInput('hide_expired'); - $hide_local = $this->getInput('hide_local'); - $priceFrom = $this->getInput('priceFrom'); - $priceTo = $this->getInput('priceTo'); - $url = $this->i8n('bridge-uri') - . 'search/advanced?q=' - . urlencode($q) - . '&hide_expired=' . $hide_expired - . '&hide_local=' . $hide_local - . '&priceFrom=' . $priceFrom - . '&priceTo=' . $priceTo - /* Some default parameters - * search_fields : Search in Titres & Descriptions & Codes - * sort_by : Sort the search by new deals - * time_frame : Search will not be on a limited timeframe - */ - . '&search_fields[]=1&search_fields[]=2&search_fields[]=3&sort_by=new&time_frame=0'; - return $url; - } - - /** - * Returns the RSS Feed URI for a group Feed - * @return string the RSS feed URI - */ - private function getGroupURI(){ - $group = $this->getInput('group'); - $order = $this->getInput('order'); - - $url = $this->i8n('bridge-uri') - . $this->i8n('uri-group') . $group . $order; - return $url; - } - - /** - * Returns the RSS Feed URI for a Talk Feed - * @return string the RSS feed URI - */ - private function getTalkURI(){ - $url = $this->getInput('url'); - return $url; - } - - /** - * This is some "localisation" function that returns the needed content using - * the "$lang" class variable in the local class - * @return various the local content needed - */ - protected function i8n($key) - { - if (array_key_exists($key, $this->lang)) { - return $this->lang[$key]; - } else { - return null; - } - } -} diff --git a/bridges/GithubPullRequestBridge.php b/bridges/GithubPullRequestBridge.php index 46d819af..ccb0754c 100644 --- a/bridges/GithubPullRequestBridge.php +++ b/bridges/GithubPullRequestBridge.php @@ -1,5 +1,5 @@ queriedContext) { + case $this->i8n('context-keyword'): + return $this->collectDataKeywords(); + break; + case $this->i8n('context-group'): + return $this->collectDataGroup(); + break; + case $this->i8n('context-talk'): + return $this->collectDataTalk(); + break; + } + } + + /** + * Get the Deal data from the choosen group in the choosed order + */ + protected function collectDataGroup() + { + $url = $this->getGroupURI(); + $this->collectDeals($url); + } + + /** + * Get the Deal data from the choosen keywords and parameters + */ + protected function collectDataKeywords() + { + /* Even if the original website uses POST with the search page, GET works too */ + $url = $this->getSearchURI(); + $this->collectDeals($url); + } + + /** + * Get the Deal data using the given URL + */ + protected function collectDeals($url){ + $html = getSimpleHTMLDOM($url); + $list = $html->find('article[id]'); + + // Deal Image Link CSS Selector + $selectorImageLink = implode( + ' ', /* Notice this is a space! */ + array( + 'cept-thread-image-link', + 'imgFrame', + 'imgFrame--noBorder', + 'thread-listImgCell', + ) + ); + + // Deal Link CSS Selector + $selectorLink = implode( + ' ', /* Notice this is a space! */ + array( + 'cept-tt', + 'thread-link', + 'linkPlain', + ) + ); + + // Deal Hotness CSS Selector + $selectorHot = implode( + ' ', /* Notice this is a space! */ + array( + 'cept-vote-box', + 'vote-box' + ) + ); + + // Deal Description CSS Selector + $selectorDescription = implode( + ' ', /* Notice this is a space! */ + array( + 'overflow--wrap-break' + ) + ); + + // Deal Date CSS Selector + $selectorDate = implode( + ' ', /* Notice this is a space! */ + array( + 'size--all-s', + 'flex', + 'boxAlign-jc--all-fe' + ) + ); + + // If there is no results, we don't parse the content because it display some random deals + $noresult = $html->find('h3[class=size--all-l size--fromW2-xl size--fromW3-xxl]', 0); + if ($noresult != null && strpos($noresult->plaintext, $this->i8n('no-results')) !== false) { + $this->items = array(); + } else { + foreach ($list as $deal) { + $item = array(); + $item['uri'] = $this->getDealURI($deal); + $item['title'] = $this->getTitle($deal); + $item['author'] = $deal->find('span.thread-username', 0)->plaintext; + + $item['content'] = '
' + . $this->getHTMLTitle($item) + . $this->getPrice($deal) + . $this->getDiscount($deal) + . $this->getShipsFrom($deal) + . $this->getShippingCost($deal) + . $this->getSource($deal) + . $deal->find('div[class*=' . $selectorDescription . ']', 0)->innertext + . '' + . $deal->find('div[class*=' . $selectorHot . ']', 0) + ->find('span', 1)->outertext + . '
'; + $dealDateDiv = $deal->find('div[class*=' . $selectorDate . ']', 0) + ->find('span[class=hide--toW3]'); + $itemDate = end($dealDateDiv)->plaintext; + // In case of a Local deal, there is no date, but we can use + // this case for other reason (like date not in the last field) + if ($this->contains($itemDate, $this->i8n('localdeal'))) { + $item['timestamp'] = time(); + } else if ($this->contains($itemDate, $this->i8n('relative-date-indicator'))) { + $item['timestamp'] = $this->relativeDateToTimestamp($itemDate); + } else { + $item['timestamp'] = $this->parseDate($itemDate); + } + $this->items[] = $item; + } + } + } + + /** + * Get the Talk lastest comments + */ + protected function collectDataTalk(){ + $threadURL = $this->getInput('url'); + $onlyWithUrl = $this->getInput('only_with_url'); + + // Get Thread ID from url passed in parameter + $threadSearch = preg_match('/-([0-9]{1,20})$/', $threadURL, $matches); + + // Show an error message if we can't find the thread ID in the URL sent by the user + if($threadSearch !== 1) { + returnClientError($this->i8n('thread-error')); + } + $threadID = $matches[1]; + + $url = $this->i8n('bridge-uri') . 'graphql'; + + // Get Cookies header to do the query + $cookies = $this->getCookies($url); + + // GraphQL String + // This was extracted from https://www.dealabs.com/assets/js/modern/common_211b99.js + // This string was extracted during a Website visit, and minified using this neat tool : + // https://codepen.io/dangodev/pen/Baoqmoy + $graphqlString = <<<'HEREDOC' +query comments($filter:CommentFilter!,$limit:Int,$page:Int){comments(filter:$filter,limit:$limit,page:$page){ +items{...commentFields}pagination{...paginationFields}}}fragment commentFields on Comment{commentId threadId url +preparedHtmlContent user{...userMediumAvatarFields...userNameFields...userPersonaFields bestBadge{...badgeFields}} +reactionCounts{type count}deletable currentUserReaction{type}reported reportable source status createdAt updatedAt +ignored popular deletedBy{username}notes{content createdAt user{username}}lastEdit{reason timeAgo userId}}fragment +userMediumAvatarFields on User{userId isDeletedOrPendingDeletion imageUrls(slot:"default",variations: +["user_small_avatar"])}fragment userNameFields on User{userId username isUserProfileHidden isDeletedOrPendingDeletion} +fragment userPersonaFields on User{persona{type text}}fragment badgeFields on Badge{badgeId level{...badgeLevelFields}} +fragment badgeLevelFields on BadgeLevel{key name description}fragment paginationFields on Pagination{count current last + next previous size order} +HEREDOC; + + // Construct the JSON object to send to the Website + $queryArray = array ( + 'query' => $graphqlString, + 'variables' => array ( + 'filter' => array ( + 'threadId' => array ( + 'eq' => $threadID, + ), + 'order' => array ( + 'direction' => 'Descending', + ), + + ), + 'page' => 1, + ), + ); + $queryJSON = json_encode($queryArray); + + // HTTP headers + $header = array( + 'Content-Type: application/json', + 'Accept: application/json, text/plain, */*', + 'X-Pepper-Txn: threads.show', + 'X-Request-Type: application/vnd.pepper.v1+json', + 'X-Requested-With: XMLHttpRequest', + $cookies, + ); + // CURL Options + $opts = array( + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => $queryJSON + ); + $json = getContents($url, $header, $opts); + $objects = json_decode($json); + foreach($objects->data->comments->items as $comment) { + $item = array(); + $item['uri'] = $comment->url; + $item['title'] = $comment->user->username . ' - ' . $comment->createdAt; + $item['author'] = $comment->user->username; + $item['content'] = $comment->preparedHtmlContent; + $item['uid'] = $comment->commentId; + // Timestamp handling needs a new parsing function + if($onlyWithUrl == true) { + // Count Links and Quote Links + $content = str_get_html($item['content']); + $countLinks = count($content->find('a[href]')); + $countQuoteLinks = count($content->find('a[href][class=userHtml-quote-source]')); + // Only add element if there are Links ans more links tant Quote links + if($countLinks > 0 && $countLinks > $countQuoteLinks) { + $this->items[] = $item; + } + } else { + $this->items[] = $item; + } + } + } + + /** + * Extract the cookies obtained from the URL + * @return array the array containing the cookies set by the URL + */ + private function getCookies($url) + { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + // get headers too with this line + curl_setopt($ch, CURLOPT_HEADER, 1); + $result = curl_exec($ch); + // get cookie + // multi-cookie variant contributed by @Combuster in comments + preg_match_all('/^Set-Cookie:\s*([^;]*)/mi', $result, $matches); + $cookies = array(); + foreach($matches[1] as $item) { + parse_str($item, $cookie); + $cookies = array_merge($cookies, $cookie); + } + $header = 'Cookie: '; + foreach($cookies as $name => $content) { + $header .= $name . '=' . $content . '; '; + } + return $header; + } + + /** + * Check if the string $str contains any of the string of the array $arr + * @return boolean true if the string matched anything otherwise false + */ + private function contains($str, array $arr) + { + foreach ($arr as $a) { + if (stripos($str, $a) !== false) { + return true; + } + } + return false; + } + + /** + * Get the Price from a Deal if it exists + * @return string String of the deal price + */ + private function getPrice($deal) + { + if ($deal->find( + 'span[class*=thread-price]', 0) != null) { + return '
' . $this->i8n('price') . ' : ' + . $deal->find( + 'span[class*=thread-price]', 0 + )->plaintext + . '
'; + } else { + return ''; + } + } + + /** + * Get the Title from a Deal if it exists + * @return string String of the deal title + */ + private function getTitle($deal) + { + + $titleRoot = $deal->find('div[class*=threadGrid-title]', 0); + $titleA = $titleRoot->find('a[class*=thread-link]', 0); + $titleFirstChild = $titleRoot->first_child(); + if($titleA !== null) { + $title = $titleA->plaintext; + } else { + // In some case, expired deals have a different format + $title = $titleRoot->find('span', 0)->plaintext; + } + + return $title; + + } + + /** + * Get the Title from a Talk if it exists + * @return string String of the Talk title + */ + private function getTalkTitle() + { + $html = getSimpleHTMLDOMCached($this->getInput('url')); + $title = $html->find('h1[class=thread-title]', 0)->plaintext; + return $title; + + } + + /** + * Get the HTML Title code from an item + * @return string String of the deal title + */ + private function getHTMLTitle($item) + { + if($item['uri'] == '') { + $html = '

' . $item['title'] . '

'; + } else { + $html = '

' + . $item['title'] . '

'; + } + + return $html; + + } + + /** + * Get the URI from a Deal if it exists + * @return string String of the deal URI + */ + private function getDealURI($deal) + { + + $uriA = $deal->find('div[class*=threadGrid-title]', 0)->find('a[class*=thread-link]', 0); + if($uriA === null) { + $uri = ''; + } else { + $uri = $uriA->href; + } + + return $uri; + + } + + /** + * Get the Shipping costs from a Deal if it exists + * @return string String of the deal shipping Cost + */ + private function getShippingCost($deal) + { + if ($deal->find('span[class*=space--ml-2 size--all-s overflow--wrap-off]', 0) != null) { + if ($deal->find('span[class*=space--ml-2 size--all-s overflow--wrap-off]', 0)->children(1) != null) { + return '
' . $this->i8n('shipping') . ' : ' + . $deal->find('span[class*=space--ml-2 size--all-s overflow--wrap-off]', 0)->children(1)->innertext + . '
'; + } else { + return '
' . $this->i8n('shipping') . ' : ' + . $deal->find('span[class*=text--color-greyShade flex--inline]', 0)->innertext + . '
'; + } + } else { + return ''; + } + } + + /** + * Get the source of a Deal if it exists + * @return string String of the deal source + */ + private function getSource($deal) + { + if ($deal->find('a[class*=text--color-greyShade]', 0) != null) { + return '
' . $this->i8n('origin') . ' : ' + . $deal->find('a[class*=text--color-greyShade]', 0)->outertext + . '
'; + } else { + return ''; + } + } + + /** + * Get the original Price and discout from a Deal if it exists + * @return string String of the deal original price and discount + */ + private function getDiscount($deal) + { + if ($deal->find('span[class*=mute--text text--lineThrough]', 0) != null) { + $discountHtml = $deal->find('span[class=space--ml-1 size--all-l size--fromW3-xl]', 0); + if ($discountHtml != null) { + $discount = $discountHtml->plaintext; + } else { + $discount = ''; + } + return '
' . $this->i8n('discount') . ' : ' + . $deal->find( + 'span[class*=mute--text text--lineThrough]', 0 + )->plaintext + . ' ' + . $discount + . '
'; + } else { + return ''; + } + } + + /** + * Get the Picture URL from a Deal if it exists + * @return string String of the deal Picture URL + */ + private function getImage($deal) + { + $selectorLazy = implode( + ' ', /* Notice this is a space! */ + array( + 'thread-image', + 'width--all-auto', + 'height--all-auto', + 'imgFrame-img', + 'img--dummy', + 'js-lazy-img' + ) + ); + + $selectorPlain = implode( + ' ', /* Notice this is a space! */ + array( + 'thread-image', + 'width--all-auto', + 'height--all-auto', + 'imgFrame-img', + ) + ); + if ($deal->find('img[class=' . $selectorLazy . ']', 0) != null) { + return json_decode( + html_entity_decode( + $deal->find('img[class=' . $selectorLazy . ']', 0) + ->getAttribute('data-lazy-img')))->{'src'}; + } else { + return $deal->find('img[class*=' . $selectorPlain . ']', 0 )->src; + } + } + + /** + * Get the originating country from a Deal if it exists + * @return string String of the deal originating country + */ + private function getShipsFrom($deal) + { + $selector = implode( + ' ', /* Notice this is a space! */ + array( + 'hide--toW2', + 'metaRibbon', + ) + ); + if ($deal->find('span[class*=' . $selector . ']', 0) != null) { + return '
' + . $deal->find('span[class*=' . $selector . ']', 0)->children(2)->plaintext + . '
'; + } else { + return ''; + } + } + + /** + * Transforms a local date into a timestamp + * @return int timestamp of the input date + */ + private function parseDate($string) + { + $month_local = $this->i8n('local-months'); + $month_en = array( + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ); + + // A date can be prfixed with some words, we remove theme + $string = $this->removeDatePrefixes($string); + // We translate the local months name in the english one + $date_str = trim(str_replace($month_local, $month_en, $string)); + + // If the date does not contain any year, we add the current year + if (!preg_match('/[0-9]{4}/', $string)) { + $date_str .= ' ' . date('Y'); + } + + // Add the Hour and minutes + $date_str .= ' 00:00'; + $date = DateTime::createFromFormat('j F Y H:i', $date_str); + // In some case, the date is not recognized : as a workaround the actual date is taken + if($date === false) { + $date = new DateTime(); + } + return $date->getTimestamp(); + } + + /** + * Remove the prefix of a date if it has one + * @return the date without prefiux + */ + private function removeDatePrefixes($string) + { + $string = str_replace($this->i8n('date-prefixes'), array(), $string); + return $string; + } + + /** + * Remove the suffix of a relative date if it has one + * @return the relative date without suffixes + */ + private function removeRelativeDateSuffixes($string) + { + if (count($this->i8n('relative-date-ignore-suffix')) > 0) { + $string = preg_replace($this->i8n('relative-date-ignore-suffix'), '', $string); + } + return $string; + } + + /** + * Transforms a relative local date into a timestamp + * @return int timestamp of the input date + */ + private function relativeDateToTimestamp($str) { + $date = new DateTime(); + + // In case of update date, replace it by the regular relative date first word + $str = str_replace($this->i8n('relative-date-alt-prefixes'), $this->i8n('local-time-relative')[0], $str); + + $str = $this->removeRelativeDateSuffixes($str); + + $search = $this->i8n('local-time-relative'); + + $replace = array( + '-', + 'minute', + 'hour', + 'day', + 'month', + 'year', + '' + ); + + $date->modify(str_replace($search, $replace, $str)); + return $date->getTimestamp(); + } + + /** + * Returns the RSS Feed title according to the parameters + * @return string the RSS feed Tiyle + */ + public function getName(){ + switch($this->queriedContext) { + case $this->i8n('context-keyword'): + return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-keyword') . ' : ' . $this->getInput('q'); + break; + case $this->i8n('context-group'): + $values = $this->getParameters()[$this->i8n('context-group')]['group']['values']; + $group = array_search($this->getInput('group'), $values); + return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-group') . ' : ' . $group; + break; + case $this->i8n('context-talk'): + return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-talk') . ' : ' . $this->getTalkTitle(); + break; + default: // Return default value + return static::NAME; + } + } + + /** + * Returns the RSS Feed URI according to the parameters + * @return string the RSS feed Title + */ + public function getURI(){ + switch($this->queriedContext) { + case $this->i8n('context-keyword'): + return $this->getSearchURI(); + break; + case $this->i8n('context-group'): + return $this->getGroupURI(); + break; + case $this->i8n('context-talk'): + return $this->getTalkURI(); + break; + default: // Return default value + return static::URI; + } + } + + /** + * Returns the RSS Feed URI for a keyword Feed + * @return string the RSS feed URI + */ + private function getSearchURI(){ + $q = $this->getInput('q'); + $hide_expired = $this->getInput('hide_expired'); + $hide_local = $this->getInput('hide_local'); + $priceFrom = $this->getInput('priceFrom'); + $priceTo = $this->getInput('priceTo'); + $url = $this->i8n('bridge-uri') + . 'search/advanced?q=' + . urlencode($q) + . '&hide_expired=' . $hide_expired + . '&hide_local=' . $hide_local + . '&priceFrom=' . $priceFrom + . '&priceTo=' . $priceTo + /* Some default parameters + * search_fields : Search in Titres & Descriptions & Codes + * sort_by : Sort the search by new deals + * time_frame : Search will not be on a limited timeframe + */ + . '&search_fields[]=1&search_fields[]=2&search_fields[]=3&sort_by=new&time_frame=0'; + return $url; + } + + /** + * Returns the RSS Feed URI for a group Feed + * @return string the RSS feed URI + */ + private function getGroupURI(){ + $group = $this->getInput('group'); + $order = $this->getInput('order'); + + $url = $this->i8n('bridge-uri') + . $this->i8n('uri-group') . $group . $order; + return $url; + } + + /** + * Returns the RSS Feed URI for a Talk Feed + * @return string the RSS feed URI + */ + private function getTalkURI(){ + $url = $this->getInput('url'); + return $url; + } + + /** + * This is some "localisation" function that returns the needed content using + * the "$lang" class variable in the local class + * @return various the local content needed + */ + protected function i8n($key) + { + if (array_key_exists($key, $this->lang)) { + return $this->lang[$key]; + } else { + return null; + } + } +} diff --git a/bridges/Rule34Bridge.php b/bridges/Rule34Bridge.php index b210aa38..5c8ddc93 100644 --- a/bridges/Rule34Bridge.php +++ b/bridges/Rule34Bridge.php @@ -1,5 +1,4 @@