Save My Kindle: Make EPUBs with JS
This Page is translated by automatic translation tool. Some text may be inaccurate or ambiguous.
This article is related to a personal project, from which some snippets in this article may be found.
data source
Looking at the client of DMZJ (a Chinese website for manga & novel) has long been unhappy, it is better to bully this APK while is not confuscated enough, and use their API to play with myself. With the idea of resurrecting my seven-year-old Kindle, this time I will catch an anime one. Home light novel source, make it an e-book for it to read (why don’t you catch comics? Don’t ask is today’s World Book Day). Although Anime Home has a lot of Chinese light novel resources, its mobile reading experience is not flattering, and the full-screen advertisements on the web basically cut off the possibility of reading with the slow and stuck browser of Kindle. The original idea is to host a web page locally and make a static reading page suitable for the Kindle browser through the DMZJ API (for performance reasons, it is best to directly disable the JavaScript of the Kindle browser). So I quickly thought that since the API is already available, wouldn’t it be beautiful to just pick it up and make an e-book and send it over? Anyway, everything is difficult at the beginning, as long as you get the API, the rest of the client, the web or the local storage can be easily at your fingertips. Of course, judging from the demise history of several versions of DMZJ clients that I have witnessed on the Windows UWP side, their APIs still change frequently, so it is better to store them locally.
As mentioned earlier, DMZJ’s API has been crawled at least a few years ago, so there have been numerous third-party API-based clients, and there are still APIs available on GitHub in 2021. But unfortunately, the results of the packet capture now show that the previous API is outdated, at least in the comic/novel chapter section, DMZJ’s API has introduced new encryption.

So sacrificed jadx
, directly decompiled to see its novel details page NovelInstructionActivity
source code. We can see that the basic information interface of the novel is encrypted.
1 |
|
Then we look at its RSAUtil
, the private key is in plaintext, you know.
Simply call it and find that the previously captured request can be decoded normally, and the content should be protobuf.
1 |
|
Use protoc --decode_raw < ~/Downloads/dmzj_resp.bin > ~/Downloads/dmzj_resp.txt
to decode, you can see the following structure:

But we also need to find the meaning of these fields. In the apk, we can see three ProtoBuf entity classes generated by protoc
:

For example, the Novel type corresponds to three objects NovelChapter, NovelInfo, NovelVolume, and their definitions are similar:
1 |
|
Apparently this corresponds to the structure of protobuf. At this point we can finally use the new version of the DMZJ API.
The following is the Protobuf IDL example of the DMZJ light novel I summarized:
1 |
|
Simply decode it with protobuf.js
to get the directory we want:
Of course, there is the last step to get the text of the corresponding chapter. This interface needs to be accessed with the volumeId
and chapterId
obtained in the previous step and the two queries t
and k
: http://jurisdiction.muwai .com/lnovel/${volumeId}_${chapterId}.txt
.

As the name suggests, t
is the current timestamp, and k
should be a random ID generated based on the timestamp, changing any of them, or not uploading will result in a 403.

Decompile NovelBrowsActivity
, you can see the logic of chapter refresh:
1 |
|
In the loadChapterNovel
method, we can see the method MyspUtils
that splices out the final URL, this is just a tool for taking SahredPreference, it will take the cache address of the corresponding URL from SahredPreference, and then load it locally:
1 |
|
After the cache is not hit, the NovelHelper.getLocalLocalFile()
method is called (this method will actually perform the download, but the name is… good coding practice). Therefore, the URL made earlier is only used to query the cache, and does not trigger the download behavior in this method.
In NovelHelper
, we finally see the part of calculating two queries, the key is an MD5 encoding mixed with timestamps.
1 |
|
Now we have a usable query.

Make an eBook
I have to admit that the recent API revision of DMZJ has indeed brought a lot of trouble to obtaining data. Fortunately, the obfuscation intensity of the client APK is very low, and its encryption logic and interface details can be obtained through decompilation. Now we have a complete data source, including book information (author, cover, region, tag, etc.), bibliographic information (volume, chapter), and the corresponding text for each chapter.
Here I used the npm module epub-gen
to generate Epub eBooks. Since Epub also applies HTML and CSS typesetting, you only need to provide HTML chapter text and external CSS to generate Epub. The use of epub-gen is quite simple. You only need to specify some options and construct an Epub object from the list of objects in the chapters as the content to complete the e-book. generation. The module can also automatically download the picture in the url as the cover.
The code to generate the Epub eBook from the data structure obtained earlier is as follows:
1 |
|

Everything works perfectly…but epub-gen
doesn’t actually support multi-level directories, which means, we get a volume-chapter two-level structure that cannot be generated into an epub….. . But it’s not a big problem. First of all, there are some workarounds, such as inserting empty pages in the data
of the volume, and then adding other chapters; secondly, you can directly modify the epub-gen
, which is beyond the scope of this article.
This article uses CC BY-SA 4.0 License. You may need to give appropriate credit, provide a link to the license, and indicate if changes were made when referencing this article.