import { postgrestLocalUrl, postgrestUrl, siteUrl, strapiUrl } from './constants'; import { getSafeDate } from './dates'; import qs from 'qs'; import { IStream } from '@futureporn/types'; const fetchStreamsOptions = { next: { tags: ['streams'], revalidation: 1 } } export async function getStreamByUUID(uuid: string): Promise { // const query = qs.stringify({ // filters: { // cuid: { // $eq: cuid // } // }, // pagination: { // limit: 1 // }, // populate: { // vtuber: { // fields: ['slug', 'displayName'] // }, // tweet: { // fields: ['isChaturbateInvite', 'isFanslyInvite', 'url'] // }, // vods: { // fields: ['note', 'cuid', 'publishedAt'], // populate: { // tagVodRelations: { // fields: ['id'] // }, // timestamps: '*' // } // } // } // }); const query = `select=*,vods(*),vtuber:vtubers(slug,display_name,id,image,image_blur)&uuid=eq.${uuid}&limit=1` const res = await fetch(`${postgrestUrl}/streams?${query}`); const json = await res.json(); console.log(json) return json[0]; } export function getUrl(stream: IStream, slug: string, date: string): string { return `${siteUrl}/vt/${slug}/stream/${getSafeDate(date)}` } export function getPaginatedUrl(): (slug: string, pageNumber: number) => string { return (slug: string, pageNumber: number) => { return `${siteUrl}/vt/${slug}/streams/${pageNumber}` } } export function getLocalizedDate(stream: IStream): string { return new Date(stream.date).toLocaleDateString() } export async function getStreamsForYear(year: number): Promise { const startOfYear = new Date(year, 0, 0); const endOfYear = new Date(year, 11, 31); const pageSize = 100; // Number of records per page let currentPage = 0; let allStreams: IStream[] = []; while (true) { const query = qs.stringify({ filters: { date: { $gte: startOfYear, $lte: endOfYear, }, }, populate: { vtuber: { fields: ['displayName'] } }, pagination: { page: currentPage, pageSize: pageSize, } }); const res = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions); if (!res.ok) { // Handle error if needed console.error('here is the res.body') console.error((await res.text())); throw new Error(`Error fetching streams: ${res.status}`); } const json = await res.json(); const streams = json as IStream[]; if (streams.length === 0) { // No more records, break the loop break; } allStreams = [...allStreams, ...streams]; currentPage += pageSize; } return allStreams; } export async function getStream(id: number): Promise { const query = qs.stringify({ filters: { id: { $eq: id } } }); const res = await fetch(`${postgrestUrl}/vods?${query}`, fetchStreamsOptions); const json = await res.json(); return json.data; } export async function getAllStreams(archiveStatuses = ['missing', 'issue', 'good']): Promise { throw new Error('getAllStreams function is not performant. please use something more efficient.') const pageSize = 100; // Adjust this value as needed const sortDesc = true; // Adjust the sorting direction as needed const allStreams: IStream[] = []; let currentPage = 1; while (true) { const query = qs.stringify({ populate: { vtuber: { fields: ['slug', 'displayName', 'image', 'imageBlur', 'themeColor'], }, muxAsset: { fields: ['playbackId', 'assetId'], }, thumbnail: { fields: ['cdnUrl', 'url'], }, tagstreamRelations: { fields: ['tag'], populate: ['tag'], }, videoSrcB2: { fields: ['url', 'key', 'uploadId', 'cdnUrl'], }, tweet: { fields: ['isChaturbateInvite', 'isFanslyInvite'] } }, filters: { archiveStatus: { '$in': archiveStatuses } }, sort: { date: sortDesc ? 'desc' : 'asc', }, pagination: { pageSize, page: currentPage, }, }); const response = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions); const responseData = await response.json(); if (!responseData.data || responseData.data.length === 0) { // No more data to fetch break; } allStreams.push(...responseData.data); currentPage++; } return allStreams; } export async function getStreamForVtuber(vtuberId: number, safeDate: string): Promise { const query = qs.stringify({ populate: { vods: { fields: [ 'id', 'date' ] }, tweet: { fields: [ 'id' ] } } }); const response = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions); if (response.status !== 200) throw new Error('network fetch error while attempting to getStreamForVtuber'); const responseData = await response.json(); return responseData; } export async function getAllStreamsForVtuber(vtuberId: number, archiveStatuses = ['missing', 'issue', 'good']): Promise { const maxRetries = 3; let retries = 0; let allStreams: IStream[] = []; let currentPage = 1; while (retries < maxRetries) { try { const query = qs.stringify({ populate: '*', filters: { archiveStatus: { '$in': archiveStatuses }, vtuber: { id: { $eq: vtuberId } } }, sort: { date: 'desc', }, pagination: { pageSize: 100, page: currentPage, }, }); // console.log(`strapiUrl=${strapiUrl}`) const response = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions) if (response.status !== 200) { // If the response status is not 200 (OK), consider it a network failure const bod = await response.text(); console.log(response.status); console.log(bod); retries++; continue; } const responseData = await response.json(); if (!responseData.data || responseData.data.length === 0) { // No more data to fetch break; } allStreams.push(...responseData.data); currentPage++; } catch (error) { // Network failure or other error occurred retries++; } } if (retries === maxRetries) { throw new Error(`Failed to fetch streams after ${maxRetries} retries.`); } return allStreams; } /** * Used as table data on /archive page. * .pageIndex, pagination.pageSize */ export async function fetchStreamData({ pageIndex, pageSize }: { pageIndex: number, pageSize: number }) { // console.log(`fetchStreamData() invoked`) const offset = pageIndex * pageSize; // const query = qs.stringify({ // populate: { // vtuber: { // fields: ['slug', 'displayName', 'publishedAt', 'image', 'imageBlur'] // } // }, // filters: { // vtuber: { // publishedAt: { // $notNull: true // } // } // }, // pagination: { // start: offset, // limit: pageSize, // withCount: true // }, // sort: ['date:desc'] // }) const query = 'select=*,vtuber:vtubers(id,slug,image,image_blur,theme_color,display_name)' const response = await fetch( `${postgrestUrl}/streams?${query}` ); const data = await response.json(); // console.log(data) const filtered = data.filter((datum: any) => !!datum?.vtuber) const d = { rows: filtered, pageCount: Math.ceil(filtered.length / pageSize), rowCount: filtered, } // console.log(`fetchStreamData with pageIndex=${pageIndex}, pageSize=${pageSize}\n\n${JSON.stringify(d, null, 2)}`) return d; } export async function getStreamCountForVtuber(vtuberId: number, archiveStatuses = ['missing', 'issue', 'good']): Promise { if (!vtuberId) throw new Error(`getStreamCountForVtuber requires a vtuberId, but it was undefined.`); const res = await fetch( `${postgrestLocalUrl}/streams?vtuber_id=eq.${vtuberId}&archive_status=in.(${archiveStatuses.join(',')})&limit=1`, Object.assign(fetchStreamsOptions, { headers: { 'Prefer': 'count=exact' } }) ) const total = res.headers.get('Content-Range')?.split('/').at(-1) const data = await res.json() // console.log(`getStreamCountForVtuber with vtuberId=${vtuberId}, archiveStatuses=${archiveStatuses}, Content-Range=${res.headers.get('Content-Range')}`) // console.log(JSON.stringify(data, null, 2)) if (!total) { console.error(`Failed to getStreamCountForVtuber-- total was falsy.`) return 1; } else return parseInt(total)+1; } export async function getStreamsForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25, sortDesc = true): Promise { // console.log(`getStreamsForVtuber() with strapiUrl=${strapiUrl}`) const query = qs.stringify( { populate: { vtuber: { fields: [ 'id', ] } }, filters: { vtuber: { id: { $eq: vtuberId } } }, pagination: { page: page, pageSize: pageSize }, sort: { date: (sortDesc) ? 'desc' : 'asc' } } ) const res = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions) const data = await res.json() // console.log(data) return data } // /** // * This returns stale data, because futureporn-historian is broken. // * @todo get live data from historian // * @see https://gitea.futureporn.net/futureporn/futureporn-historian/issues/1 // */ // export async function getProgress(vtuberSlug: string): Promise<{ complete: number; total: number }> { // const query = qs.stringify({ // filters: { // vtuber: { // slug: { // $eq: vtuberSlug // } // } // } // }) // const data = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions) // .then((res) => res.json()) // .then((g) => { // return g // }) // const total = data.meta.pagination.total // return { // complete: total, // total: total // } // }