Skip to content

Commit

Permalink
v2.3.0
Browse files Browse the repository at this point in the history
 feat(watched channel): title regex by @Zibbp
  • Loading branch information
TheFrodo committed Jun 3, 2024
1 parent 53a9a74 commit 27df547
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 322 deletions.
11 changes: 10 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [3000]
"appPort": ["0.0.0.0:3000:3000"]
"appPort": [
"0.0.0.0:3000:3000"
],
"customizations": {
"vscode": {
"extensions": [
"eamodio.gitlens"
]
}
}

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
Expand Down
Empty file added .vscode/settings.json
Empty file.
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

0 comments on commit 27df547

Please sign in to comment.