// ==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()