155 lines
5.5 KiB
JavaScript

import { load } from 'cheerio';
/**
* Minimal Rumble provider handler
*/
const handler = {
id: 'ru',
label: 'Rumble',
/**
* @param {string} q
* @param {{ limit: number, page?: number }} opts
* @returns {Promise<Array<any>>}
*/
async search(q, opts) {
const { limit = 10, page = 1 } = opts || {};
try {
const perPage = Math.min(Math.max(1, Number(limit || 10)), 50);
const pageNum = Math.max(1, Number(page || 1));
const params = new URLSearchParams({ q: q });
if (pageNum > 1) params.set('page', pageNum.toString());
const response = await fetch(`https://rumble.com/search/video?${params.toString()}` , {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8'
}
});
if (!response.ok) {
throw new Error(`Rumble API error: ${response.status}`);
}
const html = await response.text();
const $ = load(html);
const items = [];
const parseDurationToSeconds = (raw) => {
if (raw == null) return undefined;
const value = String(raw).trim();
if (!value) return undefined;
// Plain numeric seconds
const numeric = Number(value);
if (Number.isFinite(numeric) && numeric > 0) return Math.floor(numeric);
// ISO-8601 style: PT#H#M#S
const isoMatch = value.match(/^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/i);
if (isoMatch) {
const hours = Number(isoMatch[1] || 0);
const minutes = Number(isoMatch[2] || 0);
const seconds = Number(isoMatch[3] || 0);
const totalIso = (hours * 3600) + (minutes * 60) + seconds;
if (totalIso > 0) return totalIso;
}
// Text formats like "1h 2m 3s" or "15m13s"
const textMatch = value.match(/^(?:(\d+)\s*h(?:ours?)?)?\s*(?:(\d+)\s*m(?:in(?:utes)?)?)?\s*(?:(\d+)\s*s(?:ec(?:onds)?)?)?$/i);
if (textMatch && (textMatch[1] || textMatch[2] || textMatch[3])) {
const hours = Number(textMatch[1] || 0);
const minutes = Number(textMatch[2] || 0);
const seconds = Number(textMatch[3] || 0);
const totalText = (hours * 3600) + (minutes * 60) + seconds;
if (totalText > 0) return totalText;
}
// Colon separated HH:MM:SS or MM:SS
const colonCandidate = value.replace(/[^0-9:]/g, '');
if (colonCandidate.includes(':')) {
const segments = colonCandidate.split(':').filter(Boolean).map(s => Number(s));
if (segments.length >= 2 && segments.every(n => Number.isFinite(n))) {
while (segments.length < 3) segments.unshift(0);
const [hours, minutes, seconds] = segments.slice(-3);
const totalColon = (hours * 3600) + (minutes * 60) + seconds;
if (totalColon > 0) return totalColon;
}
}
// Fallback: first integer found, assume seconds if >0
const fallbackDigits = value.match(/(\d+)/);
if (fallbackDigits) {
const seconds = Number(fallbackDigits[1]);
if (Number.isFinite(seconds) && seconds > 0) return seconds;
}
return undefined;
};
$('li.video-listing-entry').each((_idx, el) => {
if (items.length >= perPage) return false;
const $el = $(el);
const anchor = ($el.find('a.video-item--a').attr('href') || '').trim();
if (!anchor) return;
const img = $el.find('img.video-item--img');
const rawThumbnail =
img.attr('data-src') ||
img.attr('data-original') ||
img.attr('src') ||
'';
const normalizedThumb = rawThumbnail.replace(/\s+/g, '').trim();
const title = $el.find('h3.video-item--title').text().replace(/\s+/g, ' ').trim();
const uploaderName = $el.find('.ellipsis-1').text().replace(/\s+/g, ' ').trim();
const url = anchor.startsWith('http') ? anchor : `https://rumble.com${anchor}`;
const id =
$el.attr('data-id') ||
url.split('/').filter(Boolean).pop() ||
String(Math.random());
const thumbnail = normalizedThumb
? normalizedThumb.startsWith('//')
? `https:${normalizedThumb}`
: normalizedThumb
: undefined;
const durationCandidates = [
$el.attr('data-duration'),
$el.attr('data-video-duration'),
$el.data('duration'),
$el.find('[data-duration]').attr('data-duration'),
$el.find('[data-video-duration]').attr('data-video-duration'),
$el.find('time[datetime]').attr('datetime'),
$el.find('.video-item--duration, .video-item--meta time, .video-item--meta .duration, .video-item--meta-duration, .video-item--length').first().text(),
];
let durationSeconds;
for (const candidate of durationCandidates) {
const parsed = parseDurationToSeconds(candidate);
if (typeof parsed === 'number' && parsed > 0) {
durationSeconds = parsed;
break;
}
}
items.push({
title: title || url,
id,
url,
thumbnail,
uploaderName: uploaderName || undefined,
type: 'video',
duration: durationSeconds
});
});
return items;
} catch (error) {
console.error('Rumble search error:', error);
return [];
}
}
};
export default handler;