dup

dmzj update predict

// ==UserScript==
// @name         dup
// @version      0.0.1
// @include      https://manhua.dmzj.com/*
// @description  dmzj update predict
// @grant        GM_xmlhttpRequest
// @namespace    https://greasyfork.org/users/164996a
// @require      https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js
// ==/UserScript==
// https://github.com/Tom-Alexander/regression-js
const Regression = () => {
  const DEFAULT_OPTIONS = { order: 2, precision: 2, period: null }
  function gaussianElimination(input, order) {
    const matrix = input
    const n = input.length - 1
    const coefficients = [order]
    for (let i = 0; i < n; i++) {
      let maxrow = i
      for (let j = i + 1; j < n; j++) {
        if (Math.abs(matrix[i][j]) > Math.abs(matrix[i][maxrow])) {
          maxrow = j
        }
      }
      for (let k = i; k < n + 1; k++) {
        const tmp = matrix[k][i]
        matrix[k][i] = matrix[k][maxrow]
        matrix[k][maxrow] = tmp
      }
      for (let j = i + 1; j < n; j++) {
        for (let k = n; k >= i; k--) {
          matrix[k][j] -= (matrix[k][i] * matrix[i][j]) / matrix[i][i]
        }
      }
    }
    for (let j = n - 1; j >= 0; j--) {
      let total = 0
      for (let k = j + 1; k < n; k++) {
        total += matrix[k][j] * coefficients[k]
      }
      coefficients[j] = (matrix[n][j] - total) / matrix[j][j]
    }
    return coefficients
  }
  function round(number, precision) {
    const factor = 10 ** precision
    return Math.round(number * factor) / factor
  }
  const methods = {
    linear(data, options) {
      const sum = [0, 0, 0, 0, 0]
      let len = 0

      for (let n = 0; n < data.length; n++) {
        if (data[n][1] !== null) {
          len++
          sum[0] += data[n][0]
          sum[1] += data[n][1]
          sum[2] += data[n][0] * data[n][0]
          sum[3] += data[n][0] * data[n][1]
          sum[4] += data[n][1] * data[n][1]
        }
      }

      const run = len * sum[2] - sum[0] * sum[0]
      const rise = len * sum[3] - sum[0] * sum[1]
      const gradient = run === 0 ? 0 : round(rise / run, options.precision)
      const intercept = round(sum[1] / len - (gradient * sum[0]) / len, options.precision)

      const predict = x => [
        round(x, options.precision),
        round(gradient * x + intercept, options.precision)
      ]

      const points = data.map(point => predict(point[0]))

      return {
        points,
        predict,
        equation: [gradient, intercept],
        r2: round(determinationCoefficient(data, points), options.precision),
        string: intercept === 0 ? `y = ${gradient}x` : `y = ${gradient}x + ${intercept}`
      }
    },
    polynomial(data, options) {
      const lhs = []
      const rhs = []
      let a = 0
      let b = 0
      const len = data.length
      const k = options.order + 1
      for (let i = 0; i < k; i++) {
        for (let l = 0; l < len; l++) {
          if (data[l][1] !== null) {
            a += data[l][0] ** i * data[l][1]
          }
        }
        lhs.push(a)
        a = 0
        const c = []
        for (let j = 0; j < k; j++) {
          for (let l = 0; l < len; l++) {
            if (data[l][1] !== null) {
              b += data[l][0] ** (i + j)
            }
          }
          c.push(b)
          b = 0
        }
        rhs.push(c)
      }
      rhs.push(lhs)
      const coefficients = gaussianElimination(rhs, k).map(v =>
        round(v, options.precision)
      )
      const predict = x => [
        round(x, options.precision),
        round(
          coefficients.reduce((sum, coeff, power) => sum + coeff * x ** power, 0),
          options.precision
        )
      ]
      return {
        predict
      }
    }
  }
  function createWrapper() {
    const reduce = (accumulator, name) => ({
      _round: round,
      ...accumulator,
      [name](data, supplied) {
        return methods[name](data, {
          ...DEFAULT_OPTIONS,
          ...supplied
        })
      }
    })
    return Object.keys(methods).reduce(reduce, {})
  }
  return createWrapper()
}

const gmFetch = url =>
  new Promise((resolve, reject) => {
    GM_xmlhttpRequest({
      url: url,
      method: 'GET',
      onload: resolve,
      onerror: reject
    })
  })
// https://github.com/tkkcc/flutter_dmzj/blob/master/lib/util/api.dart
const comic = async id => {
  const channel = 'Android'
  const version = '2.7.009'
  const api3 = 'https://v3api.dmzj.com'
  let a = await gmFetch(`${api3}/comic/${id}.json?channel=${channel}&version=${version}`)
  if (a.status !== 200) return
  a = JSON.parse(a.responseText)
  // only process first chapter
  if (a.status[0].tag_name !== '连载中') return
  // console.log(a)
  a = a.chapters[0].data.map(i => ({
    id: i.chapter_id,
    order: i.chapter_order,
    title: i.chapter_title,
    size: i.filesize,
    time: i.updatetime
  }))
  return a
}

const format = i => new Date(i * 1000).toISOString().slice(0, 10)
const human = i => {
  const a = new Date(i * 1000)
  const b = new Date()
  let c = ((a - b) / (1000 * 60 * 60 * 24)) >> 0
  // console.log(c)
  if (c === 0) return '今天更新'
  if (c === 1) return '明天更新'
  if (c < 7) return c + '天后更新'
  c = (c / 7) >> 0
  if (c < 3) return '下周更新'
  if (c < 5) return c + '周后更新'
  if (c < 6) return c + '本月更新'
}

const html = `
<style>
body {
  text-align: center;
}
div.regression_canvas {
  background: #fefefe;
  display: none;
  // margin: 3em;
  padding: 1em;
  width: 40em;
  left: -36em;
  top: 0em;
  z-index: 2;
}
span.regression_app:hover > div {
  display: inline-block;
  position: absolute;
}
span.regression_app {
  position: relative;
  color: slateblue;
  float: right;
}
</style>
<span class="regression_app">
<div class="regression_canvas">
  <canvas width="10" height="10"></canvas>
</div>
</span>`

// main
const main = async () => {
  // data
  if (typeof g_current_id === undefined) return
  const p = document.querySelector(
    'div.middleright div.odd_anim_title > div.odd_anim_title_m'
  )
  if (!p) return
  const a = await comic(g_current_id)
  if (!a || a.length < 5) return
  a.sort((a, b) => a.time - b.time)
  const b = a.slice(-5).map((i, index) => [index, i.time])
  const result = Regression().polynomial(b, { order: 2 })
  let d = result.predict(b.length)
  if (d[1] < b[b.length - 1][1]) return
  d = human(d[1])
  if (!d) return

  // dom
  p.insertAdjacentHTML('beforeend', html)
  document.querySelector('.regression_app').insertAdjacentText('afterbegin', d)
  const ctx = document
    .querySelector('.regression_canvas')
    .firstElementChild.getContext('2d')
  const config = {
    type: 'line',
    data: {
      labels: a.map(i => i.title),
      datasets: [
        {
          backgroundColor: 'slateblue',
          borderColor: 'slateblue',
          data: a.map(i => i.time),
          fill: false
        }
      ]
    },
    options: {
      responsive: true,
      legend: {
        display: false
      },
      title: {
        display: true,
        text: '更新记录'
      },
      tooltips: {
        intersect: false,
        callbacks: {
          title(item, data) {
            return item[0].xLabel + ' ' + format(item[0].yLabel) + '更新'
          },
          label() {}
        }
      },
      elements: {
        line: {
          tension: 0 // disables bezier curves
        }
      },
      scales: {
        xAxes: [
          {
            gridLines: {
              display: false
            }
          }
        ],
        yAxes: [
          {
            gridLines: {
              display: false
            },
            ticks: {
              callback: format
            }
          }
        ]
      }
    }
  }
  new Chart(ctx, config)
}
main()