This commit is contained in:
Dark Steveneq
2025-08-29 22:10:22 +02:00
parent a3a631d9ff
commit f020ce8d40
10 changed files with 583 additions and 339 deletions

627
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,6 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
@import 'tailwindcss'; @import 'tailwindcss';
@theme {
--font-display: "Montserrat", sans-serif;
}

View File

@@ -6,6 +6,6 @@
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div class="contents">%sveltekit.body%</div> <div class="contents font-display">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@@ -16,7 +16,7 @@
{#if href != null} {#if href != null}
<a class=" <a class="
text-white bg-indigo-400 text-white bg-indigo-400
cursor-grab cursor-pointer
hover:bg-indigo-500 hover:bg-indigo-500
not-motion-reduce:transition-colors not-motion-reduce:transition-colors
shadow shadow-stone-900 shadow shadow-stone-900

View File

@@ -17,18 +17,14 @@
} = $props(); } = $props();
title = title.trim() title = title.trim()
if (title.length > titleLength) {
title = title.slice(0, titleLength).trim() + "...";
}
</script> </script>
<a class=" <a class="
bg-white bg-white
cursor-grab cursor-pointer
shadow-sm shadow-black shadow-sm shadow-black
flex flex-col flex flex-col
px-3 py-2 px-3 py-2
min-w-md max-w-md
rounded-md rounded-md
" href="/watch/{id}"> " href="/watch/{id}">
{#if thumbnail != null} {#if thumbnail != null}

View File

@@ -0,0 +1,66 @@
type Video = {
id: string
title: string
description: string
mime: string
uploader?: string
uploaderID?: string
views?: number
likes?: number
dislikes?: number
creationDate?: number
videoURL?: string
thumbnailURL?: string
duration?: number
hasVideo: boolean
width?: number
height?: number
fps?: number
};
type VideoComment = {
id: string
parentID: string
videoID: string
content: string
uploader: string
uploaderID?: string
likes?: number
dislikes?: number
creationDate: number
editDate?: number
};
interface IVideoList {
videos: Video[]
HasMore(): boolean
HasPrevious(): boolean
Next(): void
Previous(): void
};
interface ICommentList {
id: string
comments: VideoComment[]
HasMore(): boolean
Next(): void
}
interface IProvider {
GetVideos(): Promise<IVideoList | null>
GetVideoInfo(id: string): Promise<Video | null>
GetComments(id: string): Promise<ICommentList | null>
SearchVideos(query: string): Promise<IVideoList | null>
CanUpload(): Promise<boolean>
// TODO: Upload support
};
export { type ICommentList, type IProvider, type IVideoList, type Video, type VideoComment };

View File

@@ -0,0 +1,135 @@
import { type ICommentList, type IProvider, type IVideoList, type Video } from "./iProvider";
class YouvideoVideoList implements IVideoList {
videos: Video[];
constructor(initialVideos: Video[]) {
this.videos = initialVideos;
}
HasMore(): boolean {
return false;
}
HasPrevious(): boolean {
return false;
}
Next(): void {
}
Previous(): void {
}
}
type unprocessedVideo = {
cached: boolean
description: string
extension?: string
id: string
metadata: {
duration: number
fps: number
size: number[]
}
name: string
};
class YouvideoProvider implements IProvider {
instanceURL: URL
constructor(url: string) {
this.instanceURL = new URL(url);
}
async GetVideos(): Promise<IVideoList | null> {
const resp = await fetch(new URL("/api/videos", this.instanceURL))
if (!resp.ok) {
return null;
}
let initalVideos: Video[] = [];
const json: unprocessedVideo[] = await resp.json();
json.forEach(video => {
let convertedVideo: Video = {
description: video.description,
hasVideo: true,
id: video.id,
mime: "",
title: video.name,
fps: video.metadata.fps,
duration: video.metadata.duration,
uploader: "Youvideo Provider"
};
initalVideos.push(convertedVideo);
});
return new YouvideoVideoList(initalVideos);
}
async GetVideoInfo(id: string): Promise<Video | null> {
const resp = await fetch(new URL("/video/" + id, this.instanceURL));
if (!resp.ok) {
return null;
}
const video: unprocessedVideo = await resp.json();
if (video.extension == null) {
return null;
}
let convertedVideo: Video = {
description: video.description,
hasVideo: true,
id: video.id,
mime: "",
title: video.name,
fps: video.metadata.fps,
duration: video.metadata.duration,
uploader: "Youvideo Provider",
videoURL: new URL("/youvideo/api/videofile/with_extension/" + id + video.extension, this.instanceURL).toString()
};
return convertedVideo;
}
async GetComments(id: string): Promise<ICommentList | null> {
return null;
}
async SearchVideos(query: string): Promise<IVideoList | null> {
const resp = await fetch(new URL("/api/videos", this.instanceURL))
if (!resp.ok) {
return null;
}
let initalVideos: Video[] = [];
const json: unprocessedVideo[] = await resp.json();
json.forEach(video => {
let contains = false;
contains = video.description.includes(query) || video.name.includes(query);
// This is like this to allow nicer things
if (!contains) {
return;
}
let convertedVideo: Video = {
description: video.description,
hasVideo: true,
id: video.id,
mime: "",
title: video.name,
fps: video.metadata.fps,
duration: video.metadata.duration,
uploader: "Youvideo Provider",
width: video.metadata.size[0],
height: video.metadata.size[1]
};
initalVideos.push(convertedVideo);
});
return new YouvideoVideoList(initalVideos);
}
async CanUpload(): Promise<boolean> {
return false;
}
}
export { YouvideoProvider };

View File

@@ -12,7 +12,7 @@
</svelte:head> </svelte:head>
<header class="bg-neutral-100 p-2 rounded-b-xl flex flex-row justify-between"> <header class="bg-neutral-100 p-2 rounded-b-xl flex flex-row justify-between">
<a class="text-2xl" href="/">TheyWatch</a> <a class="text-2xl font-bold" href="/">TheyWatch</a>
<nav class="flex flex-row align-middle"> <nav class="flex flex-row align-middle">
<Button href="/upload">Upload video</Button> <Button href="/upload">Upload video</Button>
</nav> </nav>

View File

@@ -1,21 +1,35 @@
<script> <script lang="ts">
import VideoCard from "$lib/components/VideoCard.svelte"; import VideoCard from "$lib/components/VideoCard.svelte";
import type { IVideoList } from "$lib/providers/iProvider";
import { YouvideoProvider } from "$lib/providers/youvideoProvider";
import { onMount } from "svelte";
const provider = new YouvideoProvider("https://youvideo.nonamesoft.xyz/youvideo");
let videolist: IVideoList | null = $state(null);
let error = $state("");
onMount(async () => {
const vl = await provider.GetVideos();
if (vl == null) {
error = "Failed to get videos";
return
}
videolist = vl
})
</script> </script>
<h1>Welcome to SvelteKit</h1> {#if error != ""}
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p> <p class="
w-full p-1 rounded-lg
<div class="flex flex-row gap-x-2"> text-white bg-red-500 text-center font-bold
">{error}</p>
{/if}
<div class="grid grid-cols-5 gap-2">
{#each videolist?.videos ?? [] as video}
<VideoCard <VideoCard
title="How I cloned YouVideo without getting a Cease and Decist" id={video.id}
id="2137" title={video.title}
views={9999999999} uploader={"Youvideo"}
uploader="Ghost Fox"
/>
<VideoCard
title="I got sued..."
id="2138"
views={2137420691337}
uploader="Ghost Fox"
/> />
{/each}
</div> </div>

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import Button from "$lib/components/Button.svelte";
import type { Video } from "$lib/providers/iProvider";
import { YouvideoProvider } from "$lib/providers/youvideoProvider";
import { onMount } from "svelte";
import type { PageProps } from "./$types";
let { params }: PageProps = $props();
const provider = new YouvideoProvider("https://youvideo.nonamesoft.xyz/youvideo");
let video: Video | null = $state(null);
let error: string = $state("");
onMount(async () => {
const vid = await provider.GetVideoInfo(params.id);
if (vid == null) {
error = "Video not found!";
return;
}
video = vid;
})
</script>
{#if error != ""}
<p class="text-center text-3xl font-bold">{error}</p>
<Button href="/">Go back</Button>
{:else}
<video controls src={video?.videoURL ?? ""}>
<track kind="captions">
</video>
<h3 class="font-bold text-xl">{video?.title}</h3>
<h5 class="text-sm">{video?.description}</h5>
{/if}