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.
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.
- Client
Webnovel client, instantiate, login then call the API endpoints using
this.apiClient
- SessionInfo :
Object
Auth methods session info
- RegisterPayload :
Object
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>
- .register() β
Promise.<RegisterPayload>
- .confirmRegistration(emailkey, code) β
Promise.<SessionInfo>
- .confirmCode(encry, code) β
Promise.<SessionInfo>
- .login(emailVer) β
Promise.<SessionInfo>
- .resumeSession() β
Promise.<SessionInfo>
- .getChapter(bookId, chapterId) β
Promise.<Object>
- static
Webnovel client
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 |
Got auth (passport endpoint) client instance
Kind: instance property of Client
Access: public
Got API (idroid) client instance
Kind: instance property of Client
Access: public
client.register() β Promise.<RegisterPayload>
Register an account, gets credentials from Client context
Kind: instance method of Client
client.confirmRegistration(emailkey, code) β Promise.<SessionInfo>
Confirm registration and get session creds
Kind: instance method of Client
Param | Type |
---|---|
emailkey | string |
code | string |
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
gets and decrypts a chapter, unauthenticated requests probably won't work
Kind: instance method of Client
Param | Type |
---|---|
bookId | string |
chapterId | string |
Kind: static class of Client
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 |
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. |
Kind: global typedef Properties
Name | Type | Description |
---|---|---|
code | number |
status code |
data | Object |
data payload |
string |
email used for the registration | |
emailkey | string |
email confirmation code |
msg | string |
status message |
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: "https://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
});
}