1pub mod bookshelf;
2pub mod build;
3pub mod check;
4pub mod completions;
5pub mod download;
6pub mod info;
7pub mod read;
8pub mod real_cugan;
9pub mod search;
10pub mod sign;
11pub mod template;
12pub mod transform;
13pub mod unzip;
14pub mod update;
15pub mod zip;
16
17use std::{cmp, collections::HashMap, path::Path, process, sync::Arc};
18
19use clap::ValueEnum;
20use fluent_templates::{Loader, fluent_bundle::FluentValue};
21use novel_api::Client;
22use ratatui::{
23 buffer::Buffer,
24 layout::{Rect, Size},
25 text::Text,
26 widgets::{Block, Paragraph, StatefulWidget, Widget, Wrap, block::Title},
27};
28use strum::AsRefStr;
29use tokio::signal;
30use tui_widgets::scrollview::{ScrollView, ScrollViewState};
31use url::Url;
32
33use crate::{LANG_ID, LOCALES};
34
35const DEFAULT_PROXY: &str = "http://127.0.0.1:8080";
36
37const DEFAULT_PROXY_SURGE: &str = "http://127.0.0.1:6152";
38
39#[must_use]
40#[derive(Clone, PartialEq, ValueEnum, AsRefStr)]
41pub enum Source {
42 #[strum(serialize = "sfacg")]
43 Sfacg,
44 #[strum(serialize = "ciweimao")]
45 Ciweimao,
46 #[strum(serialize = "ciyuanji")]
47 Ciyuanji,
48}
49
50#[must_use]
51#[derive(Clone, PartialEq, ValueEnum, AsRefStr)]
52pub enum Format {
53 Pandoc,
54 Mdbook,
55}
56
57#[must_use]
58#[derive(Clone, PartialEq, ValueEnum, AsRefStr)]
59pub enum Convert {
60 S2T,
61 T2S,
62 JP2T2S,
63 CUSTOM,
64}
65
66#[inline]
67#[must_use]
68fn default_cert_path() -> String {
69 novel_api::home_dir_path()
70 .unwrap()
71 .join(".mitmproxy")
72 .join("mitmproxy-ca-cert.pem")
73 .display()
74 .to_string()
75}
76
77fn set_options<T, E>(client: &mut T, proxy: &Option<Url>, no_proxy: &bool, cert: &Option<E>)
78where
79 T: Client,
80 E: AsRef<Path>,
81{
82 if let Some(proxy) = proxy {
83 client.proxy(proxy.clone());
84 }
85
86 if *no_proxy {
87 client.no_proxy();
88 }
89
90 if let Some(cert) = cert {
91 client.cert(cert.as_ref().to_path_buf())
92 }
93}
94
95fn handle_ctrl_c<T>(client: &Arc<T>)
96where
97 T: Client + Send + Sync + 'static,
98{
99 let client = Arc::clone(client);
100
101 tokio::spawn(async move {
102 signal::ctrl_c().await.unwrap();
103
104 tracing::warn!("Download terminated, login data will be saved");
105
106 client.shutdown().await.unwrap();
107 process::exit(128 + libc::SIGINT);
108 });
109}
110
111fn cert_help_msg() -> String {
112 let args = {
113 let mut map = HashMap::new();
114 map.insert(
115 "cert_path".into(),
116 FluentValue::String(default_cert_path().into()),
117 );
118 map
119 };
120
121 LOCALES.lookup_with_args(&LANG_ID, "cert", &args)
122}
123
124#[derive(Default, PartialEq)]
125enum Mode {
126 #[default]
127 Running,
128 Quit,
129}
130
131pub struct ScrollableParagraph<'a> {
132 title: Option<Title<'a>>,
133 text: Text<'a>,
134}
135
136impl<'a> ScrollableParagraph<'a> {
137 pub fn new<T>(text: T) -> Self
138 where
139 T: Into<Text<'a>>,
140 {
141 ScrollableParagraph {
142 title: None,
143 text: text.into(),
144 }
145 }
146
147 pub fn title<T>(self, title: T) -> Self
148 where
149 T: Into<Title<'a>>,
150 {
151 ScrollableParagraph {
152 title: Some(title.into()),
153 ..self
154 }
155 }
156}
157
158impl StatefulWidget for ScrollableParagraph<'_> {
159 type State = ScrollViewState;
160
161 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
162 let mut block = Block::bordered();
163 if self.title.is_some() {
164 block = block.title(self.title.as_ref().unwrap().clone());
165 }
166
167 let paragraph = Paragraph::new(self.text.clone()).wrap(Wrap { trim: false });
168 let mut scroll_view = ScrollView::new(Size::new(area.width - 1, area.height));
169 let mut block_area = block.inner(scroll_view.buf().area);
170
171 let scroll_height = cmp::max(
172 paragraph.line_count(block_area.width) as u16
173 + (scroll_view.buf().area.height - block_area.height),
174 area.height,
175 );
176
177 let scroll_width = if area.height >= scroll_height {
178 area.width
180 } else {
181 area.width - 1
182 };
183
184 scroll_view = ScrollView::new(Size::new(scroll_width, scroll_height));
185
186 let scroll_view_buf = scroll_view.buf_mut();
187 block_area = block.inner(scroll_view_buf.area);
188
189 Widget::render(block, scroll_view_buf.area, scroll_view_buf);
190 Widget::render(paragraph, block_area, scroll_view_buf);
191 StatefulWidget::render(scroll_view, area, buf, state);
192 }
193}