// Channel manager (subscribed + discovery), the channel About/detail page, ephemeral exploration // of un-subscribed channels, and the per-user channel blocklist. Only touches core's req. import { req } from "./core"; interface ChannelLink { title: string | null; url: string; } export interface ChannelDetail { id: string; title: string | null; handle: string | null; description: string | null; thumbnail_url: string | null; banner_url: string | null; subscriber_count: number | null; video_count: number | null; total_view_count: number | null; published_at: string | null; external_links: ChannelLink[]; country: string | null; default_language: string | null; topic_categories: string[]; keywords: string | null; subscribed: boolean; explored: boolean; blocked: boolean; stored_videos: number; deep_requested: boolean; priority: number; backfill_done: boolean; from_explore: boolean; details_synced: boolean; tag_ids: number[]; } export interface ExploreResult { channel_id: string; ingested: number; next_page_token: string | null; } export interface MyStatus { channels_total: number; channels_details_synced: number; channels_recent_synced: number; channels_deep_done: number; channels_recent_pending: number; channels_deep_pending: number; channels_deep_requested: number; deep_pending_count: number; deep_eta_seconds: number; my_videos: number; total_videos: number; quota_used_today: number; quota_remaining_today: number; paused: boolean; sync_active: boolean; // a channel-sync job (backfill/rss) is running right now } export interface ManagedChannel { id: string; title: string | null; handle: string | null; thumbnail_url: string | null; subscriber_count: number | null; video_count: number | null; description: string | null; stored_videos: number; last_video_at: string | null; total_duration_seconds: number; count_normal: number; count_short: number; count_live: number; priority: number; hidden: boolean; deep_requested: boolean; deep_in_queue: boolean; tag_ids: number[]; details_synced: boolean; recent_synced: boolean; backfill_done: boolean; } // A channel that shows up in the user's playlists but that they don't subscribe to — // surfaced in the Channel manager's Discovery tab so they can subscribe in one click. export interface DiscoveredChannel { id: string; title: string | null; handle: string | null; thumbnail_url: string | null; subscriber_count: number | null; video_count: number | null; description: string | null; playlist_video_count: number; // how many of the user's playlist videos are from here playlist_count: number; // across how many of their playlists details_synced: boolean; } export const channelsApi = { myStatus: (): Promise => req("/api/sync/my-status"), channels: (): Promise => req("/api/channels"), updateChannel: ( id: string, patch: { priority?: number; hidden?: boolean; deep_requested?: boolean }, ) => req(`/api/channels/${id}`, { method: "PATCH", body: JSON.stringify(patch) }), resetChannelBackfill: (id: string) => req(`/api/channels/${id}/reset-backfill`, { method: "POST" }), deepAll: (on = true): Promise<{ updated: number; deep_requested: boolean }> => req(`/api/sync/deep-all?on=${on}`, { method: "POST" }), attachChannelTag: (id: string, tagId: number) => req(`/api/channels/${id}/tags`, { method: "POST", body: JSON.stringify({ tag_id: tagId }) }), detachChannelTag: (id: string, tagId: number) => req(`/api/channels/${id}/tags/${tagId}`, { method: "DELETE" }), unsubscribeChannel: (id: string) => req(`/api/channels/${id}/subscription`, { method: "DELETE" }), discoveredChannels: (): Promise => req("/api/channels/discovery"), subscribeChannel: (id: string) => req(`/api/channels/${id}/subscribe`, { method: "POST" }), // --- channel page (About detail + ephemeral exploration of un-subscribed channels) --- channelDetail: (id: string): Promise => req(`/api/channels/${id}`), // Open an un-subscribed channel for browsing: ingest a page of its uploads (newest-first, // marked via_explore) + record the exploration. `pageToken` continues into older uploads // ("load more"). `quiet` so a quota-reserve 429 / load error renders inline on the page. exploreChannel: (id: string, pageToken?: string | null): Promise => { const p = new URLSearchParams(); if (pageToken) p.set("page_token", pageToken); const qs = p.toString(); return req( `/api/channels/${id}/explore${qs ? `?${qs}` : ""}`, { method: "POST" }, { quiet: true }, ); }, // --- per-user channel blocklist (excluded from live search + feed/explore) --- blockedChannels: (): Promise< { id: string; title: string | null; handle: string | null; thumbnail_url: string | null }[] > => req("/api/channels/blocked/list"), blockChannel: (id: string) => req(`/api/channels/${id}/block`, { method: "POST" }), unblockChannel: (id: string) => req(`/api/channels/${id}/block`, { method: "DELETE" }), };