webnovel.js
v1.2.7
Published
Webnovel/Qidian closed API client for Node.js
Downloads
10
Readme
Webnovel/Qidian API client
Node.js client for Qidian APIs build by reverse engineering their native application. Uses native app endpoints.
Note: All endpoints default to webnovel.com's.
If you just want to use this as a reference the useful files are:
The rest is just traffic sniffing.
Usage
yarn add webnovel.js
const { Client: WNClient } = require("webnovel.js");
(async () => {
const username = "[email protected]";
const client = new WNClient({
username,
password: "supersekret",
uuid: "000000003ede1bf9000000003ede1bf9" // UUID
});
const res = await client.login(true);
// Since we set emailVer = true we need to manually check for "encry" (email verification token)
// you can of course catch and call client.sendEmail() on your own
const {
data: { encry }
} = res;
// if you want raw tokens (ticket, autologin)
let user;
if (encry) {
const code = await getCodeUsingIMAP(email); // somehow get the emailed code
user = await client.confirmCode(encry, code); // cookies now set
} else {
user = res;
}
const {
body: {
Data: { Email }
}
} = await client.apiClient("/user/get");
console.log(Email === username); // true
// Ze Tian Ji 😍
const bookId = "8205217405006105";
// destructure the first chapter
// *note*: check com.qidian.QDReader.components.book.al.QDChapterManager
// you probably need to send some other requests, I'm getting some incorrect
// chapter IDs.. maybe /book-case/report-operation-time
const {
body: {
Data: {
Chapters: [, { Id: secChptID }]
}
}
} = await client.apiClient("/book/get-chapters", {
query: {
bookId,
maxUpdateTime: 0,
maxIndex: 0,
sign: ""
}
});
const chapter = await client.getChapter(bookId, secChptID);
// ...
})();
The client class only implements complex/encrypted/signed requests, so for the most part you need to manually find the endpoint you need and use the Client.apiClient
Got instance to request it.
Most API endpoints are declared in the com.qidian.QDReader.components.api
package, in the Urls
class.
Maybe have a look at examples too.
Classes
Typedefs
Client
Webnovel client, instantiate, login then call the API endpoints using this.apiClient
Kind: global class
- Client
- new Client()
- instance
- .ctx
- .authClient : got.GotInstance.<got.GotJSONFn>
- .apiClient : got.GotInstance.<got.GotJSONFn>
- .confirmCode(encry, code) ⇒ Promise.<SessionInfo>
- .login(emailVer) ⇒ Promise.<SessionInfo>
- .resumeSession() ⇒ Promise.<SessionInfo>
- .getChapter(bookId, chapterId) ⇒ Promise.<Object>
- static
new Client()
Webnovel client
client.ctx
Kind: instance property of Client
Properties
| Name | Type | Description | | ------------------------ | ---------------------- | ----------------------------------------------------------- | | credentials | Object | Auth credentials | | cookieJar | CookieJar | CookieJar instance | | apiURL | string | API base URL | | authURL | string | Auth API base URL | | uuid | string | IMEI/UUID, | | session | Object | Session properties, can be used to manually resume sessions | | session.id | number | User session ID | | session.key | string | User session key | | session.autoLoginKey | string | User session autologin key | | session.autoLoginExpires | string | Autologin expiration time |
client.authClient : got.GotInstance.<got.GotJSONFn>
Got auth (passport endpoint) client instance
Kind: instance property of Client
Access: public
client.apiClient : got.GotInstance.<got.GotJSONFn>
Got API (idroid) client instance
Kind: instance property of Client
Access: public
client.confirmCode(encry, code) ⇒ Promise.<SessionInfo>
Login using email verification code (if login method returned code 11318)
Kind: instance method of Client
Throws:
- AuthError
| Param | Type | Description | | ----- | ------------------- | --------------------------------------------- | | encry | string | encry property from the failed login response | | code | string | email verif. code |
client.login(emailVer) ⇒ Promise.<SessionInfo>
Login into Webnovel
Kind: instance method of Client
Throws:
- AuthError
| Param | Type | Default | Description | | -------- | -------------------- | ------------------ | --------------------------------------------------------------------- | | emailVer | boolean | false | Set to true if you want it to pass and send an email with a ver. code |
client.resumeSession() ⇒ Promise.<SessionInfo>
Resume current session
Kind: instance method of Client
client.getChapter(bookId, chapterId) ⇒ Promise.<Object>
gets and decrypts a chapter, unauthenticated requests probably won't work
Kind: instance method of Client
| Param | Type | | --------- | ------------------- | | bookId | string | | chapterId | string |
Client.Client
Kind: static class of Client
new Client(obj)
Creates an instance of Webnovel Client.
| Param | Type | Description | | ------------ | ------------------- | -------------------------------------------------------------------------- | | obj | Object | | | obj.username | string | Webnovel username | | obj.password | string | Webnovel password | | obj.apiURL | string | override Webnovel API endpoint (Qidian should work) | | obj.authURL | string | override Webnovel auth API endoint (Qidian should work) | | obj.uuid | string | UUID is auto generated if not passed, which will trigger mail verification |
SessionInfo : Object
Auth methods session info
Kind: global typedef
Properties
| Name | Type | Description | | ------------------------- | ------------------- | ----------------------------------------------------- | | code | number | status code. | | data | Object | Session data | | data.ticket | string | Session validation ticket | | data.ukey | string | Currently logged in user's key (used in jwkey cookie) | | data.autoLoginFlag | number | Whether we logged in with an autologin flag | | data.autoLoginSessionKey | string | AL session key | | data.autoLoginKeepTime | number | AL session lifetime | | data.autoLoginExpiredTime | number | expiration unix timestamp | | data.userid | number | user ID | | msg | string | ok. |
Web login
Here's the web login version: note: csrf token is static
const NodeRSA = require("node-rsa");
// Webnovel's auth RSA key
// https://passport.webnovel.com/login.html
const keyData = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOf5B7Sg/EsfK+29BhFn1SUgoX
gcLP9Dl1Sf3g3PgwRTEkqMwhFVpIYoNVo1TV1q6Y6dRYZ1BExt/tqQqJcLvQhCKc
b4JuINKdftwG5le+Q2n6S/Ioyx7euYZgkmm3LSQ5VW7JmWV9VJFOIm4mpHmom9kE
CwVP/wBG9hmUs+USSwIDAQAB
-----END PUBLIC KEY-----`;
/**
* web passport aes encryption
* @param {string} str
* @returns {string} aes -> base64 encoded string
*/
function rsaEncrypt(str) {
const key = new NodeRSA();
key.importKey(keyData, "public");
return key.encrypt(str, "base64");
}
/**
* @param {Object} q
* @param {string} q.username - webnovel username
* @param {string} q.password - webnovel password
* @param {string} q._csrfToken - CSRF token from cookie
* @param {CookieJar} cookieJar
* @returns {GotPromise<string>}
*/
function web(q, cookieJar) {
const query = {
appId: 900,
areaId: 1,
source: "",
returnurl: "http://www.webnovel.com",
version: "",
imei: "",
qimei: "",
target: "",
format: "",
ticket: "",
autotime: "",
auto: 1, // adds autologin tokens
fromuid: 0,
method: "LoginV1.checkCodeCallback",
logintype: 22,
username: "",
password: rsaEncrypt(q.password),
_csrfToken: q._csrfToken
};
return got("https://ptlogin.webnovel.com/login/checkcode", {
query,
headers: {
"user-agent":
"Mozilla/5.0 (X11; Linux x86_64; rv:69.0) Gecko/20100101 Firefox/69.0"
},
cookieJar,
json: true
});
}