Migrated git root from W:/programming/Projects/ to W:/programming/Projects/Tubearr/. Previous history preserved in Tubearr-full-backup.bundle at parent directory. Completed milestones: M001 through M005 Active: M006/S02 (Add Channel UX)
115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
import type {
|
|
Platform,
|
|
PlatformSourceMetadata,
|
|
PlatformContentMetadata,
|
|
PlaylistDiscoveryResult,
|
|
Channel,
|
|
} from '../types/index';
|
|
|
|
// ── Options ──
|
|
|
|
/** Options for fetchRecentContent, loaded from platform settings. */
|
|
export interface FetchRecentContentOptions {
|
|
/** Maximum items to enumerate in the discovery phase. Default: 50 */
|
|
limit?: number;
|
|
/** Set of platformContentIds already known — skips enrichment for these. */
|
|
existingIds?: Set<string>;
|
|
/** Milliseconds to wait between per-item enrichment calls. Default: 1000 */
|
|
rateLimitDelay?: number;
|
|
}
|
|
|
|
// ── Interface ──
|
|
|
|
/**
|
|
* Extensible plugin contract for platform integrations.
|
|
* Each platform (YouTube, SoundCloud, etc.) implements this interface
|
|
* to provide channel resolution and content fetching via yt-dlp.
|
|
*/
|
|
export interface PlatformSource {
|
|
/** Resolve a platform URL to channel metadata (name, platformId, image). */
|
|
resolveChannel(url: string): Promise<PlatformSourceMetadata>;
|
|
|
|
/** Fetch recent content items for a channel. */
|
|
fetchRecentContent(
|
|
channel: Channel,
|
|
options?: FetchRecentContentOptions
|
|
): Promise<PlatformContentMetadata[]>;
|
|
|
|
/**
|
|
* Fetch ALL content for a channel (no playlist-items limit).
|
|
* Used by back-catalog import. Optional — platforms that don't support
|
|
* full catalog fetch fall back to fetchRecentContent with a high limit.
|
|
*/
|
|
fetchAllContent?(channel: Channel): Promise<PlatformContentMetadata[]>;
|
|
|
|
/**
|
|
* Fetch playlists for a channel, with video-to-playlist mappings.
|
|
* Optional — not all platforms expose playlist information.
|
|
*/
|
|
fetchPlaylists?(channel: Channel): Promise<PlaylistDiscoveryResult[]>;
|
|
}
|
|
|
|
// ── Registry ──
|
|
|
|
/**
|
|
* Maps Platform enum values to PlatformSource implementations.
|
|
* Resolves the correct source from a URL via pattern matching.
|
|
*/
|
|
export class PlatformRegistry {
|
|
private readonly sources = new Map<Platform, PlatformSource>();
|
|
|
|
/** Register a platform source implementation. */
|
|
register(platform: Platform, source: PlatformSource): void {
|
|
this.sources.set(platform, source);
|
|
}
|
|
|
|
/** Get the source for a known platform. */
|
|
get(platform: Platform): PlatformSource | undefined {
|
|
return this.sources.get(platform);
|
|
}
|
|
|
|
/**
|
|
* Detect the platform from a URL and return the corresponding source.
|
|
* Returns null if the URL doesn't match any registered platform.
|
|
*/
|
|
getForUrl(url: string): { platform: Platform; source: PlatformSource } | null {
|
|
const platform = detectPlatformFromUrl(url);
|
|
if (!platform) return null;
|
|
|
|
const source = this.sources.get(platform);
|
|
if (!source) return null;
|
|
|
|
return { platform, source };
|
|
}
|
|
}
|
|
|
|
// ── URL Detection ──
|
|
|
|
const YOUTUBE_PATTERNS = [
|
|
/^https?:\/\/(www\.)?youtube\.com\/@/,
|
|
/^https?:\/\/(www\.)?youtube\.com\/channel\//,
|
|
/^https?:\/\/(www\.)?youtube\.com\/c\//,
|
|
/^https?:\/\/(www\.)?youtube\.com\/user\//,
|
|
/^https?:\/\/youtu\.be\//,
|
|
];
|
|
|
|
const SOUNDCLOUD_PATTERNS = [
|
|
/^https?:\/\/(www\.)?soundcloud\.com\/[^/]+\/?$/,
|
|
/^https?:\/\/(www\.)?soundcloud\.com\/[^/]+$/,
|
|
];
|
|
|
|
function detectPlatformFromUrl(url: string): Platform | null {
|
|
for (const pattern of YOUTUBE_PATTERNS) {
|
|
if (pattern.test(url)) return 'youtube' as Platform;
|
|
}
|
|
|
|
// SoundCloud: match artist pages, reject track/set URLs
|
|
if (/^https?:\/\/(www\.)?soundcloud\.com\//.test(url)) {
|
|
// Reject track and set URLs
|
|
if (/\/(tracks|sets)\//.test(url)) return null;
|
|
// Accept artist-level URLs
|
|
return 'soundcloud' as Platform;
|
|
}
|
|
|
|
return null;
|
|
}
|