Skip to content
This repository has been archived by the owner on Dec 24, 2024. It is now read-only.

feat(watched channel): title regex #28

Merged
merged 5 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
419 changes: 133 additions & 286 deletions package-lock.json

Large diffs are not rendered by default.

35 changes: 17 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,39 @@
"lint": "next lint"
},
"dependencies": {
"@mantine/carousel": "^7.5.3",
"@mantine/core": "^7.5.3",
"@mantine/dates": "^7.5.3",
"@mantine/form": "^7.5.3",
"@mantine/hooks": "^7.5.3",
"@mantine/notifications": "^7.5.3",
"@mantine/carousel": "^7.8.0",
"@mantine/core": "^7.8.0",
"@mantine/dates": "^7.8.0",
"@mantine/form": "^7.8.0",
"@mantine/hooks": "^7.8.0",
"@mantine/notifications": "^7.8.0",
"@tabler/icons-react": "^2.47.0",
"@tanstack/react-query": "^5.28.9",
"@tanstack/react-query-devtools": "^5.28.10",
"@types/node": "20.12.2",
"@types/react": "18.2.73",
"@types/react-dom": "18.2.23",
"@vidstack/react": "^1.10.7",
"@tanstack/react-query": "^5.29.2",
"@tanstack/react-query-devtools": "^5.29.2",
"@types/node": "20.12.7",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
"@vidstack/react": "^1.11.17",
"axios": "^1.6.8",
"clsx": "^2.1.0",
"cookies-next": "^2.1.1",
"dayjs": "^1.11.10",
"embla-carousel-react": "^8.0.0",
"embla-carousel-react": "^8.0.2",
"events": "^3.3.0",
"lodash": "^4.17.21",
"mantine-datatable": "^7.6.1",
"mantine-datatable": "^7.8.1",
"media-icons": "^0.10.0",
"next": "^14.1.4",
"plyr-react": "^5.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.51.2",
"typescript": "5.4.3",
"vidstack": "^1.10.7",
"react-hook-form": "^7.51.3",
"typescript": "5.4.5",
"zustand": "^4.5.2"
},
"devDependencies": {
"postcss": "^8.4.38",
"postcss-preset-mantine": "^1.13.0",
"postcss-preset-mantine": "^1.14.4",
"postcss-simple-vars": "^7.0.1"
}
}
115 changes: 109 additions & 6 deletions src/components/Admin/Watched/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import {
ActionIcon,
Box,
Button,
Checkbox,
Divider,
Grid,
Group,
Loader,
MultiSelect,
NumberInput,
Select,
SimpleGrid,
Switch,
Text,
TextInput,
Expand All @@ -16,7 +21,9 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useApi } from "../../../hooks/useApi";
import GanymedeLoader from "../../Utils/GanymedeLoader";
import { LiveTitleRegex } from "../../../ganymede-defs";
import { IconPlus, IconTrash } from "@tabler/icons-react";
import classes from "./Watched.module.css"

const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
const { handleSubmit } = useForm();
Expand All @@ -42,6 +49,7 @@ const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
[]
);
const [selectedTwitchCategories, setSelectedTwitchCategories] = useState([]);
const [liveTitleRegexes, setLiveTitleRegexes] = useState<LiveTitleRegex[]>([]);

const qualityOptions = [
{ label: "Best", value: "best" },
Expand All @@ -66,6 +74,7 @@ const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
setRenderChat(watched?.render_chat);
setDownloadSubOnly(watched?.download_sub_only);
setMaxVideoAge(watched?.video_age);
setLiveTitleRegexes(watched?.edges.title_regex)

if (watched?.edges?.categories) {
const tmpArr = [];
Expand Down Expand Up @@ -100,6 +109,7 @@ const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
download_sub_only: downloadSubOnly,
categories: selectedTwitchCategories,
max_age: maxVideoAge,
regex: liveTitleRegexes
},
withCredentials: true,
},
Expand Down Expand Up @@ -140,6 +150,7 @@ const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
download_sub_only: downloadSubOnly,
categories: selectedTwitchCategories,
max_age: maxVideoAge,
regex: liveTitleRegexes
},
withCredentials: true,
},
Expand All @@ -163,6 +174,7 @@ const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
download_sub_only: downloadSubOnly,
categories: selectedTwitchCategories,
max_age: maxVideoAge,
regex: liveTitleRegexes
},
withCredentials: true,
},
Expand Down Expand Up @@ -213,8 +225,13 @@ const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
},
});

const getTwitchCategoriesClick = () => {
getTwitchCategories()
}

// Fetch categories
const { data: twitchCategoriesResp } = useQuery({
const { data: twitchCategoriesResp, refetch: getTwitchCategories } = useQuery({
enabled: false,
queryKey: ["admin-categories"],
queryFn: () => {
setTwitchCategoriesLoading(true);
Expand Down Expand Up @@ -373,9 +390,97 @@ const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
onChange={setMaxVideoAge}
/>
</div>
<Divider my="sm" />
{/* title regex */}
<Group>
<Title order={5}>Title Regex</Title>
<ActionIcon size="sm" variant="filled" color="green" aria-label="Settings" onClick={() => {
const newRegex: LiveTitleRegex = {
apply_to_videos: false,
case_sensitive: false,
negative: false,
regex: ""
}
setLiveTitleRegexes(liveTitleRegexes => [...(liveTitleRegexes ?? []), newRegex])
}}>
<IconPlus style={{ width: '70%', height: '70%' }} stroke={1.5} />
</ActionIcon>
</Group>
<div>
<Text size="sm">Use regex to filter and match specific patterns in livestream and video titles. See <a className={classes.link} href="https://github.com/Zibbp/ganymede/wiki/Watched-Channel-Title-Regex" target="_blank">wiki</a> for more information.</Text>
</div>

<div>
{liveTitleRegexes && liveTitleRegexes.map((regex: LiveTitleRegex, index) => (
<div>
<Grid grow>
<Grid.Col span={11}>
<TextInput
label="Regex"
placeholder="(?i:rerun)"
value={regex.regex}
onChange={(e) => {
const updatedRegexes = [...liveTitleRegexes];
updatedRegexes[index].regex = e.currentTarget.value;
setLiveTitleRegexes(updatedRegexes)
}}
/>
<Group mt={7}>
<Checkbox
defaultChecked
label="Negative"
description="Invert match"
color="violet"
checked={regex.negative}
onChange={(e) => {
const updatedRegexes = [...liveTitleRegexes];
updatedRegexes[index].negative = e.currentTarget.checked;
setLiveTitleRegexes(updatedRegexes)
}}
/>
<Checkbox
defaultChecked
label="Apply to video downloads"
description="Applies to live streams only by default"
color="violet"
checked={regex.apply_to_videos}
onChange={(e) => {
const updatedRegexes = [...liveTitleRegexes];
updatedRegexes[index].apply_to_videos = e.currentTarget.checked;
setLiveTitleRegexes(updatedRegexes)
}}
/>
</Group>
</Grid.Col>
<Grid.Col span={1}>
<Group mt={25}>
<ActionIcon size="lg" variant="filled" color="red" aria-label="Settings" onClick={() => {
const updatedRegexs = [...liveTitleRegexes]
updatedRegexs.splice(index, 1)
setLiveTitleRegexes(updatedRegexs)
}}>
<IconTrash style={{ width: '70%', height: '70%' }} stroke={1.5} />
</ActionIcon>
</Group>
</Grid.Col>
</Grid>
</div>
))}
</div>
<Divider my="sm" />

<Group>
<Title order={5}>Categories</Title>
</Group>
<div>
<Text size="sm">Archive videos from these categories. Leave blank to archive all categories. Does not apply to live streams.</Text>
</div>

<div>
{twitchCategoriesLoading || formattedTwitchCategories.length == 0 ? (
<Loader color="violet" />
{formattedTwitchCategories.length == 0 ? (
<Button variant="filled" color="violet" onClick={(e) => getTwitchCategoriesClick()}
loading={twitchCategoriesLoading}
>Load categories</Button>
) : (
<MultiSelect
searchable
Expand All @@ -384,9 +489,7 @@ const AdminWatchedDrawer = ({ handleClose, watched, mode }) => {
onChange={setSelectedTwitchCategories}
data={formattedTwitchCategories}
comboboxProps={{ position: 'top', middlewares: { flip: false, shift: false } }}
label="Archive specific video categories"
placeholder="Search for a category"
description="Archive only videos from these categories. Leave blank to archive all categories. Does not apply to live streams."
clearButtonLabel="Clear selection"
clearable
/>
Expand Down
3 changes: 3 additions & 0 deletions src/components/Admin/Watched/Watched.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@
}
.queueDrawer {
overflow-y: scroll;
}
.link {
color: var(--mantine-color-blue-6);
}
18 changes: 8 additions & 10 deletions src/components/Vod/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useQuery } from "@tanstack/react-query";
import '@vidstack/react/player/styles/default/theme.css';
import '@vidstack/react/player/styles/default/layouts/video.css';

import { MediaPlayer, MediaPlayerInstance, MediaProvider, Poster, Track } from '@vidstack/react';
import { MediaPlayer, MediaPlayerInstance, MediaProvider, MediaSrc, Poster, Track, VideoMimeType } from '@vidstack/react';
import { defaultLayoutIcons, DefaultVideoLayout } from '@vidstack/react/player/layouts/default';
import TheaterModeIcon from "./TheaterModeIcon";
import { escapeURL } from "../../util/util";
Expand All @@ -24,7 +24,7 @@ const NewVideoPlayer = ({ vod }: any) => {

const player = useRef<MediaPlayerInstance>(null)

const [videoSource, setVideoSource] = useState([{ src: "", type: "" }]);
const [videoSource, setVideoSource] = useState<MediaSrc>();
const [videoType, setVideoType] = useState<string>("");
const [videoPoster, setVideoPoster] = useState<string>("");
const [videoTitle, setVideoTitle] = useState<string>("");
Expand Down Expand Up @@ -97,17 +97,15 @@ const NewVideoPlayer = ({ vod }: any) => {
if (!player) return;

const ext = vod.video_path.substr(vod.video_path.length - 4);
let type = "video/mp4";
let type: VideoMimeType = "video/mp4";
if (ext == "m3u8") {
type = "application/x-mpegURL";
type = "application/x-mpegurl";
}

setVideoSource([
{
src: `${publicRuntimeConfig.CDN_URL}${escapeURL(vod.video_path)}`,
type: type,
},
]);
setVideoSource({
src: `${publicRuntimeConfig.CDN_URL}${escapeURL(vod.video_path)}`,
type: type
})
setVideoType(type);
setVideoTitle(vod.title);

Expand Down
21 changes: 21 additions & 0 deletions src/ganymede-defs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,24 @@ export interface ProxyItem {
url: string;
header: string;
}

export interface Chapter {
startTime: string;
endTime: string;
text: string;
}

export interface ChapterData {
id: string;
type: string;
title: string;
start?: number;
end: number;
edges: any;
}

export interface LiveTitleRegex {
regex: string;
negative: boolean;
apply_to_videos: boolean;
}
2 changes: 1 addition & 1 deletion src/pages/vods/[vodId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import classes from "./vodId.module.css"

async function fetchVod(vodId: string) {
return useApi(
{ method: "GET", url: `/api/v1/vod/${vodId}?with_channel=true` },
{ method: "GET", url: `/api/v1/vod/${vodId}?with_channel=true&with_chapters=true&with_muted_segments=true` },
false
).then((res) => res?.data);
}
Expand Down
Loading