update
This commit is contained in:
@@ -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"
|
||||
+18
-7
@@ -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<String, EpisodeListItem>) -> Vec<&String> {
|
||||
let mut items: Vec<&String> = hs.keys().collect::<Vec<&String>>();
|
||||
items.sort_by(|a: &&String,b: &&String| {
|
||||
if let Ok(ai) = a.parse::<isize>() && let Ok(bi) = b.parse::<isize>() {
|
||||
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::<Vec<ListItem>>()).block(block_sc);
|
||||
let list = List::new(a.iter().map(|li| ListItem::new(li.name.clone())).collect::<Vec<ListItem>>()).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::<Vec<&String>>();
|
||||
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);
|
||||
|
||||
+87
-14
@@ -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') => {
|
||||
if state.entry_selected {
|
||||
|
||||
} else {
|
||||
state.list_state_source.select(None);
|
||||
if p>0 {
|
||||
state.status = LibraryStateStatus::Lib(p-1);
|
||||
true
|
||||
} else {
|
||||
state.status = LibraryStateStatus::Lib(state.lib.keys().len()-1);
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
KeyCode::Right | KeyCode::Char('d') => {
|
||||
if state.entry_selected {
|
||||
|
||||
} else {
|
||||
state.list_state_source.select(None);
|
||||
state.status = LibraryStateStatus::Lib(p+1);
|
||||
true
|
||||
}
|
||||
true
|
||||
},
|
||||
KeyCode::Up | KeyCode::Char('w') => {
|
||||
if state.entry_selected {
|
||||
if state.list_state_eps.selected().is_none() {
|
||||
state.list_state_eps.select_last();
|
||||
}else {
|
||||
state.list_state_eps.select_previous();
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
if state.list_state_eps.selected().is_none() {
|
||||
state.list_state_eps.select_first();
|
||||
}else {
|
||||
state.list_state_eps.select_next();
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
|
||||
} else {
|
||||
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 {
|
||||
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<dyn AnimeSource + Send + Sync> = ex.toAnimeSource();
|
||||
s.lib_url_parse(u_clone.episodes).await
|
||||
}
|
||||
Library::LibraryType::Manga => {
|
||||
SourceType::Manga => {
|
||||
let m: Box<dyn MangaSource + Send + Sync> = 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}
|
||||
}
|
||||
|
||||
+6
-4
@@ -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<S: MangaSource> {
|
||||
episodes: Vec<Chapter>,
|
||||
@@ -98,6 +99,7 @@ pub trait AnimeSource: Send + Sync {
|
||||
async fn episode_openable_link(&self, card: Episode) -> Result<String, Box<dyn std::error::Error>>;
|
||||
async fn episode_play_url(&self, url: &str) -> Result<String, Box<dyn std::error::Error>>;
|
||||
async fn lib_url_parse(&self, url: String) -> Result<HashMap<String, EpisodeListItem>, Box<dyn std::error::Error + Send + Sync>>;
|
||||
async fn lib_ep_select(&self, eli: EpisodeListItem) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -142,5 +144,5 @@ pub trait MangaSource: Send + Sync {
|
||||
async fn lib_url_parse(&self, url: String) -> Result<HashMap<String, EpisodeListItem>, Box<dyn std::error::Error + Send + Sync>>;
|
||||
// async fn chapter(&self, card: Episode) -> Result<String, Box<dyn std::error::Error>>;
|
||||
// async fn episode_play_url(&self, url: &str) -> Result<String, Box<dyn std::error::Error>>;
|
||||
|
||||
async fn lib_ep_select(&self, eli: EpisodeListItem) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
}
|
||||
|
||||
@@ -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<dyn std::error::Error + Send + Sync>> {
|
||||
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<String, reqwest::Error> {
|
||||
// get(url).await?.error_for_status()?.text().await
|
||||
// }
|
||||
|
||||
@@ -175,5 +175,7 @@ impl MangaSource for Mangaworld {
|
||||
}
|
||||
return Ok(hs);
|
||||
}
|
||||
|
||||
async fn lib_ep_select(&self, eli: EpisodeListItem) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(()) //TODO
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user