- Notifications
You must be signed in to change notification settings - Fork 54
feat(multi.suwayomi): add user and password support #223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -15,12 +15,61 @@ | |
| use aidoku::{ | ||
| AidokuError, BaseUrlProvider, Chapter, DynamicListings, FilterValue, Listing, ListingProvider, | ||
| Manga, MangaPageResult, Page, PageContent, Result, Source, | ||
| alloc::{String, Vec}, | ||
| alloc::{String, Vec, vec}, | ||
| imports::net::Request, | ||
| prelude::*, | ||
| }; | ||
| use alloc::string::ToString; | ||
| use alloc::vec; | ||
| use core::fmt::Write; | ||
| | ||
| fn base64_encode(input: &str) -> String { | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can use the | ||
| const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||
| let bytes = input.as_bytes(); | ||
| let mut output = String::with_capacity((bytes.len() + 2) / 3 * 4); | ||
| Check warning on line 28 in sources/multi.suwayomi/src/lib.rs | ||
| | ||
| for chunk in bytes.chunks(3) { | ||
| let b0 = chunk[0] as u32; | ||
| let b1 = *chunk.get(1).unwrap_or(&0) as u32; | ||
| let b2 = *chunk.get(2).unwrap_or(&0) as u32; | ||
| | ||
| let triple = (b0 << 16) | (b1 << 8) | b2; | ||
| | ||
| output.push(CHARSET[((triple >> 18) & 0x3F) as usize] as char); | ||
| output.push(CHARSET[((triple >> 12) & 0x3F) as usize] as char); | ||
| | ||
| if chunk.len() > 1 { | ||
| output.push(CHARSET[((triple >> 6) & 0x3F) as usize] as char); | ||
| } else { | ||
| output.push('='); | ||
| } | ||
| | ||
| if chunk.len() > 2 { | ||
| output.push(CHARSET[(triple & 0x3F) as usize] as char); | ||
| } else { | ||
| output.push('='); | ||
| } | ||
| } | ||
| output | ||
| } | ||
| | ||
| fn url_encode(input: &str) -> String { | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's an | ||
| let mut output = String::new(); | ||
| for byte in input.bytes() { | ||
| match byte { | ||
| b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { | ||
| output.push(byte as char); | ||
| } | ||
| _ => { | ||
| let _ = write!(output, "%{:02X}", byte); | ||
| } | ||
| } | ||
| } | ||
| output | ||
| } | ||
| | ||
| fn get_basic_credentials() -> Option<(String, String)> { | ||
| settings::get_credentials() | ||
| } | ||
| | ||
| struct Suwayomi; | ||
| | ||
| | @@ -30,10 +79,54 @@ | |
| T: serde::de::DeserializeOwned, | ||
| { | ||
| let base_url = settings::get_base_url()?; | ||
| Request::post(format!("{base_url}/api/graphql"))? | ||
| .header("Content-Type", "application/json") | ||
| .body(body.to_string()) | ||
| .json_owned::<GraphQLResponse<T>>() | ||
| let auth_mode = settings::get_auth_mode(); | ||
| let body_str = body.to_string(); | ||
| | ||
| let send_req = |with_basic: bool| { | ||
| let mut request = Request::post(format!("{base_url}/api/graphql"))? | ||
| .header("Content-Type", "application/json") | ||
| .body(body_str.clone()); | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this shouldn't need to be cloned | ||
| | ||
| if with_basic { | ||
| if let Some((user, pass)) = get_basic_credentials() { | ||
| let auth = base64_encode(&format!("{}:{}", user, pass)); | ||
| request = request.header("Authorization", &format!("Basic {}", auth)); | ||
| } | ||
| } | ||
| Check warning on line 95 in sources/multi.suwayomi/src/lib.rs | ||
| | ||
| request.json_owned::<GraphQLResponse<T>>() | ||
| }; | ||
| | ||
| let do_login_html = || -> Result<()> { | ||
| if let Some((user, pass)) = get_basic_credentials() { | ||
| let form = format!("user={}&pass={}", url_encode(&user), url_encode(&pass)); | ||
| let _ = Request::post(format!("{base_url}/login.html"))? | ||
| .header("Content-Type", "application/x-www-form-urlencoded") | ||
| .body(form) | ||
| .send() | ||
| .ok(); | ||
| } | ||
| Ok(()) | ||
| }; | ||
| | ||
| match auth_mode.as_str() { | ||
| "none" => send_req(false), | ||
| | ||
| "basic_auth" => send_req(true), | ||
| | ||
| "simple_login" => { | ||
| do_login_html()?; | ||
| send_req(false) | ||
| } | ||
| _ => { | ||
| let resp = send_req(true); | ||
| if resp.is_err() { | ||
| do_login_html()?; | ||
| return send_req(true); | ||
| } | ||
| resp | ||
| } | ||
| } | ||
| } | ||
| } | ||
| | ||
| | ||
Cruellest marked this conversation as resolved. Show resolved Hide resolved |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,23 @@ | ||
| use aidoku::{AidokuError, alloc::string::String, imports::defaults::defaults_get, prelude::bail}; | ||
| use aidoku::{Result, alloc::String, imports::defaults::defaults_get, prelude::*}; | ||
| | ||
| const BASE_URL_KEY: &str = "baseUrl"; | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no reason to remove this. you can also add a | ||
| pub fn get_base_url() -> Result<String> { | ||
| let url: String = defaults_get::<String>("baseUrl").ok_or(error!("Missing baseUrl"))?; | ||
| Ok(url.trim_end_matches('/').into()) | ||
| } | ||
| | ||
| pub fn get_auth_mode() -> String { | ||
| match defaults_get::<String>("authMode") { | ||
| Some(v) if !v.is_empty() => v, | ||
| _ => "auto".into(), | ||
| } | ||
| } | ||
| | ||
| pub fn get_credentials() -> Option<(String, String)> { | ||
| let user: String = defaults_get::<String>("credentials.username")?; | ||
| let pass: String = defaults_get::<String>("credentials.password")?; | ||
| | ||
| pub fn get_base_url() -> Result<String, AidokuError> { | ||
| let base_url = defaults_get::<String>(BASE_URL_KEY); | ||
| match base_url { | ||
| Some(url) if !url.is_empty() => Ok(url), | ||
| _ => bail!("Base Url not configured"), | ||
| if user.is_empty() { | ||
| return None; | ||
| } | ||
| Some((user, pass)) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it not possible to determine this automatically via some request to suwayomi? I would expect it to be, but if not that's okay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not. Most clients ask the user to input the auth mode.