From fe4ee6a2d7db7f01b92a2c6a6561af560309b612 Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Tue, 28 Jan 2025 17:41:01 +0100 Subject: [PATCH 01/35] Refac jump to an empty player wrapper when opening the EmbeddableVideoPlayer --- frontend/src/components/EmbeddableVideoPlayer.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/EmbeddableVideoPlayer.tsx b/frontend/src/components/EmbeddableVideoPlayer.tsx index 7be85110..53eb198a 100644 --- a/frontend/src/components/EmbeddableVideoPlayer.tsx +++ b/frontend/src/components/EmbeddableVideoPlayer.tsx @@ -28,7 +28,7 @@ const EmbeddableVideoPlayer = ({ videoId }: EmbeddableVideoPlayerProps) => { const [, setSearchParams] = useSearchParams(); - const [refresh, setRefresh] = useState(false); + const [refresh, setRefresh] = useState(true); const [loading, setLoading] = useState(false); const [videoResponse, setVideoResponse] = useState(); @@ -72,8 +72,12 @@ const EmbeddableVideoPlayer = ({ videoId }: EmbeddableVideoPlayerProps) => { })(); }, [videoId, refresh]); + useEffect(() => { + inlinePlayerRef.current?.scrollIntoView(); + }, []); + if (videoResponse === undefined) { - return []; + return
; } const video = videoResponse.data; From b78d881bf66ebe63d6c4c955d210a853633acdae Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Wed, 29 Jan 2025 18:07:28 +0100 Subject: [PATCH 02/35] Fix scrollbar causing page flicker/jiggle --- frontend/src/style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/style.css b/frontend/src/style.css index 5a4f6a5e..e9c59492 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -16,6 +16,8 @@ } html { + width: 100vw; + overflow-x: hidden; height: 100%; scrollbar-color: var(--accent-font-dark) #0000; } From c2cd02e7bd489d3b166d5d1c5085b3944d92883b Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Wed, 29 Jan 2025 18:17:17 +0100 Subject: [PATCH 03/35] Fix channel list having 1rem offset, compared to video list entries --- frontend/src/style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/style.css b/frontend/src/style.css index e9c59492..e8b91ced 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -959,7 +959,6 @@ video:-webkit-full-screen { } .channel-banner img { - margin-top: 1rem; width: 100%; } From 9e4f9de11920048a003bb1dfdc60d241ce2a3f08 Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Wed, 29 Jan 2025 18:31:05 +0100 Subject: [PATCH 04/35] Refac invert filter toggle text to be inline with other pages --- frontend/src/pages/Home.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 725f64bb..a5d83c31 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -184,7 +184,7 @@ const Home = () => {

Recent Videos

- +
From 3c06b06960fb73735083ea5ce2a51e700a4134c1 Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Wed, 29 Jan 2025 18:32:11 +0100 Subject: [PATCH 05/35] Refac hide total_hits on channels page until we added total_hits to video list and playlist pages --- frontend/src/pages/Channels.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/Channels.tsx b/frontend/src/pages/Channels.tsx index 55582aed..b113708d 100644 --- a/frontend/src/pages/Channels.tsx +++ b/frontend/src/pages/Channels.tsx @@ -60,7 +60,7 @@ const Channels = () => { const channels = channelListResponse?.data; const pagination = channelListResponse?.paginate; - const channelCount = pagination?.total_hits; + // const channelCount = pagination?.total_hits; const hasChannels = channels?.length !== 0; useEffect(() => { @@ -184,7 +184,7 @@ const Channels = () => { />
- {hasChannels &&

Total channels: {channelCount}

} + {/* {hasChannels &&

Total channels: {channelCount}

} */}
{!hasChannels &&

No channels found...

} From 1c643bef8e206833896c41e52f774eb8dc80dae7 Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Wed, 29 Jan 2025 19:02:36 +0100 Subject: [PATCH 06/35] Add error message to login --- frontend/src/pages/Login.tsx | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index ac6ac55a..0a3cab85 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -6,18 +6,20 @@ import Button from '../components/Button'; import signIn from '../api/actions/signIn'; const Login = () => { + useColours(); + + const navigate = useNavigate(); + const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [saveLogin, setSaveLogin] = useState(false); - const navigate = useNavigate(); - - useColours(); - - const form_error = false; + const [errorMessage, setErrorMessage] = useState(null); const handleSubmit = async (event: { preventDefault: () => void }) => { event.preventDefault(); + setErrorMessage(null); + const loginResponse = await signIn(username, password, saveLogin); const signedIn = loginResponse.status === 200; @@ -25,6 +27,8 @@ const Login = () => { if (signedIn) { navigate(Routes.Home); } else { + const data = await loginResponse.json(); + setErrorMessage(data?.message || 'Unknown Error'); navigate(Routes.Login); } }; @@ -37,7 +41,13 @@ const Login = () => {

Tube Archivist

Your Self Hosted YouTube Media Server

- {form_error &&

Failed to login.

} + {errorMessage !== null && ( +

+ Failed to login. +
+ {errorMessage} +

+ )}
{ value={username} onChange={event => setUsername(event.target.value)} /> +
+ { value={password} onChange={event => setPassword(event.target.value)} /> +
+

Remember me:{' '} { }} />

+ +
+ +
+ + + +
{infoDialogContent}
+
+
{sponsorBlock?.is_enabled && ( <> diff --git a/frontend/src/style.css b/frontend/src/style.css index e8b91ced..b212c000 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -790,7 +790,8 @@ video:-webkit-full-screen { position: absolute; z-index: 1; top: 20%; - width: 100%; + transform: translateX(-50%); + left: 50%; text-align: center; } @@ -798,7 +799,16 @@ video:-webkit-full-screen { background: rgba(0, 0, 0, 0.5); color: #eeeeee; font-size: 1.3em; - display: none; +} + +.video-modal-table { + margin: auto; + background: rgba(0, 0, 0, 0.5); +} + +.video-modal-form { + margin: auto; + background: rgba(0, 0, 0, 0.5); } .video-main video { From 2f1a43df557dfca4cb8d1a9c60749687609ddcec Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Sat, 1 Feb 2025 13:48:29 +0100 Subject: [PATCH 24/35] Fix only refresh video resposne once --- .../src/components/EmbeddableVideoPlayer.tsx | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/EmbeddableVideoPlayer.tsx b/frontend/src/components/EmbeddableVideoPlayer.tsx index d68c9a17..039855de 100644 --- a/frontend/src/components/EmbeddableVideoPlayer.tsx +++ b/frontend/src/components/EmbeddableVideoPlayer.tsx @@ -35,37 +35,39 @@ const EmbeddableVideoPlayer = ({ videoId }: EmbeddableVideoPlayerProps) => { useEffect(() => { (async () => { - const videoResponse = await loadVideoById(videoId); + if (refresh || videoId !== videoResponse?.data.youtube_id) { + const videoResponse = await loadVideoById(videoId); - const playlistIds = videoResponse.data.playlist; - if (playlistIds !== undefined) { - const playlists = await Promise.all( - playlistIds.map(async playlistid => { - const playlistResponse = await loadPlaylistById(playlistid); + const playlistIds = videoResponse.data.playlist; + if (playlistIds !== undefined) { + const playlists = await Promise.all( + playlistIds.map(async playlistid => { + const playlistResponse = await loadPlaylistById(playlistid); - return playlistResponse.data; - }), - ); + return playlistResponse.data; + }), + ); - const playlistsFiltered = playlists - .filter(playlist => { - return playlist.playlist_subscribed; - }) - .map(playlist => { - return { - id: playlist.playlist_id, - name: playlist.playlist_name, - }; - }); + const playlistsFiltered = playlists + .filter(playlist => { + return playlist.playlist_subscribed; + }) + .map(playlist => { + return { + id: playlist.playlist_id, + name: playlist.playlist_name, + }; + }); - setPlaylists(playlistsFiltered); + setPlaylists(playlistsFiltered); + } + + setVideoResponse(videoResponse); + + inlinePlayerRef.current?.scrollIntoView(); + + setRefresh(false); } - - setVideoResponse(videoResponse); - - inlinePlayerRef.current?.scrollIntoView(); - - setRefresh(false); })(); }, [videoId, refresh]); From 4dafa2f427d8b3622319f41c44f83a81b81daea3 Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Sat, 1 Feb 2025 13:53:04 +0100 Subject: [PATCH 25/35] Fix only refresh video resposne once --- frontend/src/pages/Video.tsx | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/Video.tsx b/frontend/src/pages/Video.tsx index 48250db1..1e9f1b5b 100644 --- a/frontend/src/pages/Video.tsx +++ b/frontend/src/pages/Video.tsx @@ -142,19 +142,22 @@ const Video = () => { useEffect(() => { (async () => { - const videoResponse = await loadVideoById(videoId); - const simmilarVideosResponse = await loadSimmilarVideosById(videoId); - const customPlaylistsResponse = await loadPlaylistList({ type: 'custom' }); - const commentsResponse = await loadCommentsbyVideoId(videoId); - const videoNavResponse = await loadVideoNav(videoId); + if (refreshVideoList || videoId !== videoResponse?.data?.youtube_id) { + const videoByIdResponse = await loadVideoById(videoId); + const simmilarVideosResponse = await loadSimmilarVideosById(videoId); + const customPlaylistsResponse = await loadPlaylistList({ type: 'custom' }); + const commentsResponse = await loadCommentsbyVideoId(videoId); + const videoNavResponse = await loadVideoNav(videoId); - setVideoResponse(videoResponse); - setSimmilarVideos(simmilarVideosResponse); - setVideoPlaylistNav(videoNavResponse); - setCustomPlaylistsResponse(customPlaylistsResponse); - setCommentsResponse(commentsResponse); - setRefreshVideoList(false); + setVideoResponse(videoByIdResponse); + setSimmilarVideos(simmilarVideosResponse); + setVideoPlaylistNav(videoNavResponse); + setCustomPlaylistsResponse(customPlaylistsResponse); + setCommentsResponse(commentsResponse); + setRefreshVideoList(false); + } })(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [videoId, refreshVideoList]); useEffect(() => { From a09274495848cc401d21d3cb00ee7c5b0e2188a4 Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Sat, 1 Feb 2025 15:05:53 +0100 Subject: [PATCH 26/35] Refac skip videoProgress update when currentTime equals video duration --- frontend/src/components/VideoPlayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/VideoPlayer.tsx b/frontend/src/components/VideoPlayer.tsx index 58dc48d6..9677067c 100644 --- a/frontend/src/components/VideoPlayer.tsx +++ b/frontend/src/components/VideoPlayer.tsx @@ -88,7 +88,7 @@ const handleTimeUpdate = }); } - if (currentTime < 10) return; + if (currentTime < 10 && currentTime === Number(videoTag.currentTarget.duration)) return; if (Number((currentTime % 10).toFixed(1)) <= 0.2) { // Check progress every 10 seconds or else progress is checked a few times a second const videoProgressResponse = await updateVideoProgressById({ From a7d11f53a84ba2d3c88cd11302f36df419287d67 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 1 Feb 2025 23:21:21 +0700 Subject: [PATCH 27/35] disable onPause for progress gt 95% --- frontend/src/components/VideoPlayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/VideoPlayer.tsx b/frontend/src/components/VideoPlayer.tsx index 9677067c..e720a66d 100644 --- a/frontend/src/components/VideoPlayer.tsx +++ b/frontend/src/components/VideoPlayer.tsx @@ -346,7 +346,7 @@ const VideoPlayer = ({ onPause={async (videoTag: VideoTag) => { const currentTime = Number(videoTag.currentTarget.currentTime); - if (currentTime < 10) return; + if (currentTime < 10 || currentTime > duration * 0.95) return; await updateVideoProgressById({ youtubeId: videoId, From c7fc2666fa423a31948be7d146816ae5105a2fc6 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 2 Feb 2025 15:36:25 +0700 Subject: [PATCH 28/35] add lsof to debug install --- Dockerfile | 2 +- deploy.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e7271478..29f75c40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,7 +55,7 @@ RUN apt-get clean && apt-get -y update && apt-get -y install --no-install-recomm # install debug tools for testing environment RUN if [ "$INSTALL_DEBUG" ] ; then \ apt-get -y update && apt-get -y install --no-install-recommends \ - vim htop bmon net-tools iputils-ping procps \ + vim htop bmon net-tools iputils-ping procps lsof \ && pip install --user ipython pytest pytest-django \ ; fi diff --git a/deploy.sh b/deploy.sh index 486da8ca..5f4aa931 100755 --- a/deploy.sh +++ b/deploy.sh @@ -34,7 +34,7 @@ function sync_blackhole { --exclude ".mypy_cache" \ . -e ssh "$host":tubearchivist - ssh "$host" 'docker build -t bbilly1/tubearchivist:unstable tubearchivist' + ssh "$host" 'docker build --build-arg INSTALL_DEBUG=1 -t bbilly1/tubearchivist:unstable tubearchivist' ssh "$host" 'docker compose -f docker/docker-compose.yml up -d' } From 2ec81c7ac7550be0569645d43068398338627f6c Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 2 Feb 2025 15:45:40 +0700 Subject: [PATCH 29/35] context manager for yt-dlp extract, stricter worker recycle --- backend/download/src/yt_dlp_base.py | 29 +++++++++++++++-------------- docker_assets/run.sh | 6 +++++- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/backend/download/src/yt_dlp_base.py b/backend/download/src/yt_dlp_base.py index a18e5ef0..b3ce3e16 100644 --- a/backend/download/src/yt_dlp_base.py +++ b/backend/download/src/yt_dlp_base.py @@ -82,23 +82,24 @@ class YtWrap: def extract(self, url): """make extract request""" - try: - response = yt_dlp.YoutubeDL(self.obs).extract_info(url) - except cookiejar.LoadError as err: - print(f"cookie file is invalid: {err}") - return False - except yt_dlp.utils.ExtractorError as err: - print(f"{url}: failed to extract with message: {err}, continue...") - return False - except yt_dlp.utils.DownloadError as err: - if "This channel does not have a" in str(err): + with yt_dlp.YoutubeDL(self.obs) as ydl: + try: + response = ydl.extract_info(url) + except cookiejar.LoadError as err: + print(f"cookie file is invalid: {err}") return False + except yt_dlp.utils.ExtractorError as err: + print(f"{url}: failed to extract: {err}, continue...") + return False + except yt_dlp.utils.DownloadError as err: + if "This channel does not have a" in str(err): + return False - print(f"{url}: failed to get info from youtube with message {err}") - if "Temporary failure in name resolution" in str(err): - raise ConnectionError("lost the internet, abort!") from err + print(f"{url}: failed to get info from youtube: {err}") + if "Temporary failure in name resolution" in str(err): + raise ConnectionError("lost the internet, abort!") from err - return False + return False self._validate_cookie() diff --git a/docker_assets/run.sh b/docker_assets/run.sh index 283b70f0..33029f1c 100644 --- a/docker_assets/run.sh +++ b/docker_assets/run.sh @@ -20,7 +20,11 @@ python manage.py ta_startup # start all tasks nginx & -celery -A task.celery worker --loglevel=INFO --max-tasks-per-child 10 & +celery -A task.celery worker \ + --loglevel=INFO \ + --concurrency 4 \ + --max-tasks-per-child 5 \ + --max-memory-per-child 150000 & celery -A task beat --loglevel=INFO \ --scheduler django_celery_beat.schedulers:DatabaseScheduler & python backend_start.py From 847764e4402d54f78ab18f34ff9ec00516596b7e Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 2 Feb 2025 15:50:08 +0700 Subject: [PATCH 30/35] strip nullbyte from cookie --- backend/download/src/yt_dlp_base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/download/src/yt_dlp_base.py b/backend/download/src/yt_dlp_base.py index b3ce3e16..ca7ce938 100644 --- a/backend/download/src/yt_dlp_base.py +++ b/backend/download/src/yt_dlp_base.py @@ -132,7 +132,8 @@ class CookieHandler: def set_cookie(self, cookie): """set cookie str and activate in config""" - RedisArchivist().set_message("cookie", cookie, save=True) + cookie_clean = cookie.strip("\x00") + RedisArchivist().set_message("cookie", cookie_clean, save=True) AppConfig().update_config({"downloads.cookie_import": True}) self.config["downloads"]["cookie_import"] = True print("[cookie]: activated and stored in Redis") From 3aa41232db161ec8c87a926e161859725661d1ba Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 2 Feb 2025 16:30:43 +0700 Subject: [PATCH 31/35] fix progress inconsistency+ --- backend/common/src/watched.py | 6 +++++- backend/common/views.py | 2 +- backend/video/views.py | 9 ++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/common/src/watched.py b/backend/common/src/watched.py index ad5a671a..d3d3b376 100644 --- a/backend/common/src/watched.py +++ b/backend/common/src/watched.py @@ -6,15 +6,17 @@ functionality: from datetime import datetime from common.src.es_connect import ElasticWrap +from common.src.ta_redis import RedisArchivist from common.src.urlparser import Parser class WatchState: """handle watched checkbox for videos and channels""" - def __init__(self, youtube_id, is_watched): + def __init__(self, youtube_id: str, is_watched: bool, user_id: int): self.youtube_id = youtube_id self.is_watched = is_watched + self.user_id = user_id self.stamp = int(datetime.now().timestamp()) self.pipeline = f"_ingest/pipeline/watch_{youtube_id}" @@ -50,6 +52,8 @@ class WatchState: } } response, status_code = ElasticWrap(path).post(data=data) + key = f"{self.user_id}:progress:{self.youtube_id}" + RedisArchivist().del_message(key) if status_code != 200: print(response) raise ValueError("failed to mark video as watched") diff --git a/backend/common/views.py b/backend/common/views.py index 20c12804..b7b477c9 100644 --- a/backend/common/views.py +++ b/backend/common/views.py @@ -75,7 +75,7 @@ class WatchedView(ApiBaseView): message = {"message": "missing id or is_watched"} return Response(message, status=400) - WatchState(youtube_id, is_watched).change() + WatchState(youtube_id, is_watched, request.user.id).change() return Response({"message": "success"}, status=200) diff --git a/backend/video/views.py b/backend/video/views.py index d601282d..0b3d93bb 100644 --- a/backend/video/views.py +++ b/backend/video/views.py @@ -126,10 +126,9 @@ class VideoProgressView(ApiBaseView): current_progress = self.response["data"]["player"] current_progress.update({"position": position, "youtube_id": video_id}) - watched = self._check_watched_state(video_id, current_progress) + watched = self._check_watched(request, video_id, current_progress) if watched: - redis_con.del_message(key) - expire = 360 + expire = 60 else: expire = False @@ -138,7 +137,7 @@ class VideoProgressView(ApiBaseView): return Response(current_progress) - def _check_watched_state(self, video_id, current_progress) -> bool: + def _check_watched(self, request, video_id, current_progress) -> bool: """check watched state""" if current_progress["watched"]: return True @@ -147,7 +146,7 @@ class VideoProgressView(ApiBaseView): current_progress["duration"], current_progress["position"] ) if watched: - WatchState(video_id, watched).change() + WatchState(video_id, watched, request.user.id).change() return watched From 8f22d0d9e2b79426e4d5c03faa184be5bee36006 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 2 Feb 2025 16:41:57 +0700 Subject: [PATCH 32/35] sanitize validate cookie str --- backend/download/src/yt_dlp_base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/download/src/yt_dlp_base.py b/backend/download/src/yt_dlp_base.py index ca7ce938..52070818 100644 --- a/backend/download/src/yt_dlp_base.py +++ b/backend/download/src/yt_dlp_base.py @@ -163,9 +163,10 @@ class CookieHandler: self.store_validation(response) # update in redis to avoid expiring - modified = validator.obs["cookiefile"].getvalue() + modified = validator.obs["cookiefile"].getvalue().strip("\x00") if modified: - RedisArchivist().set_message("cookie", modified) + cookie_clean = modified.strip("\x00") + RedisArchivist().set_message("cookie", cookie_clean) if not response: mess_dict = { From 1e12a060ced0a090e6afbdf505806eda24e8245b Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 2 Feb 2025 16:43:59 +0700 Subject: [PATCH 33/35] add progress delete button --- frontend/src/components/VideoListItem.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/src/components/VideoListItem.tsx b/frontend/src/components/VideoListItem.tsx index f6deb65d..6876ed45 100644 --- a/frontend/src/components/VideoListItem.tsx +++ b/frontend/src/components/VideoListItem.tsx @@ -3,6 +3,7 @@ import Routes from '../configuration/routes/RouteList'; import { VideoType, ViewLayoutType } from '../pages/Home'; import iconPlay from '/img/icon-play.svg'; import iconDotMenu from '/img/icon-dot-menu.svg'; +import iconClose from '/img/icon-close.svg'; import defaultVideoThumb from '/img/default-video-thumb.jpg'; import updateWatchedState from '../api/actions/updateWatchedState'; import formatDate from '../functions/formatDates'; @@ -10,6 +11,7 @@ import WatchedCheckBox from './WatchedCheckBox'; import MoveVideoMenu from './MoveVideoMenu'; import { useState } from 'react'; import getApiUrl from '../configuration/getApiUrl'; +import deleteVideoProgressById from '../api/actions/deleteVideoProgressById'; type VideoListItemProps = { video: VideoType; @@ -84,6 +86,17 @@ const VideoListItem = ({ refreshVideoList(true); }} /> + {video.player.progress && ( + { + await deleteVideoProgressById(video.youtube_id); + refreshVideoList(true); + }} + /> + )} {formatDate(video.published)} | {video.player.duration_str} From 4d0dc27ef1019e45d4ad5c8f7a265fabc7dc5f78 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 2 Feb 2025 16:51:14 +0700 Subject: [PATCH 34/35] add note about failing subtitles in debug, #871 --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b47d3b91..206029a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -140,6 +140,9 @@ The documentation available at [docs.tubearchivist.com](https://docs.tubearchivi This codebase is set up to be developed natively outside of docker as well as in a docker container. Developing outside of a docker container can be convenient, as IDE and hot reload usually works out of the box. But testing inside of a container is still essential, as there are subtle differences, especially when working with the filesystem and networking between containers. +Note: +- Subtitles currently fail to load with `DJANGO_DEBUG=True`, that is due to incorrect `Content-Type` error set by Django's static file implementation. That's only if you run the Django dev server, Nginx sets the correct headers. + ### Native Instruction For convenience, it's recommended to still run Redis and ES in a docker container. Make sure both containers can be reachable over the network. From c71e1acf745c2fa36a8d536b101225cbcef931e0 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 2 Feb 2025 17:29:08 +0700 Subject: [PATCH 35/35] add error messages to schedule form --- frontend/src/functions/APIClient.ts | 13 +++ frontend/src/pages/SettingsScheduling.tsx | 101 ++++++++++++++++------ 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/frontend/src/functions/APIClient.ts b/frontend/src/functions/APIClient.ts index 21a5e734..48b7900a 100644 --- a/frontend/src/functions/APIClient.ts +++ b/frontend/src/functions/APIClient.ts @@ -10,6 +10,11 @@ export interface ApiClientOptions extends Omit { body?: Record | string; } +export interface ApiError { + status: number; + message: string; +} + const APIClient = async ( endpoint: string, { method = 'GET', body, headers = {}, ...options }: ApiClientOptions = {}, @@ -30,6 +35,14 @@ const APIClient = async ( }); // Handle common errors + if (response.status === 400) { + const data = await response.json(); + throw { + status: response.status, + message: data?.message || 'An error occurred while processing the request.', + } as ApiError; + } + if (response.status === 401) { logOut(); window.location.href = Routes.Login; diff --git a/frontend/src/pages/SettingsScheduling.tsx b/frontend/src/pages/SettingsScheduling.tsx index d3f50d93..d1ebd685 100644 --- a/frontend/src/pages/SettingsScheduling.tsx +++ b/frontend/src/pages/SettingsScheduling.tsx @@ -13,6 +13,7 @@ import createAppriseNotificationUrl, { AppriseTaskNameType, } from '../api/actions/createAppriseNotificationUrl'; import deleteAppriseNotificationUrl from '../api/actions/deleteAppriseNotificationUrl'; +import { ApiError } from '../functions/APIClient'; const SettingsScheduling = () => { const [refresh, setRefresh] = useState(false); @@ -29,6 +30,11 @@ const SettingsScheduling = () => { const [zipBackupDays, setZipBackupDays] = useState(); const [notificationUrl, setNotificationUrl] = useState(); const [notificationTask, setNotificationTask] = useState(''); + const [checkReindexError, setCheckReindexError] = useState(null); + const [updateSubscribedError, setUpdateSubscribedError] = useState(null); + const [downloadPendingError, setDownloadPendingError] = useState(null); + const [thumnailCheckError, setThumnailCheckError] = useState(null); + const [zipBackupError, setZipBackupError] = useState(null); useEffect(() => { (async () => { @@ -144,15 +150,24 @@ const SettingsScheduling = () => {
@@ -190,15 +205,24 @@ const SettingsScheduling = () => {
@@ -237,15 +261,24 @@ const SettingsScheduling = () => {