diff --git a/bridges/CruncyrollBridge.php b/bridges/CruncyrollBridge.php new file mode 100644 index 00000000..12f8ba98 --- /dev/null +++ b/bridges/CruncyrollBridge.php @@ -0,0 +1,129 @@ + [ + 'name' => 'Category', + 'type' => 'text', + 'required' => true, + 'exampleValue' => 'Announcements,News,News', + ], + 'page_size' => [ + 'name' => 'Page Size', + 'type' => 'number', + 'required' => false, + 'defaultValue' => 16, + ], + 'page' => [ + 'name' => 'Page', + 'type' => 'number', + 'required' => false, + 'defaultValue' => 1, + ], + ], + ]; + + /** + * Collects data from the Crunchyroll API and populates items. + * + * @throws Exception If the API call or JSON decoding fails. + */ + public function collectData() + { + // Define API base URL + $apiBaseUrl = 'https://cr-news-api-service.prd.crunchyrollsvc.com/v1/en-US/stories/search'; + + // Retrieve input parameters + $category = $this->getInput('category'); + $pageSize = $this->getInput('page_size'); + $page = $this->getInput('page'); + + // Construct the API URL with query parameters + $apiUrl = sprintf( + '%s?category=%s&page_size=%d&page=%d', + $apiBaseUrl, + urlencode($category), + $pageSize, + $page + ); + + // Define HTTP headers for the API request + $options = [ + 'http' => [ + 'method' => 'GET', + 'header' => [ + 'User-Agent: Mozilla/5.0 (compatible; RSSBridge/2025)', + 'Accept: application/json', + 'Accept-Language: en-US,en;q=0.5', + 'Origin: https://www.crunchyroll.com', + 'Referer: https://www.crunchyroll.com/', + ], + ], + ]; + + // Use getContents for better error handling and compliance + $response = getContents($apiUrl, [], $options); + + if ($response === false) { + throw new Exception('Failed to fetch data from the Crunchyroll API.'); + } + + // Parse the JSON response + $data = json_decode($response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new Exception('Failed to decode JSON response: ' . json_last_error_msg()); + } + + // Map UUIDs to author names from the `rels` array + $authorMap = []; + foreach ($data['rels'] as $rel) { + if (isset($rel['uuid'], $rel['content']['name'])) { + $authorMap[$rel['uuid']] = $rel['content']['name']; + } + } + + // Process each story in the response + foreach ($data['stories'] as $story) { + $item = []; + + // Find the author name(s) for the story + $authorNames = []; + if (!empty($story['content']['authors'])) { + foreach ($story['content']['authors'] as $authorUuid) { + if (isset($authorMap[$authorUuid])) { + $authorNames[] = $authorMap[$authorUuid]; + } + } + } + + // Set the `author` field to the resolved names or default to 'Unknown' + $item['author'] = implode(', ', $authorNames) ?: 'Unknown'; + + // Set the item properties + $item['uri'] = self::URI . '/' . $story['slug']; + $item['title'] = $story['content']['headline']; + $item['timestamp'] = strtotime($story['content']['article_date']); + $item['content'] = sprintf( + '%s
%s', + $story['content']['thumbnail']['filename'], + htmlspecialchars($story['content']['thumbnail']['alt']), + htmlspecialchars($story['content']['lead']) + ); + $item['categories'] = $story['tag_list'] ?? []; + $item['uid'] = $story['uuid']; + + // Add the item to the feed + $this->items[] = $item; + } + } +} diff --git a/bridges/HidiveBridge.php b/bridges/HidiveBridge.php new file mode 100644 index 00000000..9b92df14 --- /dev/null +++ b/bridges/HidiveBridge.php @@ -0,0 +1,101 @@ + 9, + 'skip' => 0, + 'filter' => new stdClass() + ]); + + // Define headers + $headers = [ + 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0', + 'Accept: */*', + 'Accept-Language: en-US,en;q=0.5', + 'Content-Type: application/json', + 'Origin: https://news.hidive.com', + 'Referer: https://news.hidive.com/' + ]; + + // Prepare the HTTP options for getContents + $options = [ + 'http' => [ + 'method' => 'POST', + 'header' => implode("\r\n", $headers), + 'content' => $postData, + 'ignore_errors' => true + ] + ]; + + // Use getContents for the HTTP request + $response = getContents($apiUrl, $options); + + if ($response === false) { + returnServerError('Unable to fetch data from HIDIVE API.'); + } + + // Decode the JSON response + $data = json_decode($response, true); + if (json_last_error() !== JSON_ERROR_NONE) { + returnServerError('Failed to decode JSON: ' . json_last_error_msg()); + } + + // Process each news item + foreach ($data as $item) { + $newsItem = []; + + // Clean and format the data + $excerpt = isset($item['excerpt']) ? ltrim($item['excerpt']) : ''; + $seoUrl = isset($item['seoUrl']) ? 'https://news.hidive.com' . $item['seoUrl'] : ''; + $image = isset($item['image']) ? 'https:' . $item['image'] : ''; + + // Create feed item + $newsItem['uri'] = $seoUrl; + $newsItem['title'] = $item['title'] ?? ''; + $newsItem['timestamp'] = strtotime($item['releaseDate'] ?? ''); + + // Construct content with image and excerpt + $content = ''; + if ($image) { + $content .= '' . 
+                           htmlspecialchars($item['title'] ?? '') . ''; + } + $content .= '

' . htmlspecialchars($excerpt) . '

'; + + $newsItem['content'] = $content; + + // Add categories if available + if (isset($item['categories']) && is_array($item['categories'])) { + $newsItem['categories'] = $item['categories']; + } + + // Add author if available + if (isset($item['author'])) { + $newsItem['author'] = $item['author']; + } + + $this->items[] = $newsItem; + } + } + + public function getName() { + return self::NAME; + } + + public function getURI() { + return self::URI; + } + + public function getIcon() { + return 'https://www.hidive.com/favicon.ico'; + } +}