1.2.7 • Published 5 years ago

webnovel.js v1.2.7

Weekly downloads
2
License
MIT
Repository
github
Last release
5 years ago

Webnovel/Qidian API client

npm bundle size NPM npm

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 = "some@mail.com";

  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

new Client()

Webnovel client

client.ctx

Kind: instance property of Client
Properties

NameTypeDescription
credentialsObjectAuth credentials
cookieJarCookieJarCookieJar instance
apiURLstringAPI base URL
authURLstringAuth API base URL
uuidstringIMEI/UUID,
sessionObjectSession properties, can be used to manually resume sessions
session.idnumberUser session ID
session.keystringUser session key
session.autoLoginKeystringUser session autologin key
session.autoLoginExpiresstringAutologin 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
ParamTypeDescription
encrystringencry property from the failed login response
codestringemail verif. code

client.login(emailVer) ⇒ Promise.<SessionInfo>

Login into Webnovel

Kind: instance method of Client
Throws:

  • AuthError
ParamTypeDefaultDescription
emailVerbooleanfalseSet 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

ParamType
bookIdstring
chapterIdstring

Client.Client

Kind: static class of Client

new Client(obj)

Creates an instance of Webnovel Client.

ParamTypeDescription
objObject
obj.usernamestringWebnovel username
obj.passwordstringWebnovel password
obj.apiURLstringoverride Webnovel API endpoint (Qidian should work)
obj.authURLstringoverride Webnovel auth API endoint (Qidian should work)
obj.uuidstringUUID is auto generated if not passed, which will trigger mail verification

SessionInfo : Object

Auth methods session info

Kind: global typedef
Properties

NameTypeDescription
codenumberstatus code.
dataObjectSession data
data.ticketstringSession validation ticket
data.ukeystringCurrently logged in user's key (used in jwkey cookie)
data.autoLoginFlagnumberWhether we logged in with an autologin flag
data.autoLoginSessionKeystringAL session key
data.autoLoginKeepTimenumberAL session lifetime
data.autoLoginExpiredTimenumberexpiration unix timestamp
data.useridnumberuser ID
msgstringok.

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
  });
}
1.2.7

5 years ago

1.2.6

5 years ago

1.2.5

5 years ago

1.2.4

5 years ago

1.2.3

5 years ago

1.2.2

5 years ago

1.2.1

5 years ago

1.2.0

5 years ago

1.1.1

5 years ago

1.1.0

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago