From 758dc3fad254cc9485c3499c2f1035962565be63 Mon Sep 17 00:00:00 2001 From: Domipoke <42298508+Domipoke@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:16:25 +0200 Subject: [PATCH] update --- debug.log | 0 library/animelibrary.toml | 4 ++ src/components.rs | 25 +++++--- src/main.rs | 117 +++++++++++++++++++++++++++++++------- src/source.rs | 10 ++-- src/source/animeworld.rs | 17 +++++- src/source/mangaworld.rs | 4 +- 7 files changed, 141 insertions(+), 36 deletions(-) delete mode 100644 debug.log diff --git a/debug.log b/debug.log deleted file mode 100644 index e69de29..0000000 diff --git a/library/animelibrary.toml b/library/animelibrary.toml index c4b3876..7c96350 100644 --- a/library/animelibrary.toml +++ b/library/animelibrary.toml @@ -1,3 +1,7 @@ [[Animeworld]] name="Frieren: Beyond Journey's End 2" episodes="https://www.animeworld.ac/play/frieren-beyond-journeys-end-2.JvOQx" + +[[Animeworld]] +name="One Piece" +episodes="https://www.animeworld.ac/play/one-piece-subita.qzG-LE/HPKmX1" \ No newline at end of file diff --git a/src/components.rs b/src/components.rs index 73a974f..31d7dad 100644 --- a/src/components.rs +++ b/src/components.rs @@ -200,7 +200,8 @@ pub mod list { } pub fn handle_input(&mut self, kc: ratatui::crossterm::event::KeyCode) -> bool { if let Some(gcw) = self.get_current_widget() { - return (gcw.handle_input)(kc); + let f = (gcw.handle_input)(kc); + return f; } false } @@ -328,6 +329,16 @@ pub mod Library { trasmitter_to_app, } } + pub fn sorted_keys_from_hash(hs: &HashMap) -> Vec<&String> { + let mut items: Vec<&String> = hs.keys().collect::>(); + items.sort_by(|a: &&String,b: &&String| { + if let Ok(ai) = a.parse::() && let Ok(bi) = b.parse::() { + return bi.cmp(&ai); + } + return b.cmp(a); + }); // ? REVERSE SORT - MAX TO MIN -> NEWEST FIRST + items + } } pub enum LibraryStateStatus { Empty, @@ -386,7 +397,7 @@ pub mod Library { state.lib = lib; state.status = LibraryStateStatus::Lib(0); state.entry_selected = false; - return Paragraph::new("Lib loaded").block(block).render(area, buf); + return Paragraph::new("Lib loading").block(block).render(area, buf); } else { return Paragraph::new("No lib found. Edit ./library/animelibrary.toml or ./library/mangalibrary.toml. Format:\n[[Animeworld]]\nname=\"Frieren\"\nepisodes=\"https://url_to_a_generic_episode_of_animeworld_frieren_or_chapter_list_manga_page/\"").block(block).render(area, buf); } @@ -408,7 +419,7 @@ pub mod Library { let key = *k; if let Some(a) = state.lib.get(key) { state.current_source = Some(key.clone()); - let list = List::new(a.iter().map(|li| ListItem::new(li.name.clone())).collect::>()).block(block_sc); + let list = List::new(a.iter().map(|li| ListItem::new(li.name.clone())).collect::>()).block(block_sc).highlight_symbol(">> "); StatefulWidget::render(list, lay[0], buf, &mut state.list_state_source); } else { Widget::render(block_sc, lay[0], buf); @@ -417,13 +428,13 @@ pub mod Library { Widget::render(block_sc, lay[0], buf); } if state.entry_selected && let Some(eps_hash) = &state.episodes_loaded { - let mut items = eps_hash.keys().collect::>(); - items.sort_by(|a,b| b.cmp(a)); // ? REVERSE SORT - MAX TO MIN -> NEWEST FIRST + let items = Self::sorted_keys_from_hash(eps_hash); let mut eps = Vec::with_capacity(items.len()); for it in items { - eps.push(ListItem::new(it.clone())); + let s = eps_hash.get(it).and_then(|f| Some(f.title.clone())).unwrap_or(it.clone()); + eps.push(ListItem::new(s)); } - let list_ep = List::new(eps).block(block_eps); + let list_ep = List::new(eps).block(block_eps).highlight_symbol(">> ");; StatefulWidget::render(list_ep, lay[1], buf, &mut state.list_state_eps); } else { Widget::render(block_eps, lay[1], buf); diff --git a/src/main.rs b/src/main.rs index 8a902bc..d2d3476 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ mod components; mod source; mod utils; mod folders; -use std::{any::Any, cell::RefCell, collections::HashMap, process::{Command, Stdio}, rc::Rc, sync::Arc, time::Duration, vec}; +use std::{any::Any, cell::RefCell, cmp, collections::HashMap, process::{Command, Stdio}, rc::Rc, sync::Arc, time::Duration, vec}; use color_eyre::{Result}; use ratatui::{ DefaultTerminal, Frame, crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}, layout::{Constraint, Direction, Layout, Rect}, style::Style, text::{Line, Span}, widgets::{Block, BorderType, Borders, List, ListItem, ListState, Paragraph} @@ -183,6 +183,7 @@ impl App { match self.library.rx.try_recv() { Ok(h)=>{ self.library.library_list_state.borrow_mut().episodes_loaded = Some(h); + self.library.library_list_state.borrow_mut().entry_selected = true; } Err(_) => {} } @@ -224,7 +225,7 @@ impl App { } else { self.navigation_menu.handle_input(k); } - } + }, KeyCode::Enter => { if self.navigation_menu.lastpath.is_empty() { self.navigation_menu.enter(); @@ -458,11 +459,22 @@ impl App { .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::new().fg(ratatui::style::Color::Cyan)); + let lay = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Percentage(100), + Constraint::Min(3) + ]).split(area); let mut state = render_ls.borrow_mut(); let l = Library::Library::new(lib_type,tx_clone_render.clone()); //TODO - + let cmd_list = Self::generate_cmd_list(vec![ + (CmdListType::Multi, "W,A,S,D or Arrows","to move"), + (CmdListType::Single, "Enter","select"), + (CmdListType::Single, "Esc","Go Back"), + ]); + frame.render_widget( cmd_list, lay[1]); //frame.render_widget(b, area); - frame.render_stateful_widget(l, area, &mut *state); + frame.render_stateful_widget(l, lay[0], &mut *state); }), handle_input: Box::new(move |k: KeyCode| { let mut state = input_ls.borrow_mut(); let tx_clone_input_clone = tx_clone_input.clone(); @@ -470,36 +482,90 @@ impl App { LibraryStateStatus::Lib(p) => { match k { KeyCode::Left | KeyCode::Char('a') => { - state.list_state_source.select(None); - state.status = LibraryStateStatus::Lib(p-1); + if state.entry_selected { + + } else { + state.list_state_source.select(None); + if p>0 { + state.status = LibraryStateStatus::Lib(p-1); + } else { + state.status = LibraryStateStatus::Lib(state.lib.keys().len()-1); + } + } true - } + }, KeyCode::Right | KeyCode::Char('d') => { - state.list_state_source.select(None); - state.status = LibraryStateStatus::Lib(p+1); + if state.entry_selected { + + } else { + state.list_state_source.select(None); + state.status = LibraryStateStatus::Lib(p+1); + } true - } + }, KeyCode::Up | KeyCode::Char('w') => { if state.entry_selected { - state.list_state_eps.select_previous(); + if state.list_state_eps.selected().is_none() { + state.list_state_eps.select_last(); + }else { + state.list_state_eps.select_previous(); + } } else { - state.list_state_source.select_previous(); + if state.list_state_source.selected().is_none() { + state.list_state_source.select_last(); + }else { + state.list_state_source.select_previous(); + } } true }, KeyCode::Down | KeyCode::Char('s')=> { if state.entry_selected { - state.list_state_eps.select_next(); + if state.list_state_eps.selected().is_none() { + state.list_state_eps.select_first(); + }else { + state.list_state_eps.select_next(); + } } else { - state.list_state_source.select_next(); + if state.list_state_source.selected().is_none() { + state.list_state_source.select_first(); + }else { + state.list_state_source.select_next(); + } } true }, KeyCode::Enter => { - if state.entry_selected { - + let state_bo = &state; + if state_bo.entry_selected { + let kind = match lib_type { + Library::LibraryType::Anime => SourceType::Anime, + Library::LibraryType::Manga => SourceType::Manga + }; + if let Some(u) = state_bo.list_state_eps.selected() && let Some(current) = state_bo.current_source.clone() && let Some(episode_loaded) = &state_bo.episodes_loaded { + let items =Library::Library::sorted_keys_from_hash(&episode_loaded); + if let Some(item) = items.get(u) { + //source.episode_play_url(url); + if let Some(eli) = episode_loaded.get(*item).clone() { + let eli_c = (*eli).clone(); + tokio::spawn(async move { + if let Ok(ex) = Extension::buildfromName(kind, ¤t){ + match kind { + SourceType::Anime => { + let s_a = ex.toAnimeSource(); + let _ = s_a.lib_ep_select(eli_c).await; + } + SourceType::Manga => { + let s_m = ex.toMangaSource(); + let _ = s_m.lib_ep_select(eli_c).await; + } + }; + } + }); + } + } + } } else { - let state_bo = &state; if let Some(sel) = state_bo.list_state_source.selected() && let Some(current) = state_bo.current_source.clone() { let kind = match lib_type { Library::LibraryType::Anime => SourceType::Anime, @@ -508,21 +574,21 @@ impl App { if let Some(u_list) = state_bo.lib.get(¤t) { if let Some(u) = u_list.get(sel) { let u_clone = u.clone(); - let lib_type_clone = lib_type.clone(); + // let lib_type_clone = lib_type.clone(); tokio::spawn(async move { if let Ok(ex) = Extension::buildfromName(kind, ¤t) { - let hs = match lib_type_clone { - Library::LibraryType::Anime => { + let hs = match kind { + SourceType::Anime => { let s: Box = ex.toAnimeSource(); s.lib_url_parse(u_clone.episodes).await } - Library::LibraryType::Manga => { + SourceType::Manga => { let m: Box = ex.toMangaSource(); m.lib_url_parse(u_clone.episodes).await } }; if let Ok(o) = hs { - tx_clone_input_clone.send(o); + let _ = tx_clone_input_clone.send(o); } } }); @@ -531,6 +597,13 @@ impl App { } } true + }, + KeyCode::Esc | KeyCode::Backspace => { + if state.entry_selected { + state.entry_selected = false; + return true; + } + false } _ => {false} } diff --git a/src/source.rs b/src/source.rs index 9aa0791..67f141a 100644 --- a/src/source.rs +++ b/src/source.rs @@ -6,7 +6,7 @@ pub mod animeworld; pub mod mangaworld; pub const AVAIBLEANIMESOURCE: [&str; 1] = ["animeworld"]; -#[derive(Clone)] +#[derive(Clone, Copy)] pub enum SourceType { Anime, Manga } @@ -35,9 +35,10 @@ pub struct Episode { pub section: usize, pub url: String } +#[derive(Clone)] pub struct EpisodeListItem { - title: String, - url: String + pub title: String, + pub url: String } pub struct Comic { episodes: Vec, @@ -98,6 +99,7 @@ pub trait AnimeSource: Send + Sync { async fn episode_openable_link(&self, card: Episode) -> Result>; async fn episode_play_url(&self, url: &str) -> Result>; async fn lib_url_parse(&self, url: String) -> Result, Box>; + async fn lib_ep_select(&self, eli: EpisodeListItem) -> Result<(), Box>; } #[async_trait] @@ -142,5 +144,5 @@ pub trait MangaSource: Send + Sync { async fn lib_url_parse(&self, url: String) -> Result, Box>; // async fn chapter(&self, card: Episode) -> Result>; // async fn episode_play_url(&self, url: &str) -> Result>; - + async fn lib_ep_select(&self, eli: EpisodeListItem) -> Result<(), Box>; } diff --git a/src/source/animeworld.rs b/src/source/animeworld.rs index b89533f..2f97cd4 100644 --- a/src/source/animeworld.rs +++ b/src/source/animeworld.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, process::Command}; +use std::{collections::HashMap, process::{Command, Stdio}}; use reqwest::get; use scraper::{Html, Selector}; -use crate::source::{AnimeSource, Episode, EpisodeListItem, SourceType}; +use crate::{log, source::{AnimeSource, Episode, EpisodeListItem, SourceType}}; pub struct Animeworld { url: String, @@ -133,6 +133,19 @@ impl AnimeSource for Animeworld { } return Ok(hs); } + async fn lib_ep_select(&self, eli: EpisodeListItem) -> Result<(), Box> { + if let Some(url) = Self::id_extract(&eli.url) { + if let Ok(vlc_url) =self.episode_play_url(url).await { + if !vlc_url.is_empty() { + let _ = Command::new("vlc").arg(vlc_url) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn(); + } + } + } + Ok(()) + } // async fn fetch_html(url: String) -> Result { // get(url).await?.error_for_status()?.text().await // } diff --git a/src/source/mangaworld.rs b/src/source/mangaworld.rs index e2e0b0c..607a7d4 100644 --- a/src/source/mangaworld.rs +++ b/src/source/mangaworld.rs @@ -175,5 +175,7 @@ impl MangaSource for Mangaworld { } return Ok(hs); } - + async fn lib_ep_select(&self, eli: EpisodeListItem) -> Result<(), Box> { + Ok(()) //TODO + } }