Skip to content

Rumble 2.0 #4474

Open
Open
@Dokukanal17tv

Description

@Dokukanal17tv

Preface

I’ve come up with an idea for an improved version of the RumbleBridge to make it more modern and robust, incorporating additional data like embed URLs, thumbnails, and durations from Rumble. However, I’m inexperienced with GitHub and its workflows, so I’m submitting this as a request rather than a direct pull request. Below is the changelog detailing the enhancements and the updated feed output. I’d appreciate any feedback or assistance in getting this integrated!

Changelog: Improvements to RumbleBridge

Changed

  • Caching Behavior:

    • Changed CACHE_TIMEOUT from 60 * 60 (1 hour, fixed) to 0 (no caching by default).
    • Added new parameter cache_timeout (type: number, default: 0) to PARAMETERS, dynamically configurable via getCacheTimeout().
    • Impact: Users can now control caching through the UI (e.g., set to 0 for always fresh data).
  • Description:

    • Updated from 'Fetches the latest channel/user videos and livestreams.' to 'Fetches detailed channel/user videos and livestreams from Rumble.'.
    • Impact: Reflects the enhanced data output.

Added

  • Data Sources:

    • Added primary extraction of JSON-LD data using getContents() and preg_match() to parse <script type="application/ld+json">.
    • Retained HTML parsing with getSimpleHTMLDOM() as a fallback when JSON-LD is unavailable.
    • Impact: More stable and modern data extraction with JSON-LD; fallback improves reliability.
  • Feed Item Content:

    • JSON-LD (Primary Source):
      • Added embedUrl as an <iframe> in content.
      • Added description as a <p> in content.
      • Added thumbnailUrl to enclosures.
      • Added duration as text in content and as seconds in itunes:duration.
    • HTML Fallback:
      • Kept minimal extraction similar to the original, using defaultLinkTo() for cleaner links.
    • Impact: Richer feed items with embed URLs, thumbnails, descriptions, and durations when JSON-LD is available.
  • Parameter:

    • Added cache_timeout to PARAMETERS for user-configurable caching.
    • Impact: Enhances flexibility for feed refresh rates.

Improved

  • Error Handling:

    • Replaced throw new \Exception with API-compliant returnServerError() for invalid account, type, or failed requests.
    • Impact: Better integration with RSS-Bridge error handling conventions.
  • Code Structure:

    • Split logic into helper methods:
      • createItemFromJsonLd(): Handles JSON-LD data.
      • createItemFromHtml(): Handles HTML fallback.
      • parseDurationToSeconds(): Converts PT1H2M3S to seconds.
    • Impact: Improved readability and maintainability.

Feed Output

The feed items ($this->items) now contain the following fields, depending on the data source:

When JSON-LD is Available (Primary Source):

  • title: Video title from name (e.g., "My New Video"), fallback to "Untitled".
  • author: $account . '@rumble.com' (e.g., "[email protected]").
  • uri: Video URL from url (e.g., "https://rumble.com/v12345-my-video.html").
  • timestamp: Unix timestamp from uploadDate (e.g., 1710278400), fallback to current time.
  • content: HTML string with:
    • <iframe src="[embedUrl]" frameborder="0" allowfullscreen></iframe> (e.g., <iframe src="https://rumble.com/embed/v12345" ...>), if embedUrl exists.
    • <p>[description]</p> (e.g., <p>This is my new video about Bitcoin.</p>), if description exists.
    • <p>Duration: [duration]</p> (e.g., <p>Duration: PT15M30S</p>), if duration exists.
  • enclosures: Array with thumbnail URL from thumbnailUrl (e.g., ["https://rumble.com/thumbs/v12345.jpg"]), if available.
  • itunes:duration: Duration in seconds from duration (e.g., "930" for 15m30s), if available.

When JSON-LD is Unavailable (HTML Fallback):

  • title: Title from <h3> (e.g., "My New Video"), fallback to "Untitled".
  • author: $account . '@rumble.com' (e.g., "[email protected]").
  • uri: Video URL from <a href> (e.g., "https://rumble.com/v12345-my-video.html").
  • timestamp: Unix timestamp from <time datetime> (e.g., 1710278400), if available.
  • content: Cleaned HTML content with links to self::URI (e.g., <a href="https://rumble.com/v12345-my-video.html">My New Video</a>).

Code Rumble Bridge 2.0

<?php
class RumbleBridge extends BridgeAbstract
{
    const NAME = 'Rumble.com Bridge';
    const URI = 'https://rumble.com/';
    const DESCRIPTION = 'Fetches detailed channel/user videos and livestreams from Rumble.';
    const MAINTAINER = 'dvikan, NotsoanoNimus';
    const CACHE_TIMEOUT = 0;

    const PARAMETERS = [
        [
            'account' => [
                'name' => 'Account',
                'type' => 'text',
                'required' => true,
                'title' => 'Name of the target account (e.g., 21UhrBitcoinPodcast)',
            ],
            'type' => [
                'name' => 'Account Type',
                'type' => 'list',
                'title' => 'The type of profile to create a feed from.',
                'values' => [
                    'Channel (All)' => 'channel',
                    'Channel Videos' => 'channel-videos',
                    'Channel Livestreams' => 'channel-livestream',
                    'User (All)' => 'user',
                ],
            ],
            'cache_timeout' => [
                'name' => 'Cache Timeout (seconds)',
                'type' => 'number',
                'defaultValue' => 0,
                'title' => 'How long to cache the feed (0 for no caching)',
            ],
        ]
    ];

    public function collectData()
    {
        $account = $this->getInput('account');
        $type = $this->getInput('type');
        $url = self::URI;

        if (!preg_match('#^[\w\-_.@]+$#', $account) || strlen($account) > 64) {
            returnServerError('Invalid target account.');
        }

        switch ($type) {
            case 'user':
                $url .= "user/$account";
                break;
            case 'channel':
                $url .= "c/$account";
                break;
            case 'channel-videos':
                $url .= "c/$account/videos";
                break;
            case 'channel-livestream':
                $url .= "c/$account/livestreams";
                break;
            default:
                returnServerError('Invalid media type.');
        }

        $html = $this->getContents($url);
        if (!$html) {
            returnServerError("Failed to fetch $url");
        }

        $items = [];
        if (preg_match('/<script.*?application\/ld\+json.*?>(.*?)<\/script>/s', $html, $matches)) {
            $jsonData = json_decode($matches[1], true);
            if ($jsonData) {
                $videos = isset($jsonData['@graph']) ? $jsonData['@graph'] : [$jsonData];
                foreach ($videos as $item) {
                    if (isset($item['@type']) && $item['@type'] === 'VideoObject') {
                        $items[] = $this->createItemFromJsonLd($item, $account);
                    }
                }
            }
        }

        if (empty($items)) {
            $dom = $this->getSimpleHTMLDOM($url);
            if ($dom) {
                foreach ($dom->find('ol.thumbnail__grid div.thumbnail__grid--item') as $video) {
                    $items[] = $this->createItemFromHtml($video, $account);
                }
            } else {
                returnServerError("Failed to parse HTML from $url");
            }
        }

        $this->items = $items;
    }

    private function createItemFromJsonLd(array $json, string $account): array
    {
        $item = [
            'title' => html_entity_decode($json['name'] ?? 'Untitled', ENT_QUOTES, 'UTF-8'),
            'author' => $account . '@rumble.com',
            'uri' => $json['url'] ?? '',
            'timestamp' => (new DateTime($json['uploadDate'] ?? 'now'))->getTimestamp(),
            'content' => '',
        ];

        if (isset($json['embedUrl'])) {
            $item['content'] .= "<iframe src='{$json['embedUrl']}' frameborder='0' allowfullscreen></iframe>";
        }

        if (isset($json['description'])) {
            $item['content'] .= '<p>' . html_entity_decode($json['description'], ENT_QUOTES, 'UTF-8') . '</p>';
        }
        if (isset($json['thumbnailUrl'])) {
            $item['enclosures'] = [$json['thumbnailUrl']];
        }

        if (isset($json['duration'])) {
            $item['content'] .= "<p>Duration: {$json['duration']}</p>";
            $item['itunes:duration'] = $this->parseDurationToSeconds($json['duration']);
        }

        return $item;
    }

    private function createItemFromHtml($video, string $account): array
    {
        $href = $video->find('a', 0)->href ?? '';
        $item = [
            'title' => $video->find('h3', 0)->plaintext ?? 'Untitled',
            'author' => $account . '@rumble.com',
            'content' => $this->defaultLinkTo($video->innertext, self::URI),
            'uri' => self::URI . ltrim($href, '/'),
        ];

        $time = $video->find('time', 0);
        if ($time) {
            $item['timestamp'] = (new DateTime($time->getAttribute('datetime')))->getTimestamp();
        }

        return $item;
    }

    private function parseDurationToSeconds(string $duration): string
    {
        if (preg_match('/PT(\d+H)?(\d+M)?(\d+S)?/', $duration, $matches)) {
            $hours = (int) str_replace('H', '', $matches[1] ?? 0);
            $minutes = (int) str_replace('M', '', $matches[2] ?? 0);
            $seconds = (int) str_replace('S', '', $matches[3] ?? 0);
            return (string) ($hours * 3600 + $minutes * 60 + $seconds);
        }
        return $duration;
    }

    public function getName()
    {
        return $this->getInput('account') ? "Rumble.com - {$this->getInput('account')}" : self::NAME;
    }

    public function getCacheTimeout()
    {
        return (int) $this->getInput('cache_timeout') ?: self::CACHE_TIMEOUT;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions