Skip to content

feat: add en.theblank - #624

Open
KitIsCooked wants to merge 5 commits into
Aidoku-Community:mainfrom
KitIsCooked:feat/en.theblank
Open

feat: add en.theblank#624
KitIsCooked wants to merge 5 commits into
Aidoku-Community:mainfrom
KitIsCooked:feat/en.theblank

Conversation

@KitIsCooked

@KitIsCooked KitIsCooked commented Jul 17, 2026

Copy link
Copy Markdown

Adds a source for theblank.net, an English manga/manhwa/pornhwa reader. Implements search, manga details, chapter list, page list with HMAC-SHA256 signed image URLs, listings, and deep link handling.

fix: clippy warnings in en.theblank

feat: add en.theblank

@kkantan kkantan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the code in lib.rs could be split into models.rs for the json types and helpers.rs for helper functions, as other sources do.

looking at the keiyoushi source, it looks like it uses the "pam" template. it would be nice if this was refactored into a new template so that other sources that use the same one could be added more easily, but it's not strictly necessary.

Comment thread sources/en.theblank/src/lib.rs Outdated
}

/// Parse ISO-8601 datetime → Unix timestamp in seconds.
fn parse_iso_date(s: &str) -> i64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you should be able to use the aidoku-rs parse_date function with a provided date format.

Comment thread sources/en.theblank/res/source.json Outdated
Comment thread sources/en.theblank/res/source.json Outdated
Comment thread sources/en.theblank/src/lib.rs Outdated
Comment on lines +193 to +213
let js = format!(
r#"(async () => {{
const token = '{token}';
const page = {page};
const ts = Math.floor(Date.now() / 1000);
const nonceArr = new Uint8Array(8);
crypto.getRandomValues(nonceArr);
const nonce = Array.from(nonceArr).map(b => b.toString(16).padStart(2,'0')).join('');
const keyBytes = new Uint8Array(token.match(/.{{2}}/g).map(b => parseInt(b,16)));
const key = await crypto.subtle.importKey('raw', keyBytes, {{name:'HMAC',hash:'SHA-256'}}, false, ['sign']);
const msg = page.toString(16).padStart(2,'0') + ts.toString() + nonce;
const sig = Array.from(new Uint8Array(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(msg))))
.map(b => b.toString(16).padStart(2,'0')).join('');
return JSON.stringify({{ts: ts.toString(), nonce, sig}});
}})()"#,
token = token,
page = page,
);

let result = JsContext::new().eval_async(&js).unwrap_or_default();
let params: Option<HmacParams> = serde_json::from_str(&result).ok();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it would be preferable if this were implemented in rust rather than js, since both using the js context is discouraged when possible, and the async eval is a new api that most users I believe still don't have access to. there should be a no_std rust hmac or crypto crate that does the same thing.

Comment thread sources/en.theblank/src/lib.rs Outdated
Comment thread sources/en.theblank/src/lib.rs Outdated
.into_iter()
.map(|c| {
let key = format!("{}|{}", s.slug, c.slug);
Chapter {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it would be nicer if From<LibrarySerie> was implemented for Manga, and likewise for chapters, so you could just map(Into::into).

Comment thread sources/en.theblank/src/lib.rs Outdated

// ─── Source ───────────────────────────────────────────────────────────────────

struct Theblank;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
struct Theblank;
struct TheBlank;

Comment thread sources/en.theblank/src/lib.rs
Comment thread sources/en.theblank/Cargo.toml Outdated
panic = "abort"
opt-level = "s"
strip = true
lto = true No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
lto = true
lto = true

Comment thread sources/en.theblank/res/icon.png
@KitIsCooked

KitIsCooked commented Sep 1, 2026

Copy link
Copy Markdown
Author

Addressed all review comments --- split into models.rs/helpers.rs, replaced JsContext HMAC with pure Rust hmac-sha256, used parse_date, added From impls, added filters.json, and fixed the icon.

@kkantan kkantan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

most comments from the past review were not applied, with some being marked resolved despite this. also, you should set the minimumAppVersion to 0.7.1 and run cargo clippy to fix lint warnings.

Comment thread sources/en.theblank/res/icon.png
}
}

// ─── From impls ───────────────────────────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these should be in models.rs.


pub const BASE_URL: &str = "https://theblank.net";

// ─── MangaStatus helper ───────────────────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this should be in helpers.rs or models.rs.

}

let html = fetch_html(&url)?;
let props: LibraryProps = parse_inertia(&html).ok_or(AidokuError::Message(

Copy link
Copy Markdown
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 error! macro.

Comment thread sources/en.theblank/src/lib.rs Outdated
Comment on lines +263 to +267
impl Home for Theblank {
fn get_home(&self) -> Result<HomeLayout> {
Err(AidokuError::Unimplemented)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this should not be here if it's not implemented.

@KitIsCooked
KitIsCooked requested a review from kkantan September 2, 2026 07:46
KitIsCooked and others added 3 commits September 2, 2026 13:37
Co-authored-by: kantan <31490942+kkantan@users.noreply.github.com>
Co-authored-by: kantan <31490942+kkantan@users.noreply.github.com>
@KitIsCooked

Copy link
Copy Markdown
Author

Addressed all feedback -- used QueryParameters for URL building, removed details from search results, renamed to TheBlank, removed language field from chapters, removed unnecessary early return, added 128x128 icon, and split into models.rs/helpers.rs.

@kkantan kkantan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

my previous review comments were not addressed.

/// Fetch a URL with a mobile User-Agent to avoid Cloudflare blocks.
pub fn fetch_html(url: &str) -> Result<String> {
Request::get(url)
.map_err(|e| AidokuError::Message(format!("request error: {:?}", e)))?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I suggested this before on one place, but it applies to everywhere you use AidokuError::Message.

Suggested change
.map_err(|e| AidokuError::Message(format!("request error: {:?}", e)))?
.map_err(|e| error!("request error: {:?}", e))?

Comment on lines +89 to +91
if let Some(q) = &query {
params.push("search", Some(q));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if let Some(q) = &query {
params.push("search", Some(q));
}
if query.is_some() {
params.push("search", query.as_deref());
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants