Greasy Fork

来自缓存

Greasy Fork is available in English.

BgmSyncF

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         BgmSyncF
// @version      0.4
// @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';
	const isUserPage = /^\/user\/[^/]+$/.test(window.location.pathname);
	if (!isUserPage) {
		return;
	}
	const limit = 50;
	let guess = 1000000;
	let totalItems = 0;
	let allData = [];
	let calculateButton;
	let buttonCounter = 0;
	let contextMenu = null;
	let settingsLink = null;
	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 collection_type = [
		1,
		2,
		3,
		4,
		5
	];
	let collectStatus = {};
	let subject_type_index = 0;
	let percentageBarDiv = null;
	const nameDiv = document.querySelector('.name');
	const realname = nameDiv.querySelector('a').textContent;
	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 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);
		}
		return null;
	}
	function confirmCacheRefresh() {
		const refreshCache = confirm('是否强制刷新缓存\uFF1F非必要请勿频繁刷新\uFF01');
		if (refreshCache) {
			localStorage.removeItem(`${ username }_totalsub`);
			localStorage.removeItem(`${ username }_likes`);
			likes = 0;
			totalsub = 0;
			subject_type_index = 0;
			allData = [];
			changeButtonText('计算全站同步率');
			calculateButton.removeEventListener('dblclick', confirmCacheRefresh);
		}
	}
	async function fetchData(collection_type, offset) {
		const url = `https://api.bgm.tv/v0/users/${ username }/collections?subject_type=${ subject_type[subject_type_index] }&type=${ collection_type }&limit=${ limit }&offset=${ offset }`;
		const headers = { 'Accept': 'application/json' };
		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`);
		if (cachedtotalsub !== null && cachedlikes !== null) {
			totalsub = cachedtotalsub;
			likes = cachedlikes;
			changeButtonText('已命中缓存');
			let syncRate = 0;
			if (totalsub > 0) {
				syncRate = likes / totalsub * 100;
			}
			updateUI();
			calculateButton.addEventListener('dblclick', confirmCacheRefresh);
		} else {
			calculateButton.style.pointerEvents = 'none';
			totalsub = 0;
			changeButtonText('计算中');
			for (let ct = 1; ct < collection_type.length; ct++) {
				for (let i = 0; i < subject_type.length; i++) {
					subject_type_index = i;
					const initialData = await fetchData(ct, guess);
					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(ct, offset);
						allData.push(...data.data);
						console.log(`Fetched ${ offset + 1 }-${ offset + limit } items...`);
						updateButtonText();
					}
				}
			}
			if (settingsLink !== null) {
				for (const item of allData) {
					const subjectId = item.subject_id;
					collectStatus[subjectId] = 'collect';
				}
				localStorage.setItem('bangumi_subject_collectStatus', JSON.stringify(collectStatus));
			}
			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 (Math.abs(rate - score) === 0) {
					likes++;
				}
				totalsub++;
			}
			changeButtonText('计算全站同步率');
			calculateButton.style.pointerEvents = 'auto';
			setLocalStorageWithExpiration(`${ username }_totalsub`, totalsub, 7);
			setLocalStorageWithExpiration(`${ username }_likes`, likes, 7);
			let syncRate = 0;
			if (totalsub > 0) {
				syncRate = likes / totalsub * 100;
			}
			updateUI();
		}
	}
	function updateUI() {
		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);
			}
		}
		let percentageBarDiv = document.querySelector('.BgmSyncF');
		if (!percentageBarDiv) {
			const synchronizeDiv = document.querySelector('.userSynchronize');
			if (synchronizeDiv) {
				percentageBarDiv = document.createElement('div');
				percentageBarDiv.className = 'BgmSyncF';
				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 changeButtonText(newText) {
		const span = document.querySelector('.chiiBtn > span.BgmSyncFButton');
		if (span) {
			span.textContent = newText;
		}
	}
	function updateButtonText() {
		if (buttonCounter < 5) {
			changeButtonText('计算中' + '.'.repeat(buttonCounter));
			buttonCounter++;
		} else {
			changeButtonText('计算中');
			buttonCounter = 1;
		}
	}
	function addButton() {
		const link = document.createElement('a');
		const span = document.createElement('span');
		span.textContent = '计算全站同步率';
		span.className = 'BgmSyncFButton';
		link.href = 'javascript:void(0)';
		link.className = 'chiiBtn';
		link.addEventListener('click', main);
		link.appendChild(span);
		const actionsDiv = document.querySelector('.nameSingle > .inner > .actions');
		settingsLink = actionsDiv.querySelector('a[href="/settings"]');
		actionsDiv.appendChild(link);
		calculateButton = link;
	}
	addButton();
	async function downloadJSON(data, filename) {
		if (data.length === 0) {
			alert('没有数据可下载\uFF0C请刷新缓存后重试\u3002');
			return;
		}
		try {
			const json = JSON.stringify(data);
			const blob = new Blob([json], { type: 'application/json' });
			const url = URL.createObjectURL(blob);
			const link = document.createElement('a');
			link.href = url;
			link.download = filename;
			link.click();
		} catch (error) {
			alert('意外错误\uFF01');
			console.error(error);
		}
	}
	document.addEventListener('contextmenu', event => {
		const target = event.target;
		if (target === calculateButton || target.parentElement === calculateButton) {
			event.preventDefault();
			closeContextMenu();
			contextMenu = document.createElement('div');
			contextMenu.className = 'context-menu';
			contextMenu.style.position = 'absolute';
			contextMenu.style.left = event.pageX + 'px';
			contextMenu.style.top = event.pageY + 'px';
			contextMenu.style.backgroundColor = '#333';
			contextMenu.style.border = '0px';
			contextMenu.style.padding = '0px';
			contextMenu.style.boxShadow = '2px 2px 4px rgba(0, 0, 0, 0.2)';
			const jsonOption = document.createElement('div');
			jsonOption.textContent = '下载JSON';
			jsonOption.style.cursor = 'pointer';
			jsonOption.style.padding = '8px 12px';
			jsonOption.style.color = 'white';
			jsonOption.addEventListener('click', () => {
				const timestamp = new Date().toISOString().replace(/:/g, '-');
				const filename = `BgmSyncFdata_${ username }_${ timestamp }.json`;
				downloadJSON(allData, filename);
				closeContextMenu();
			});
			jsonOption.addEventListener('mouseenter', () => {
				jsonOption.style.backgroundColor = '#444';
			});
			jsonOption.addEventListener('mouseleave', () => {
				jsonOption.style.backgroundColor = '#333';
			});
			contextMenu.appendChild(jsonOption);
			document.body.appendChild(contextMenu);
			const removeMenu = () => {
				closeContextMenu();
				document.removeEventListener('click', removeMenu);
			};
			document.addEventListener('click', removeMenu);
		}
	});
	function closeContextMenu() {
		if (contextMenu) {
			contextMenu.remove();
			contextMenu = null;
		}
	}
}());