// ==UserScript== // @name BgmSyncF // @version 0.3 // @namespace https://jirehlov.com // @description https://bgm.tv/group/topic/386575 // @include /^https?:\/\/(bgm\.tv|chii\.in|bangumi\.tv)\/user/.+/ // @author Jirehlov // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; // Check if the current page is under /user/username const isUserPage = /^\/user\/[^/]+$/.test(window.location.pathname); if (!isUserPage) { return; // Stop script execution if not on the user page } const limit = 50; let guess = 1000000; let totalItems = 0; let allData = []; let calculateButton; let buttonCounter = 0; const [username, page = '', subpage = ''] = (() => { const {pathname} = window.location; if (/^\/user/.test(pathname)) { return pathname.match(/\/user\/(\w+)\/?(\w+)?\/?(\w+)?/).slice(1, 4); } return [ '', '', '' ]; })(); if (!username) { throw new Error('Username is not detected'); } let likes = 0; let totalsub = 0; let subject_type = [ 1, 2, 3, 4, 6 ]; let subject_type_index = 0; let percentageBarDiv = null; const nameDiv = document.querySelector('.name'); const realname = nameDiv.querySelector('a').textContent; // Function to set data in local storage with expiration time function setLocalStorageWithExpiration(key, value, expirationTimeInDays) { const expirationTimestamp = Date.now() + expirationTimeInDays * 24 * 60 * 60 * 1000; const dataToStore = { value, expiration: expirationTimestamp }; localStorage.setItem(key, JSON.stringify(dataToStore)); } // Function to get data from local storage function getLocalStorage(key) { const storedData = localStorage.getItem(key); if (storedData) { const data = JSON.parse(storedData); if (data.expiration && data.expiration > Date.now()) { return data.value; } localStorage.removeItem(key); // Remove expired data } return null; } function confirmCacheRefresh() { const refreshCache = confirm('是否强制刷新缓存\uFF1F'); if (refreshCache) { localStorage.removeItem(`${ username }_totalsub`); localStorage.removeItem(`${ username }_likes`); likes = 0; totalsub = 0; subject_type_index = 0; allData = []; calculateButton.textContent = '计算全站同步率'; } } async function fetchData(offset, cookie) { const url = `https://api.bgm.tv/v0/users/${ username }/collections?subject_type=${ subject_type[subject_type_index] }&type=2&limit=${ limit }&offset=${ offset }`; const headers = { 'Accept': 'application/json', 'Cookie': cookie }; const response = await fetch(url, { headers }); const data = await response.json(); return data; } async function main() { const cachedtotalsub = getLocalStorage(`${ username }_totalsub`); const cachedlikes = getLocalStorage(`${ username }_likes`); // Check if cache is valid if (cachedtotalsub !== null && cachedlikes !== null) { totalsub = cachedtotalsub; likes = cachedlikes; // Update button text to indicate calculation progress calculateButton.textContent = '已命中缓存'; // Calculate sync rate from cached values let syncRate = 0; if (totalsub > 0) { syncRate = likes / totalsub * 100; } // Update the UI with cached values updateUI(); // Add double-click event listener to the button calculateButton.addEventListener('dblclick', confirmCacheRefresh); } else { totalsub = 0; // Cache miss, fetch data from the API const cookie = document.cookie; // Update button text to indicate calculation progress calculateButton.textContent = '计算中...'; for (let i = 0; i < subject_type.length; i++) { subject_type_index = i; const initialData = await fetchData(guess, cookie); if ('description' in initialData && initialData.description.includes('equal to')) { totalItems = parseInt(initialData.description.split('equal to ')[1]); console.log(`Updated totalItems to: ${ totalItems }`); } else { totalItems = 0; } for (let offset = 0; offset < totalItems; offset += limit) { const data = await fetchData(offset, cookie); allData.push(...data.data); console.log(`Fetched ${ offset + 1 }-${ offset + limit } items...`); // Update button text with cyclic progress dots updateButtonText(); } } for (const item of allData) { const rate = item.rate === 0 ? 7 : parseFloat(item.rate || 0); const score = Math.round(parseFloat(item.subject && item.subject.score !== undefined ? item.subject.score : 0)); // If the difference between rate and score integer parts is 0, count it as a valid entry if (Math.abs(rate - score) === 0) { likes++; } totalsub++; } // Update button text to indicate calculation is complete calculateButton.textContent = '计算全站同步率'; // Store data in local storage with a 7-day expiration time setLocalStorageWithExpiration(`${ username }_totalsub`, totalsub, 7); setLocalStorageWithExpiration(`${ username }_likes`, likes, 7); // Calculate sync rate from fetched values let syncRate = 0; if (totalsub > 0) { syncRate = likes / totalsub * 100; } updateUI(); } } function updateUI() { // Add userSynchronize div if not present let synchronizeDiv = document.querySelector('.userSynchronize'); if (!synchronizeDiv) { const userBoxDiv = document.querySelector('.user_box.clearit'); if (userBoxDiv) { synchronizeDiv = document.createElement('div'); synchronizeDiv.className = 'userSynchronize'; userBoxDiv.appendChild(synchronizeDiv); } } // Add the percentage bar directly to the existing userSynchronize div if (!percentageBarDiv) { const synchronizeDiv = document.querySelector('.userSynchronize'); if (synchronizeDiv) { percentageBarDiv = document.createElement('div'); synchronizeDiv.appendChild(percentageBarDiv); } } if (percentageBarDiv) { let syncRate = 0; if (totalsub > 0) { syncRate = likes / totalsub * 100; } const percentageBar = `

${ realname }与全站的同步率

/ ${ likes }个共同喜好

${ syncRate.toFixed(2) }%

`; percentageBarDiv.innerHTML = percentageBar; } console.log(`Number of items with same rate and score: ${ likes }`); console.log(`Number of items in total: ${ totalsub }`); console.log(`Sync rate: ${ (likes / totalsub).toFixed(2) }`); } function updateButtonText() { if (buttonCounter < 5) { calculateButton.textContent = '计算中' + '.'.repeat(buttonCounter); buttonCounter++; } else { calculateButton.textContent = '计算中.'; buttonCounter = 1; } } function addButton() { const link = document.createElement('a'); link.href = 'javascript:void(0)'; link.textContent = '计算全站同步率'; link.className = 'chiiBtn'; link.addEventListener('click', main); const actionsDiv = document.querySelector('.nameSingle > .inner > .actions'); actionsDiv.appendChild(link); calculateButton = link; // Store the reference to the button } addButton(); }());