models/DraftEntry.js

const axios = require('axios')
const setCookie = require('set-cookie-parser')
const qs = require('querystring')
const { URLS } = require('../constants')

/**
 * Draft entry.
 */
class DraftEntry {
  /**
   * Entry author.
   *
   * @type {string}
   */
  author

  /**
   * Author URL.
   *
   * @type {string}
   */
  authorUrl

  /**
   * Entry content.
   *
   * @type {string}
   */
  content

  /**
   * Entry title.
   *
   * @type {string}
   */
  title

  /**
   * Title ID.
   *
   * @type {number}
   */
  titleId

  /**
   * Title slug.
   *
   * @type {number}
   */
  titleSlug

  /**
   * Entry title endpoint.
   *
   * @type {string}
   */
  titleEndpoint

  /**
   * Entry title URL.
   *
   * @type {string}
   */
  titleUrl

  /**
   * Create draft entry.
   *
   * @param {object}  request     Axios client.
   * @param {string}  title       Title.
   * @param {string}  cookies     Cookie string.
   */
  constructor (request, title, cookies) {
    this.title = title
    this._request = request
    this._cookies = cookies
    this._isRetrieved = false
  }

  /**
   * Parse properties with given document.
   *
   * @param   {object}  $    Cheerio document.
   * @ignore
   */
  serialize ($) {
    const username = $('div.mobile-notification-icons ul li a').attr('title')

    this.titleId = $('#title').data('id')
    this.titleUrl = URLS.BASE + $('h1#title a').attr('href')
    this.titleSlug = $('#title').data('slug')
    this.titleEndpoint = $('#title').data('slug') + '--' + this.titleId
    this.author = username
    this.authorUrl = URLS.USER + username
    this.content = $('#draft-content').text()

    // mark as retrieved
    this._isRetrieved = true
  }

  /**
   * Retrieve the draft entry.
   *
   * @returns {Promise} Promise.
   */
  retrieve () {
    return new Promise((resolve, reject) => {
      const requestOptions = {
        endpoint: `/?q=${this.title}`,
        cookie: this._cookies
      }
      this._request(requestOptions, $ => {
        this.serialize($)
        resolve()
      })
    })
  }

  /**
   * Publish the draft entry.
   *
   * @returns {Promise} Promise.
   */
  publish () {
    return new Promise((resolve, reject) => {
      if (!this._isRetrieved) {
        return reject(new Error('Retrieve first.'))
      }

      axios
        .get(`${URLS.BASE}/${this.titleEndpoint}`, {
          headers: {
            Cookie: this._cookies
          }
        })
        .then(res => {
          // parse csrf token and cookies
          const csrfRegex = new RegExp(
            '(?<=input name="__RequestVerificationToken" type="hidden" value=")(.*)(?=" />)',
            'u'
          )
          const csrfToken = csrfRegex.exec(res.data)[0]

          const cookies = setCookie.parse(res.headers['set-cookie'], {
            map: true
          })
          const csrfTokenInCookies = cookies.__RequestVerificationToken.value

          return { csrfToken, csrfTokenInCookies }
        })
        .then(async ({ csrfToken, csrfTokenInCookies }) => {
          // create entry
          const _res = await axios({
            url: URLS.ENTRY_CREATE,
            method: 'POST',
            headers: {
              Cookie: `__RequestVerificationToken=${csrfTokenInCookies}; ${this._cookies}`
            },
            data: qs.stringify({
              __RequestVerificationToken: csrfToken,
              Title: this.title,
              Content: this.content
            })
          })

          return _res
        })
        .then(res => {
          resolve()
        })
    })
  }

  /**
   * Change the draft entry content.
   *
   * @param   {string}  content Draft entry content.
   * @returns {Promise}         Promise.
   */
  change (content) {
    return new Promise((resolve, reject) => {
      if (!this._isRetrieved) {
        return reject(new Error('Retrieve first.'))
      }

      const requestBody = {
        title: this.title,
        content
      }

      const config = {
        headers: {
          'x-requested-with': 'XMLHttpRequest',
          cookie: this._cookies
        }
      }

      axios
        .post(
          `${URLS.BASE}/${this.titleEndpoint}/savedraft`,
          qs.stringify(requestBody),
          config
        )
        .then(res => {
          if (res.data.Success) {
            this.content = res.data.SavedDraftContent
            resolve()
          } else {
            reject(new Error('An unknown error occurred.'))
          }
        })
    })
  }

  /**
   * Dismiss the draft entry.
   *
   * @returns {Promise} Promise.
   */
  dismiss () {
    return new Promise((resolve, reject) => {
      if (!this._isRetrieved) {
        return reject(new Error('Retrieve first.'))
      }

      const requestBody = {
        title: this.title
      }

      const config = {
        headers: {
          'x-requested-with': 'XMLHttpRequest',
          cookie: this._cookies
        }
      }

      axios
        .post(
          `${URLS.BASE}/${this.titleEndpoint}/deletedraft`,
          qs.stringify(requestBody),
          config
        )
        .then(res => {
          resolve()
        })
    })
  }
}

module.exports = DraftEntry