johnfactotum / foliate-js

Render e-books in the browser
https://johnfactotum.github.io/foliate-js/reader.html
MIT License
318 stars 43 forks source link

Api feature request for read online ebooks #3

Closed Febtw closed 1 year ago

Febtw commented 1 year ago

Thank you for your awesome work, I can now read e-books in the browser without installing various software on my devices. But I still want to further request the api feature for read online ebooks, so that I can use one link to read the online e-books directly without download manually. I think the link could be like this https://johnfactotum.github.io/foliate-js/reader.html?file=`url-of-ebook`

It is very presumptuous to make such a request, but I think it is an important feature for an ebook reader in browser, and I‘m looking fordward to its coming.

johnfactotum commented 1 year ago

I agree that it would be a nice feature. It's not always possible to request the file by url, though, as it would be cross origin and could be blocked. An alternative is that it could be a browser extension.

Febtw commented 1 year ago

I agree that it would be a nice feature. It's not always possible to request the file by url, though, as it would be cross origin and could be blocked. An alternative is that it could be a browser extension.

Thank you for your approval! Browser extension is very easy and convenient to use, but what I think is that through the api, users can not only read online e-books, but also embed and display ebooks on their own websites, making it more convenient to use across devices. Browser extension may not be very friendly to mobile devices, because most mobile browsers cannot install extensions.

johnfactotum commented 1 year ago

Well, here's a really quick patch. It requires relaxing connect-src to accept *. I think this is fine, because unsafe-inline is still blocked, and script-src only accepts 'self', not blob:. Also, it requires downloading the whole file. No range requests. Also it doesn't have a proper loading screen.

diff --git a/reader.html b/reader.html
index ec375bb..38bdd7b 100644
--- a/reader.html
+++ b/reader.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <meta name="color-scheme" content="light dark">
 <meta name="viewport" content="width=device-width, initial-scale=1">
-<meta http-equiv="Content-Security-Policy" content="default-src 'self' blob:; script-src 'self'; style-src 'self' blob: 'unsafe-inline'; img-src 'self' blob: data:; connect-src 'self' blob: data:; frame-src blob: data:; object-src blob: data:; form-action 'none';">
+<meta http-equiv="Content-Security-Policy" content="default-src 'self' blob:; script-src 'self'; style-src 'self' blob: 'unsafe-inline'; img-src 'self' blob: data:; connect-src 'self' blob: data: *; frame-src blob: data:; object-src blob: data:; form-action 'none';">
 <title>E-Book Reader</title>
 <style>
 :root {
diff --git a/reader.js b/reader.js
index 304a049..7e12197 100644
--- a/reader.js
+++ b/reader.js
@@ -48,14 +48,14 @@ const makeDirectoryLoader = async entry => {
 }

 const isCBZ = ({ name, type }) =>
-    type === 'application/vnd.comicbook+zip' || name.endsWith('.cbz')
+    type === 'application/vnd.comicbook+zip' || name?.endsWith('.cbz')

 const isFB2 = ({ name, type }) =>
-    type === 'application/x-fictionbook+xml' || name.endsWith('.fb2')
+    type === 'application/x-fictionbook+xml' || name?.endsWith('.fb2')

 const isFBZ = ({ name, type }) =>
     type === 'application/x-zip-compressed-fb2'
-    || name.endsWith('.fb2.zip') || name.endsWith('.fbz')
+    || name?.endsWith('.fb2.zip') || name?.endsWith('.fbz')

 const getView = async (file, emit) => {
     let book
@@ -320,3 +320,10 @@ dropTarget.addEventListener('dragover', dragOverHandler)
 $('#file-input').addEventListener('change', e =>
     open(e.target.files[0]).catch(e => console.error(e)))
 $('#file-button').addEventListener('click', () => $('#file-input').click())
+
+const params = new URLSearchParams(location.search)
+const url = params.get('url')
+if (url) fetch(url)
+    .then(res => res.blob())
+    .then(blob => open(blob))
+    .catch(e => console.error(e))

You can test this this locally with python -m http.server and http://0.0.0.0:8000/reader.html?url=https://s3.amazonaws.com/moby-dick/moby-dick.epub. Note that the file is from Epub.js examples.

Febtw commented 1 year ago

Well, here's a really quick patch. It requires relaxing connect-src to accept *. I think this is fine, because unsafe-inline is still blocked, and script-src only accepts 'self', not blob:. Also, it requires downloading the whole file. No range requests. Also it doesn't have a proper loading screen.

diff --git a/reader.html b/reader.html
index ec375bb..38bdd7b 100644
--- a/reader.html
+++ b/reader.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <meta name="color-scheme" content="light dark">
 <meta name="viewport" content="width=device-width, initial-scale=1">
-<meta http-equiv="Content-Security-Policy" content="default-src 'self' blob:; script-src 'self'; style-src 'self' blob: 'unsafe-inline'; img-src 'self' blob: data:; connect-src 'self' blob: data:; frame-src blob: data:; object-src blob: data:; form-action 'none';">
+<meta http-equiv="Content-Security-Policy" content="default-src 'self' blob:; script-src 'self'; style-src 'self' blob: 'unsafe-inline'; img-src 'self' blob: data:; connect-src 'self' blob: data: *; frame-src blob: data:; object-src blob: data:; form-action 'none';">
 <title>E-Book Reader</title>
 <style>
 :root {
diff --git a/reader.js b/reader.js
index 304a049..7e12197 100644
--- a/reader.js
+++ b/reader.js
@@ -48,14 +48,14 @@ const makeDirectoryLoader = async entry => {
 }

 const isCBZ = ({ name, type }) =>
-    type === 'application/vnd.comicbook+zip' || name.endsWith('.cbz')
+    type === 'application/vnd.comicbook+zip' || name?.endsWith('.cbz')

 const isFB2 = ({ name, type }) =>
-    type === 'application/x-fictionbook+xml' || name.endsWith('.fb2')
+    type === 'application/x-fictionbook+xml' || name?.endsWith('.fb2')

 const isFBZ = ({ name, type }) =>
     type === 'application/x-zip-compressed-fb2'
-    || name.endsWith('.fb2.zip') || name.endsWith('.fbz')
+    || name?.endsWith('.fb2.zip') || name?.endsWith('.fbz')

 const getView = async (file, emit) => {
     let book
@@ -320,3 +320,10 @@ dropTarget.addEventListener('dragover', dragOverHandler)
 $('#file-input').addEventListener('change', e =>
     open(e.target.files[0]).catch(e => console.error(e)))
 $('#file-button').addEventListener('click', () => $('#file-input').click())
+
+const params = new URLSearchParams(location.search)
+const url = params.get('url')
+if (url) fetch(url)
+    .then(res => res.blob())
+    .then(blob => open(blob))
+    .catch(e => console.error(e))

You can test this this locally with python -m http.server and http://0.0.0.0:8000/reader.html?url=https://s3.amazonaws.com/moby-dick/moby-dick.epub. Note that the file is from Epub.js examples.

I tried the code and it has worked almost perfectly. epub/azw/azw3/mobi files could be read from local and online. fb2/fb2.zip/cbz files, I don't use often, work normally from local, fail online. error code on Edge browser image

And it works well with Alist (A file list program that supports multiple storage)

tested files:

  1. epub file from https://s3.amazonaws.com/moby-dick/moby-dick.epub

  2. azw file from Kindle E-Reader User and Quick Start Guides, from https://kindle.s3.amazonaws.com/EN_US_Kindle_Users_Guide.azw To allow cross origin, it could be download from my site https://crossfireqq.cf/EN_US_Kindle_Users_Guide.azw

  3. azw3 file from https://filesamples.com/formats/azw3, from https://filesamples.com/samples/ebook/azw3/Alices%20Adventures%20in%20Wonderland.azw3 To allow cross origin check, it could be download from my site https://crossfireqq.cf/Alices%20Adventures%20in%20Wonderland.azw3

  4. mobi file from https://filesamples.com/formats/mobi, from https://filesamples.com/samples/ebook/mobi/Sway.mobi To allow cross origin, it could be download from my site https://crossfireqq.cf/Sway.mobi

  5. fb2 file from https://filesamples.com/formats/fb2, from https://filesamples.com/samples/ebook/fb2/Around%20the%20World%20in%2028%20Languages.fb2 To allow cross origin, it could be download from my site https://crossfireqq.cf/Around%20the%20World%20in%2028%20Languages.fb2

  6. fb2.zip file from http://www.bibles.org.uk/The-British-Study-Edition-of-the-Urantia-Papers.fb2.zip To allow cross origin, it could be download from my site https://crossfireqq.cf/The-British-Study-Edition-of-the-Urantia-Papers.fb2.zip

  7. cbz file from https://getcomics.info/dc/batgirls-14-2023/, from MEDIAFIRE

johnfactotum commented 1 year ago

Can't test right now but I'm guessing that it fails when the mimetype is not set correctly? Probably one should set the name property for the Blob object from the url so it could detect the file type based on the file extension.

Febtw commented 1 year ago

Can't test right now but I'm guessing that it fails when the mimetype is not set correctly? Probably one should set the name property for the Blob object from the url so it could detect the file type based on the file extension.

It‘s amazing that epub/azw/azw3/mobi files don't even need correct file extensions!

johnfactotum commented 1 year ago

Kindle files are detected with magic bytes, so it always works.

It works for EPUBs, but I guess that's actually the bug. It assumes that the file is EPUB if it's a zip file and it isn't CBZ or FBZ. What it should do instead is to check the mimetype file in the container.

Also, a better check for unzipped FB2 would be to first check the first few bytes for <?xml. So probably needs to move the parseXML function into reader.js. First check whether its XML, then parse it, and then decide what to do with it depending on the name of the root element.

Febtw commented 1 year ago

Kindle files are detected with magic bytes, so it always works.

It works for EPUBs, but I guess that's actually the bug. It assumes that the file is EPUB if it's a zip file and it isn't CBZ or FBZ. What it should do instead is to check the mimetype file in the container.

Also, a better check for unzipped FB2 would be to first check the first few bytes for <?xml. So probably needs to move the parseXML function into reader.js. First check whether its XML, then parse it, and then decide what to do with it depending on the name of the root element.

😳😳I don't know how to test it then.