import React from "react"; import { useDispatch, useSelector } from "react-redux"; import { subscriptionListFilter } from "../App-Functions.ts"; import { totalNamesPublished, totalVideosPublished, videosPerNamePublished, } from "../components/StatsData.tsx"; import { addVideos, addToHashMap, setCountNewVideos, upsertVideos, upsertVideosBeginning, Video, upsertFilteredVideos, removeFromHashMap, } from "../state/features/videoSlice"; import { setIsLoadingGlobal } from "../state/features/globalSlice"; import { RootState } from "../state/store"; import { fetchAndEvaluateVideos } from "../utils/fetchVideos"; import { RequestQueue } from "../utils/queue"; import { queue } from "../wrappers/GlobalWrapper"; import { QTUBE_PLAYLIST_BASE, QTUBE_VIDEO_BASE, } from "../constants/Identifiers.ts"; import { persistReducer } from "redux-persist"; import { ContentType, VideoListType } from "../state/features/persistSlice.ts"; export const useFetchVideos = () => { const dispatch = useDispatch(); const hashMapVideos = useSelector( (state: RootState) => state.video.hashMapVideos ); const videos = useSelector((state: RootState) => state.video.videos); const filteredVideos = useSelector( (state: RootState) => state.video.filteredVideos ); const checkAndUpdateVideo = React.useCallback( (video: Video) => { const existingVideo = hashMapVideos[video.id + "-" + video.user]; if (!existingVideo) { return true; } else if ( video?.updated && existingVideo?.updated && (!existingVideo?.updated || video?.updated) > existingVideo?.updated ) { return true; } else { return false; } }, [hashMapVideos] ); const getVideo = async ( user: string, videoId: string, content: any, retries = 0 ) => { try { const res = await fetchAndEvaluateVideos({ user, videoId, content, }); if (res?.isValid) { dispatch(addToHashMap(res)); } else { dispatch(removeFromHashMap(videoId)); } } catch (error) { retries = retries + 1; if (retries < 2) { // 3 is the maximum number of retries here, you can adjust it to your needs queue.push(() => getVideo(user, videoId, content, retries + 1)); } else { console.error("Failed to get video after 3 attempts", error); } } }; const getNewVideos = React.useCallback(async () => { try { dispatch(setIsLoadingGlobal(true)); const responseData = await qortalRequest({ action: "SEARCH_QDN_RESOURCES", mode: "ALL", service: "DOCUMENT", query: "${QTUBE_VIDEO_BASE}", limit: 20, includeMetadata: true, reverse: true, excludeBlocked: true, exactMatchNames: true, }); const latestVideo = videos[0]; if (!latestVideo) return; const findVideo = responseData?.findIndex( (item: any) => item?.identifier === latestVideo?.id ); let fetchAll = responseData; let willFetchAll = true; if (findVideo !== -1) { willFetchAll = false; fetchAll = responseData.slice(0, findVideo); } const structureData = fetchAll.map((video: any): Video => { return { title: video?.metadata?.title, category: video?.metadata?.category, categoryName: video?.metadata?.categoryName, tags: video?.metadata?.tags || [], description: video?.metadata?.description, created: video?.created, updated: video?.updated, user: video.name, videoImage: "", id: video.identifier, }; }); if (!willFetchAll) { dispatch(upsertVideosBeginning(structureData)); } if (willFetchAll) { dispatch(addVideos(structureData)); } setTimeout(() => { dispatch(setCountNewVideos(0)); }, 1000); for (const content of structureData) { if (content.user && content.id) { const res = checkAndUpdateVideo(content); if (res) { queue.push(() => getVideo(content.user, content.id, content)); } } } } catch (error) { console.log(error); } finally { dispatch(setIsLoadingGlobal(false)); } }, [videos, hashMapVideos]); type FilterType = { name?: string; category?: string; subcategory?: string; keywords?: string; contentType?: ContentType; }; const emptyFilters: FilterType = { name: "", category: "", subcategory: "", keywords: "", contentType: "videos", }; const getVideos = React.useCallback( async ( filters = emptyFilters, reset?: boolean, resetFilters?: boolean, limit?: number, videoListType: VideoListType = "all" ) => { emptyFilters.contentType = filters.contentType; try { const { name = "", category = "", subcategory = "", keywords = "", contentType = filters.contentType, }: FilterType = resetFilters ? emptyFilters : filters; let offset = videos.length; if (reset) { offset = 0; } const videoLimit = limit || 20; let defaultUrl = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=${videoLimit}`; if (name) { defaultUrl = defaultUrl + `&name=${name}`; } else if (videoListType === "subscriptions") { const filteredSubscribeList = await subscriptionListFilter(false); filteredSubscribeList.map(sub => { defaultUrl += `&name=${sub.subscriberName}`; }); } if (category) { if (!subcategory) { defaultUrl = defaultUrl + `&description=category:${category}`; } else { defaultUrl = defaultUrl + `&description=category:${category};subcategory:${subcategory}`; } } if (keywords) { defaultUrl = defaultUrl + `&query=${keywords}`; } if (contentType === "playlists") { defaultUrl = defaultUrl + `&service=PLAYLIST`; defaultUrl = defaultUrl + `&identifier=${QTUBE_PLAYLIST_BASE}`; } else { defaultUrl = defaultUrl + `&service=DOCUMENT`; defaultUrl = defaultUrl + `&identifier=${QTUBE_VIDEO_BASE}`; } // const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=${videoLimit}&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}` const url = defaultUrl; const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", }, }); const responseData = await response.json(); // const responseData = await qortalRequest({ // action: "SEARCH_QDN_RESOURCES", // mode: "ALL", // service: "DOCUMENT", // query: "${QTUBE_VIDEO_BASE}", // limit: 20, // includeMetadata: true, // offset: offset, // reverse: true, // excludeBlocked: true, // exactMatchNames: true, // name: names // }) const structureData = responseData.map((video: any): Video => { return { title: video?.metadata?.title, service: video?.service, category: video?.metadata?.category, categoryName: video?.metadata?.categoryName, tags: video?.metadata?.tags || [], description: video?.metadata?.description, created: video?.created, updated: video?.updated, user: video.name, videoImage: "", id: video.identifier, }; }); if (reset) { dispatch(addVideos(structureData)); } else { dispatch(upsertVideos(structureData)); } for (const content of structureData) { if (content.user && content.id) { const res = checkAndUpdateVideo(content); if (res) { queue.push(() => getVideo(content.user, content.id, content)); } } } } catch (error) { console.log({ error }); } }, [videos, hashMapVideos] ); const getVideosFiltered = React.useCallback( async (filterValue: string) => { try { const offset = filteredVideos.length; const replaceSpacesWithUnderscore = filterValue.replace(/ /g, "_"); const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${replaceSpacesWithUnderscore}&identifier=${QTUBE_VIDEO_BASE}&limit=10&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`; const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", }, }); const responseData = await response.json(); // const responseData = await qortalRequest({ // action: "SEARCH_QDN_RESOURCES", // mode: "ALL", // service: "DOCUMENT", // query: replaceSpacesWithUnderscore, // identifier: "${QTUBE_VIDEO_BASE}", // limit: 20, // includeMetadata: true, // offset: offset, // reverse: true, // excludeBlocked: true, // exactMatchNames: true, // name: names // }) const structureData = responseData.map((video: any): Video => { return { title: video?.metadata?.title, category: video?.metadata?.category, categoryName: video?.metadata?.categoryName, tags: video?.metadata?.tags || [], description: video?.metadata?.description, created: video?.created, updated: video?.updated, user: video.name, videoImage: "", id: video.identifier, }; }); dispatch(upsertFilteredVideos(structureData)); for (const content of structureData) { if (content.user && content.id) { const res = checkAndUpdateVideo(content); if (res) { queue.push(() => getVideo(content.user, content.id, content)); } } } } catch (error) { console.log(error); } }, [filteredVideos, hashMapVideos] ); const checkNewVideos = React.useCallback(async () => { try { const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`; const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", }, }); const responseData = await response.json(); // const responseData = await qortalRequest({ // action: "SEARCH_QDN_RESOURCES", // mode: "ALL", // service: "DOCUMENT", // query: "${QTUBE_VIDEO_BASE}", // limit: 20, // includeMetadata: true, // reverse: true, // excludeBlocked: true, // exactMatchNames: true, // name: names // }) const latestVideo = videos[0]; if (!latestVideo) return; const findVideo = responseData?.findIndex( (item: any) => item?.identifier === latestVideo?.id ); if (findVideo === -1) { dispatch(setCountNewVideos(responseData.length)); return; } const newArray = responseData.slice(0, findVideo); dispatch(setCountNewVideos(newArray.length)); return; } catch (error) { console.log(error); } }, [videos]); const getVideosCount = React.useCallback(async () => { try { const url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&limit=0&service=DOCUMENT&identifier=${QTUBE_VIDEO_BASE}`; const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", }, }); const responseData = await response.json(); totalVideosPublished.value = responseData.length; const uniqueNames = new Set(responseData.map(video => video.name)); totalNamesPublished.value = uniqueNames.size; videosPerNamePublished.value = totalVideosPublished.value / totalNamesPublished.value; } catch (error) { console.log({ error }); } }, []); return { getVideos, checkAndUpdateVideo, getVideo, hashMapVideos, getNewVideos, checkNewVideos, getVideosFiltered, getVideosCount, }; };