channel: implemented basic backend data fetch

nextui
Guus van Meerveld 5 months ago
parent 8a6097302b
commit 8d112266f1

@ -0,0 +1,23 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { FC } from "react";
import { useClient } from "@/hooks/useClient";
import { Container } from "@/components/Container";
export const ChannelPage: FC<{ channelId: string }> = ({ channelId }) => {
const client = useClient();
const { error } = useQuery({
queryKey: ["channel", channelId],
queryFn: () => {
return client.getChannel(channelId);
}
});
console.log(error);
return <Container>{channelId}</Container>;
};

@ -0,0 +1,16 @@
import { NextPage } from "next";
import { Suspense } from "react";
import { ChannelPage } from "./ChannelPage";
const Page: NextPage<{ params: { id: string } }> = ({ params }) => {
return (
<>
<Suspense>
<ChannelPage channelId={params.id} />
</Suspense>
</>
);
};
export default Page;

@ -1,3 +1,4 @@
import { Channel } from "@/client/typings/channel";
import { Comments } from "@/client/typings/comment";
import { SearchResults } from "@/client/typings/search";
import { SearchOptions } from "@/client/typings/search/options";
@ -14,6 +15,8 @@ export interface ConnectedAdapter {
getWatchable(videoId: string): Promise<Watchable>;
getComments(videoId: string, repliesToken?: string): Promise<Comments>;
getChannel(channelId: string): Promise<Channel>;
}
export default interface Adapter {

@ -7,6 +7,7 @@ import Adapter, { ApiType } from "@/client/adapters";
import { Suggestions } from "@/client/typings/search/suggestions";
import Transformer from "./transformer";
import Channel, { ChannelModel } from "./typings/channel";
import Comments, { CommentsModel } from "./typings/comments";
import Search, { SearchModel } from "./typings/search";
import Stream, { StreamModel } from "./typings/stream";
@ -128,6 +129,28 @@ const getComments = async (
return data;
};
export const getChannel = async (
apiBaseUrl: string,
channelId: string,
nextpage?: string
): Promise<Channel> => {
const searchParams = new URLSearchParams();
let url;
if (nextpage) {
url = new URL(path.join("nextpage", "channel", channelId), apiBaseUrl);
searchParams.append("nextpage", nextpage);
} else url = new URL(path.join("channel", channelId), apiBaseUrl);
const response = await ky.get(url, { searchParams });
const json = await response.json();
const data = ChannelModel.parse(json);
return data;
};
const adapter: Adapter = {
apiType: ApiType.Piped,
@ -177,6 +200,10 @@ const adapter: Adapter = {
return getComments(url, videoId, repliesToken).then(
Transformer.comments
);
},
async getChannel(channelId) {
return getChannel(url, channelId, undefined).then(Transformer.channel);
}
};
}

@ -1,3 +1,4 @@
import { Channel } from "@/client/typings/channel";
import { Comments } from "@/client/typings/comment";
import {
ChannelItem,
@ -15,6 +16,7 @@ import {
} from "@/utils/parseIdFromUrl";
import { parseRelativeTime } from "@/utils/parseRelativeTime";
import PipedChannel from "./typings/channel";
import PipedComments from "./typings/comments";
import PipedItem from "./typings/item";
import PipedSearch from "./typings/search";
@ -88,7 +90,7 @@ export default class Transformer {
author: {
id: channelId,
name: data.uploaderName,
avatar: data.uploaderAvatar
avatar: data.uploaderAvatar ?? undefined
}
};
}
@ -162,4 +164,16 @@ export default class Transformer {
}))
};
}
public static channel(data: PipedChannel): Channel {
return {
name: data.name,
id: data.id,
description: data.description,
avatar: data.avatarUrl,
subscribers: data.subscriberCount,
banner: data.bannerUrl,
verified: data.verified
};
}
}

@ -0,0 +1,29 @@
import z from "zod";
import { ItemModel } from "./item";
export const tabEnum = [
"shorts",
"albums",
"playlists",
"livestreams"
] as const;
export const tabType = z.enum(tabEnum);
export const ChannelModel = z.object({
id: z.string(),
name: z.string(),
avatarUrl: z.string().url(),
bannerUrl: z.string().url(),
description: z.string(),
nextpage: z.string().nullable(),
subscriberCount: z.number(),
verified: z.boolean(),
relatedStreams: ItemModel.array(),
tabs: z.object({ name: tabType, data: z.string() }).array()
});
type Channel = z.infer<typeof ChannelModel>;
export default Channel;

@ -7,7 +7,7 @@ export const VideoModel = z.object({
uploaded: z.number(),
uploadedDate: z.string().nullable(), // The date the video was uploaded
uploaderName: z.string(),
uploaderAvatar: z.string().url(), // The avatar of the channel of the video
uploaderAvatar: z.string().url().nullable(), // The avatar of the channel of the video
uploaderUrl: z.string(), // The URL of the channel of the video
uploaderVerified: z.boolean(), // Whether or not the channel of the video is verified
url: z.string(), // The URL of the video

@ -1,6 +1,7 @@
import Adapter, { ApiType, ConnectedAdapter } from "./adapters";
import InvidiousAdapter from "./adapters/invidious";
import PipedAdapter from "./adapters/piped";
import { Channel } from "./typings/channel";
import { Comments } from "./typings/comment";
import { SearchResults } from "./typings/search";
import { SearchOptions } from "./typings/search/options";
@ -91,4 +92,10 @@ export default class Client {
return await adapter.getComments(videoId, repliesToken);
}
public async getChannel(channelId: string): Promise<Channel> {
const adapter = this.getBestAdapter();
return await adapter.getChannel(channelId);
}
}

@ -0,0 +1,9 @@
import { Author } from "./author";
export interface Channel extends Author {
id: string;
subscribers: number;
description: string;
avatar: string;
banner?: string;
}

@ -8,7 +8,7 @@ export const useClient = (): Client => {
() =>
new Client([
// { baseUrl: "https://invidious.fdn.fr/", type: ApiType.Invidious }
{ baseUrl: "https://pipedapi.kavin.rocks", type: ApiType.Piped }
{ baseUrl: "https://pipedapi.drgns.space/", type: ApiType.Piped }
])
);

Loading…
Cancel
Save