import { siteUrl, strapiUrl } from './constants'; import { getSafeDate } from './dates'; import qs from 'qs'; const fetchStreamsOptions = { next: { tags: ['streams'], revalidation: 1 } } export async function getStreamByCuid(cuid: 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 res = await fetch(`${strapiUrl}/api/streams?${query}`); const json = await res.json(); return json.data[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.attributes.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(`${strapiUrl}/api/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 IStreamsResponse; if (streams.data.length === 0) { // No more records, break the loop break; } allStreams = [...allStreams, ...streams.data]; currentPage += pageSize; } return allStreams; } export async function getStream(id: number): Promise { const query = qs.stringify({ filters: { id: { $eq: id } } }); const res = await fetch(`${strapiUrl}/api/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(`${strapiUrl}/api/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(`${strapiUrl}/api/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(`${strapiUrl}/api/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 }) { 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 response = await fetch( `${strapiUrl}/api/streams?${query}` ); const json = await response.json(); console.log(json) const d = { rows: json.data, pageCount: Math.ceil(json.meta.pagination.total / pageSize), rowCount: json.meta.pagination.total, } // 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.`); // @todo possible performance improvement is to only request the meta field, since we don't use any of the data.attributes const query = qs.stringify( { filters: { vtuber: { id: { $eq: vtuberId } }, archiveStatus: { '$in': archiveStatuses } } } ) const res = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions) const data = await res.json() // console.log(`getStreamCountForVtuber with archiveStatuses=${archiveStatuses}`) // console.log(JSON.stringify(data, null, 2)) return data.meta.pagination.total } 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(`${strapiUrl}/api/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(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions) // .then((res) => res.json()) // .then((g) => { // return g // }) // const total = data.meta.pagination.total // return { // complete: total, // total: total // } // }