feat: add AniList browsing with on-demand ZokoAnime playback

This commit is contained in:
2026-10-25 15:44:41 +01:00
parent a281c6730a
commit e328ff3883
20 changed files with 1062 additions and 235 deletions
+60
View File
@@ -1,4 +1,5 @@
use super::*;
use clap::Parser;
use ratatui::crossterm::event::KeyModifiers;
use std::{
fs,
@@ -26,6 +27,7 @@ impl Directory {
player: self.0.join("missing-player").into_os_string(),
default_config: false,
miruro: true,
anilist: false,
query: query.map(str::to_owned),
};
Browser::new(
@@ -764,3 +766,61 @@ fn episode_launch_retains_checks_on_failure_and_clears_only_after_spawn() {
assert_eq!(browser.screen().playback_videos()[0].id, hovered.id);
}
}
#[test]
fn anilist_unknown_count_allows_explicit_selection_and_provider_local_collections() {
let directory = Directory::new();
let path = directory.0.join("config.toml");
let options = Options::parse_from(["tubular", "--anilist"]);
let mut browser = Browser::new(
options,
path.clone(),
config::Config::default(),
State::open(&path).unwrap(),
);
let series = miruro_browser::anilist_series(serde_json::json!({"id":"1","catalog_provider":"anilist","title":{"romaji":"Unknown total"},"status":"RELEASING","episode_count":null,"catalog_count":null})).unwrap();
browser.push(Screen::new(Content::Series {
media: series.media,
data: Some(series.episodes),
}));
browser.edit_search();
assert!(browser.episode_input);
browser.input = Some("7".into());
browser.key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
let selected = browser.screen().selected_video().unwrap();
assert_eq!(selected.id, "anilist:1:7");
browser.save(CollectionKind::Saved);
let mut legacy = selected.clone();
legacy.id = "miruro:1:7".into();
legacy.channel = Some("Miruro".into());
browser.state.save(CollectionKind::Saved, &legacy).unwrap();
browser.collection(CollectionKind::Saved);
assert_eq!(browser.screen().len(), 1);
assert_eq!(browser.screen().selected_video().unwrap().id, selected.id);
browser.history(false);
for invalid in ["0", "01", "-1", "2147483648"] {
browser.select_episode(invalid);
assert!(browser.screen().error.is_some());
assert_eq!(browser.screen().len(), 1);
}
browser.select_episode("8");
assert!(browser.screen().error.is_none());
browser.screen_mut().toggle_checked();
browser.select_episode("7");
browser.screen_mut().toggle_checked();
let queue = browser.screen().playback_videos();
assert_eq!(
queue
.iter()
.map(|video| video.id.as_str())
.collect::<Vec<_>>(),
["anilist:1:8", "anilist:1:7"]
);
assert_eq!(
crate::youtube::video_url(&queue[0].id).unwrap(),
"https://zokoanime.video/stream/ani/1/8/sub"
);
browser.key(KeyEvent::new(KeyCode::Char('/'), KeyModifiers::NONE));
assert!(matches!(browser.screen().content, Content::Catalog { .. }));
assert!(!browser.episode_input);
}