npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

webnovel.js

v1.2.7

Published

Webnovel/Qidian closed API client for Node.js

Downloads

10

Readme

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 = "[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

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