BgmSyncF

https://bgm.tv/group/topic/386575

目前為 2023-08-31 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         BgmSyncF
// @version      0.3.1
// @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
// ==/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 = `
            <h3>${ realname }与全站的同步率</h3><small class="hot">/ ${ likes }个同分条目</small><p class="bar"><span class="percent_text rr">${ syncRate.toFixed(2) }%</span> <span class="percent" style="width:${ syncRate.toFixed(2) }%"></span> </p>
        `;
			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();
}());