您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Misc
// ==UserScript== // @name KDE Store: Graphs // @namespace https://github.com/Zren/ // @description Misc // @icon https://store.kde.org/images_sys/store_logo/kde-store.ico // @author Zren // @version 7 // @match https://www.opendesktop.org/member/*/plings* // @match https://www.opendesktop.org/u/*/plings* // @match https://store.kde.org/member/*/plings* // @match https://store.kde.org/u/*/plings* // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js // ==/UserScript== var el = function(html) { var e = document.createElement('div'); e.innerHTML = html; return e.removeChild(e.firstChild); } function daysLeftMultiplier() { var now = new Date() var startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) var endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) var totalTime = endOfMonth.valueOf() - startOfMonth.valueOf() var timeProcessed = now.valueOf() - startOfMonth.valueOf() var timeLeft = endOfMonth.valueOf() - now.valueOf() if (timeProcessed == 0) { return 1 } else { return 1 + (timeLeft / timeProcessed) } //var timeLeftInMonth = timeLeft / totalTime // return 1 + timeLeftInMonth if (timeLeftInMonth <= 0) { return 1 } else { return totalTime / timeLeft } } function zeropad(x, n) { var s = '' + x for (var i = s.length; i < n; i++) { s = '0' + s } return s } function getProductDownloadsForYearMonth(year, month) { // OpenDesktop defines: // var json_member = {"member_id":"433956","username":"Zren", ... }; var yearMonth = '' + year + zeropad(month+1, 2) var url = 'https://www.opendesktop.org/member/' + json_member.member_id + '/plingsmonthajax?yearmonth=' + yearMonth var cacheKey = 'ProductDownloadsForMonth-' + yearMonth var now = new Date() var isCurrentMonth = now.getFullYear() == year && now.getMonth() == month // Check cache first if (localStorage[cacheKey]) { var cacheData = JSON.parse(localStorage[cacheKey]) console.log('Grabbed', cacheKey, 'from localStorage cache') return Promise.resolve(cacheData) } return fetch(url, { }).then(function(res){ return res.text() }).then(function(text){ var monthData = {} var root = document.createElement('div') root.innerHTML = text var myProductList = root.querySelector('.my-products-list') var rows = myProductList.querySelectorAll('.tab-pane > .row:not(.row-total)') for (var row of rows) { var productName = row.children[1].querySelector('span').textContent var productDownloads = row.children[2].querySelector('span').textContent //console.log('graphData', productName, productDownloads, parseInt(productDownloads, 10)) productDownloads = parseInt(productDownloads, 10) monthData[productName] = productDownloads } monthData = { year: year, month: month, yearMonth: yearMonth, productDownloads: monthData, } // Save to cache if (!isCurrentMonth) { // Don't cache current month localStorage[cacheKey] = JSON.stringify(monthData) } return monthData }) } function getProductDownloadsOverTime() { var now = new Date() var month = new Date(now.getFullYear(), now.getMonth(), 1) var promises = [] for (var i = 0; i < 12; i++) { promises.push(getProductDownloadsForYearMonth(month.getFullYear(), month.getMonth())) // JavaScript's Date.month starts at 0-11 month.setMonth(month.getMonth() - 1) } return Promise.all(promises).then(function(values){ console.log('Promise.all.values', values) var graphData = {} graphData.labels = new Array(values.length).fill('') graphData.products = {} for (var monthIndex = 0; monthIndex < values.length; monthIndex++) { var monthData = values[monthIndex] graphData.labels[monthIndex] = monthData.yearMonth for (var productName of Object.keys(monthData.productDownloads)) { var productDownloads = monthData.productDownloads[productName] var productData = graphData.products[productName] if (!graphData.products[productName]) { productData = new Array(values.length).fill(0) graphData.products[productName] = productData } productData[monthIndex] = productDownloads } } return graphData }) } function randomColor() { // Based on the Random Pastel code from StackOverflow // https://stackoverflow.com/a/43195379/947742 return "hsl(" + 360 * Math.random() + ', ' + // Hue: Any (25 + 70 * Math.random()) + '%, ' + // Saturation: 25-95 (40 + 30 * Math.random()) + '%)'; // Lightness: 40-70 } // https://stackoverflow.com/a/44134328/947742 function hslToRgb(h, s, l) { h /= 360; s /= 100; l /= 100; var r, g, b; if (s === 0) { r = g = b = l; // achromatic } else { function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; } var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return { r:r, g:g, b:b } } function rgbToHex(c) { function toHex(x) { const hex = Math.round(x * 255).toString(16); return hex.length === 1 ? '0' + hex : hex; } return '#' + toHex(c.r) + toHex(c.g) + toHex(c.b) } function hslToHex(h, s, l) { var c = hslToRgb(h, s, l) function toHex(x) { const hex = Math.round(x * 255).toString(16); return hex.length === 1 ? '0' + hex : hex; } return '#' + toHex(c.r) + toHex(c.g) + toHex(c.b) } function rgbToRgba(c, a) { return 'rgba(' + Math.round(c.r * 255) + ', ' + Math.round(c.g * 255) + ', ' + Math.round(c.b * 255) + ', ' + a + ')' } function getPastelColor(i, n) { return hslToRgb(i / n * 360, 55, 70) } function convertToDatasets(graphData) { var datasets = [] var productNameList = Object.keys(graphData.products) productNameList.sort() for (var i = 0; i < productNameList.length; i++) { var productName = productNameList[i] var productData = graphData.products[productName] var dataset = {} dataset.label = productName dataset.data = Array.from(productData).reverse() dataset.fill = false var datasetColor = getPastelColor(i, productNameList.length) dataset.accentColor = rgbToHex(datasetColor) dataset.accentFadedColor = rgbToRgba(datasetColor, 0.2) dataset.backgroundColor = dataset.accentColor dataset.borderColor = dataset.accentColor dataset.lineTension = 0.1 datasets.push(dataset) } return datasets } function buildGraph(graphData) { console.log('graphData', graphData) window.graphData = graphData var datasets = window.datasets = convertToDatasets(graphData) //var labels = document.querySelectorAll('#my-payout-list ul.nav-tabs li a') //labels = Array.prototype.map.call(labels, function(e){ return e.textContent }) //labels = labels.reverse() //var labels = new Array(3).fill('Month') var labels = graphData.labels labels = labels.reverse() console.log('datasets', JSON.stringify(datasets)) console.log('labels', JSON.stringify(labels)) var graphParent = document.querySelector('.my-products-heading') var graphContainer = el('<div id="graphs" />') var graphCanvas = el('<canvas id="myChart" width="100vw" height="30vh"></canvas>') graphContainer.appendChild(graphCanvas) graphParent.parentNode.insertBefore(graphContainer, graphParent) //var navTabs = document.querySelector('#my-payout-list ul.nav-tabs') //var graphTab = el('<li><a href="#graphs" data-toggle="tab">Graphs</a></li>') //navTabs.insertBefore(graphTab, navTabs.firstChild) var ctx = document.getElementById("myChart").getContext("2d"); var myChart = window.myChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: datasets, }, options: { title: { display: true, text: 'Product Downloads Over Time', }, tooltips: { mode: 'index', intersect: false, itemSort: function (a, b, data) { return b.yLabel - a.yLabel // descending } }, legend: { position: 'left', onHover: function(e, legendItem) { if (myChart.hoveringLegendIndex != legendItem.datasetIndex) { myChart.hoveringLegendIndex = legendItem.datasetIndex for (var i = 0; i < myChart.data.datasets.length; i++) { var dataset = myChart.data.datasets[i] if (i == legendItem.datasetIndex) { dataset.borderColor = dataset.accentColor dataset.pointBackgroundColor = dataset.accentColor } else { dataset.borderColor = dataset.accentFadedColor dataset.pointBackgroundColor = dataset.accentFadedColor } } myChart.options.tooltips.enabled = false myChart.update() } } }, hover: { mode: 'nearest', intersect: true, }, scales: { yAxes: [{ //type: 'logarithmic', ticks: { //stepSize: 5, //beginAtZero:true, } }] } } }); myChart.hoveringLegendIndex = -1 myChart.canvas.addEventListener('mousemove', function(e) { if (myChart.hoveringLegendIndex >= 0) { if (e.layerX < myChart.legend.left || myChart.legend.right < e.layerX || e.layerY < myChart.legend.top || myChart.legend.bottom < e.layerY ) { myChart.hoveringLegendIndex = -1 for (var i = 0; i < myChart.data.datasets.length; i++) { var dataset = myChart.data.datasets[i] dataset.borderColor = dataset.accentColor dataset.pointBackgroundColor = dataset.accentColor } myChart.options.tooltips.enabled = true myChart.update() } } }) } function main() { getProductDownloadsOverTime().then(function(graphData){ buildGraph(graphData) }) } main()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址