fp/services/next/app/lib/streams.ts

414 lines
12 KiB
TypeScript

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<IStream> {
// 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<IStream[]> {
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<IStream> {
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<IStream[]> {
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<IStream> {
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<IStream[]> {
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<number> {
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<IStream[]> {
// 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
// }
// }