feat: add watch-later views and Terebi watched-state tracking

This commit is contained in:
2026-10-11 20:08:52 +02:00
parent af8032f12f
commit cf4941c879
5 changed files with 1556 additions and 39 deletions
+10 -6
View File
@@ -1,12 +1,13 @@
use std::{
ffi::OsStr,
io::{self, Read},
path::Path,
process::{Child, Command, Stdio},
sync::mpsc::{self, Receiver, TryRecvError},
thread,
};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
pub const PAGE_SIZE: usize = 20;
@@ -16,7 +17,7 @@ pub struct Page {
pub cutoff: Option<i64>,
}
#[derive(Deserialize)]
#[derive(Clone, Deserialize, Serialize)]
pub struct Video {
pub id: String,
pub title: String,
@@ -67,14 +68,14 @@ fn valid_channel_id(id: &str) -> bool {
.all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
}
fn valid_video_id(id: &str) -> bool {
pub(crate) fn valid_video_id(id: &str) -> bool {
id.len() == 11
&& id
.bytes()
.all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
}
fn validate_video(video: &mut Video) -> Result<(), String> {
pub(crate) fn validate_video(video: &mut Video) -> Result<(), String> {
if !valid_video_id(&video.id) {
return Err("an invalid YouTube video ID".into());
}
@@ -463,19 +464,22 @@ pub struct PlayerLaunch {
}
impl PlayerLaunch {
pub fn start(id: &str, player: &OsStr) -> io::Result<Self> {
pub fn start(id: &str, player: &OsStr, progress_file: &Path) -> io::Result<Self> {
if !valid_video_id(id) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid YouTube video ID",
));
}
crate::watch_later::register_identity(progress_file, id)?;
let mut player = isolated_command(player)
.args([
"--new-instance",
"--url",
&format!("https://www.youtube.com/watch?v={id}"),
])
.arg("--progress-file")
.arg(progress_file)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
@@ -519,7 +523,7 @@ mod tests {
fn player_launch_rejects_unsafe_video_ids_before_spawning() {
for id in ["", "--help", "jNQXAC9IVRw&list=other", "日本語"] {
assert!(matches!(
PlayerLaunch::start(id, OsStr::new("terebi")),
PlayerLaunch::start(id, OsStr::new("terebi"), Path::new("progress.jsonl")),
Err(error) if error.kind() == io::ErrorKind::InvalidInput
));
}