Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion sources/multi.suwayomi/res/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,30 @@
"returnKeyType": 9,
"autocorrectionDisabled": true,
"refreshes": ["content"]
},
{
"type": "select",
"key": "authMode",
"title": "Auth Mode",
"values": ["auto", "none", "basic_auth", "simple_login"],
"titles": [
"Auto",
"None",
"Basic Auth",
"Simple Login"
],
"default": "auto",
"refreshes": ["content"]
},
Comment on lines +15 to +28
Copy link
Contributor

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.

Copy link

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.

{
"type": "login",
"method": "basic",
"key": "credentials",
"title": "Login",
"requires": "baseUrl",
"refreshes": ["content"]
}
]
],
"footer": "Enter your Suwayomi server URL and optionally login with your credentials."
}
]
2 changes: 1 addition & 1 deletion sources/multi.suwayomi/res/source.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"info": {
"id": "multi.suwayomi",
"name": "Suwayomi",
"version": 2,
"version": 3,
"url": "https://github.com/Suwayomi",
"contentRating": 0,
"languages": [
Expand Down
105 changes: 99 additions & 6 deletions sources/multi.suwayomi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use the base64, with default features disabled, instead of implementing it yourself. I believe there are other sources that do this.

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

View workflow job for this annotation

GitHub Actions / lint

manually reimplementing `div_ceil`

warning: manually reimplementing `div_ceil` --> src/lib.rs:28:41 | 28 | let mut output = String::with_capacity((bytes.len() + 2) / 3 * 4); | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `bytes.len().div_ceil(3)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#manual_div_ceil = note: `#[warn(clippy::manual_div_ceil)]` on by default

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's an encode_uri_component in aidoku-rs helpers you can use instead, unless this is a specific implementation?

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;

Expand All @@ -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());
Copy link
Contributor

Choose a reason for hiding this comment

The 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

View workflow job for this annotation

GitHub Actions / lint

this `if` statement can be collapsed

warning: this `if` statement can be collapsed --> src/lib.rs:90:4 | 90 | / if with_basic { 91 | | if let Some((user, pass)) = get_basic_credentials() { 92 | | let auth = base64_encode(&format!("{}:{}", user, pass)); 93 | | request = request.header("Authorization", &format!("Basic {}", auth)); 94 | | } 95 | | } | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#collapsible_if = note: `#[warn(clippy::collapsible_if)]` on by default help: collapse nested if block | 90 ~ if with_basic 91 ~ && let Some((user, pass)) = get_basic_credentials() { 92 | let auth = base64_encode(&format!("{}:{}", user, pass)); 93 | request = request.header("Authorization", &format!("Basic {}", auth)); 94 ~ } |

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
}
}
}
}

Expand Down
26 changes: 19 additions & 7 deletions sources/multi.suwayomi/src/settings.rs
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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no reason to remove this. you can also add a AUTH_MODE_KEY const.

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))
}
Loading