Greasy Fork 支持简体中文。

dup

dmzj update predict

  1. // ==UserScript==
  2. // @name dup
  3. // @version 0.0.1
  4. // @include https://manhua.dmzj.com/*
  5. // @description dmzj update predict
  6. // @grant GM_xmlhttpRequest
  7. // @namespace https://greasyfork.org/users/164996a
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js
  9. // ==/UserScript==
  10. // https://github.com/Tom-Alexander/regression-js
  11. const Regression = () => {
  12. const DEFAULT_OPTIONS = { order: 2, precision: 2, period: null }
  13. function gaussianElimination(input, order) {
  14. const matrix = input
  15. const n = input.length - 1
  16. const coefficients = [order]
  17. for (let i = 0; i < n; i++) {
  18. let maxrow = i
  19. for (let j = i + 1; j < n; j++) {
  20. if (Math.abs(matrix[i][j]) > Math.abs(matrix[i][maxrow])) {
  21. maxrow = j
  22. }
  23. }
  24. for (let k = i; k < n + 1; k++) {
  25. const tmp = matrix[k][i]
  26. matrix[k][i] = matrix[k][maxrow]
  27. matrix[k][maxrow] = tmp
  28. }
  29. for (let j = i + 1; j < n; j++) {
  30. for (let k = n; k >= i; k--) {
  31. matrix[k][j] -= (matrix[k][i] * matrix[i][j]) / matrix[i][i]
  32. }
  33. }
  34. }
  35. for (let j = n - 1; j >= 0; j--) {
  36. let total = 0
  37. for (let k = j + 1; k < n; k++) {
  38. total += matrix[k][j] * coefficients[k]
  39. }
  40. coefficients[j] = (matrix[n][j] - total) / matrix[j][j]
  41. }
  42. return coefficients
  43. }
  44. function round(number, precision) {
  45. const factor = 10 ** precision
  46. return Math.round(number * factor) / factor
  47. }
  48. const methods = {
  49. linear(data, options) {
  50. const sum = [0, 0, 0, 0, 0]
  51. let len = 0
  52.  
  53. for (let n = 0; n < data.length; n++) {
  54. if (data[n][1] !== null) {
  55. len++
  56. sum[0] += data[n][0]
  57. sum[1] += data[n][1]
  58. sum[2] += data[n][0] * data[n][0]
  59. sum[3] += data[n][0] * data[n][1]
  60. sum[4] += data[n][1] * data[n][1]
  61. }
  62. }
  63.  
  64. const run = len * sum[2] - sum[0] * sum[0]
  65. const rise = len * sum[3] - sum[0] * sum[1]
  66. const gradient = run === 0 ? 0 : round(rise / run, options.precision)
  67. const intercept = round(sum[1] / len - (gradient * sum[0]) / len, options.precision)
  68.  
  69. const predict = x => [
  70. round(x, options.precision),
  71. round(gradient * x + intercept, options.precision)
  72. ]
  73.  
  74. const points = data.map(point => predict(point[0]))
  75.  
  76. return {
  77. points,
  78. predict,
  79. equation: [gradient, intercept],
  80. r2: round(determinationCoefficient(data, points), options.precision),
  81. string: intercept === 0 ? `y = ${gradient}x` : `y = ${gradient}x + ${intercept}`
  82. }
  83. },
  84. polynomial(data, options) {
  85. const lhs = []
  86. const rhs = []
  87. let a = 0
  88. let b = 0
  89. const len = data.length
  90. const k = options.order + 1
  91. for (let i = 0; i < k; i++) {
  92. for (let l = 0; l < len; l++) {
  93. if (data[l][1] !== null) {
  94. a += data[l][0] ** i * data[l][1]
  95. }
  96. }
  97. lhs.push(a)
  98. a = 0
  99. const c = []
  100. for (let j = 0; j < k; j++) {
  101. for (let l = 0; l < len; l++) {
  102. if (data[l][1] !== null) {
  103. b += data[l][0] ** (i + j)
  104. }
  105. }
  106. c.push(b)
  107. b = 0
  108. }
  109. rhs.push(c)
  110. }
  111. rhs.push(lhs)
  112. const coefficients = gaussianElimination(rhs, k).map(v =>
  113. round(v, options.precision)
  114. )
  115. const predict = x => [
  116. round(x, options.precision),
  117. round(
  118. coefficients.reduce((sum, coeff, power) => sum + coeff * x ** power, 0),
  119. options.precision
  120. )
  121. ]
  122. return {
  123. predict
  124. }
  125. }
  126. }
  127. function createWrapper() {
  128. const reduce = (accumulator, name) => ({
  129. _round: round,
  130. ...accumulator,
  131. [name](data, supplied) {
  132. return methods[name](data, {
  133. ...DEFAULT_OPTIONS,
  134. ...supplied
  135. })
  136. }
  137. })
  138. return Object.keys(methods).reduce(reduce, {})
  139. }
  140. return createWrapper()
  141. }
  142.  
  143. const gmFetch = url =>
  144. new Promise((resolve, reject) => {
  145. GM_xmlhttpRequest({
  146. url: url,
  147. method: 'GET',
  148. onload: resolve,
  149. onerror: reject
  150. })
  151. })
  152. // https://github.com/tkkcc/flutter_dmzj/blob/master/lib/util/api.dart
  153. const comic = async id => {
  154. const channel = 'Android'
  155. const version = '2.7.009'
  156. const api3 = 'https://v3api.dmzj.com'
  157. let a = await gmFetch(`${api3}/comic/${id}.json?channel=${channel}&version=${version}`)
  158. if (a.status !== 200) return
  159. a = JSON.parse(a.responseText)
  160. // only process first chapter
  161. if (a.status[0].tag_name !== '连载中') return
  162. // console.log(a)
  163. a = a.chapters[0].data.map(i => ({
  164. id: i.chapter_id,
  165. order: i.chapter_order,
  166. title: i.chapter_title,
  167. size: i.filesize,
  168. time: i.updatetime
  169. }))
  170. return a
  171. }
  172.  
  173. const format = i => new Date(i * 1000).toISOString().slice(0, 10)
  174. const human = i => {
  175. const a = new Date(i * 1000)
  176. const b = new Date()
  177. let c = ((a - b) / (1000 * 60 * 60 * 24)) >> 0
  178. // console.log(c)
  179. if (c === 0) return '今天更新'
  180. if (c === 1) return '明天更新'
  181. if (c < 7) return c + '天后更新'
  182. c = (c / 7) >> 0
  183. if (c < 3) return '下周更新'
  184. if (c < 5) return c + '周后更新'
  185. if (c < 6) return c + '本月更新'
  186. }
  187.  
  188. const html = `
  189. <style>
  190. body {
  191. text-align: center;
  192. }
  193. div.regression_canvas {
  194. background: #fefefe;
  195. display: none;
  196. // margin: 3em;
  197. padding: 1em;
  198. width: 40em;
  199. left: -36em;
  200. top: 0em;
  201. z-index: 2;
  202. }
  203. span.regression_app:hover > div {
  204. display: inline-block;
  205. position: absolute;
  206. }
  207. span.regression_app {
  208. position: relative;
  209. color: slateblue;
  210. float: right;
  211. }
  212. </style>
  213. <span class="regression_app">
  214. <div class="regression_canvas">
  215. <canvas width="10" height="10"></canvas>
  216. </div>
  217. </span>`
  218.  
  219. // main
  220. const main = async () => {
  221. // data
  222. if (typeof g_current_id === undefined) return
  223. const p = document.querySelector(
  224. 'div.middleright div.odd_anim_title > div.odd_anim_title_m'
  225. )
  226. if (!p) return
  227. const a = await comic(g_current_id)
  228. if (!a || a.length < 5) return
  229. a.sort((a, b) => a.time - b.time)
  230. const b = a.slice(-5).map((i, index) => [index, i.time])
  231. const result = Regression().polynomial(b, { order: 2 })
  232. let d = result.predict(b.length)
  233. if (d[1] < b[b.length - 1][1]) return
  234. d = human(d[1])
  235. if (!d) return
  236.  
  237. // dom
  238. p.insertAdjacentHTML('beforeend', html)
  239. document.querySelector('.regression_app').insertAdjacentText('afterbegin', d)
  240. const ctx = document
  241. .querySelector('.regression_canvas')
  242. .firstElementChild.getContext('2d')
  243. const config = {
  244. type: 'line',
  245. data: {
  246. labels: a.map(i => i.title),
  247. datasets: [
  248. {
  249. backgroundColor: 'slateblue',
  250. borderColor: 'slateblue',
  251. data: a.map(i => i.time),
  252. fill: false
  253. }
  254. ]
  255. },
  256. options: {
  257. responsive: true,
  258. legend: {
  259. display: false
  260. },
  261. title: {
  262. display: true,
  263. text: '更新记录'
  264. },
  265. tooltips: {
  266. intersect: false,
  267. callbacks: {
  268. title(item, data) {
  269. return item[0].xLabel + ' ' + format(item[0].yLabel) + '更新'
  270. },
  271. label() {}
  272. }
  273. },
  274. elements: {
  275. line: {
  276. tension: 0 // disables bezier curves
  277. }
  278. },
  279. scales: {
  280. xAxes: [
  281. {
  282. gridLines: {
  283. display: false
  284. }
  285. }
  286. ],
  287. yAxes: [
  288. {
  289. gridLines: {
  290. display: false
  291. },
  292. ticks: {
  293. callback: format
  294. }
  295. }
  296. ]
  297. }
  298. }
  299. }
  300. new Chart(ctx, config)
  301. }
  302. main()