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',
+ $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 .= '';
+ }
+ $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'; + } +}