您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在Cubox左上角添加一个随机漫游的按钮(也可使用Ctrl+R触发)
// ==UserScript== // @name Cubox助手 // @author 岚浅浅 // @description 在Cubox左上角添加一个随机漫游的按钮(也可使用Ctrl+R触发) // @namespace http://tampermonkey.net/ // @version 2.0.2 // @include *cubox.pro/* // @exclude *image.cubox.pro/* // @grant GM_addStyle // @license GPL-3.0 License // @require http://code.jquery.com/jquery-3.6.0.min.js // ==/UserScript== $(function () { 'use strict'; const HOST = location.hostname; const API_ENDPOINTS = { INBOX: `https://${HOST}/c/api/v2/search_engine/inbox`, MY: `https://${HOST}/c/api/v2/search_engine/my` }; const MAX_VISITED_CARDS = 1000; const PAGE_CACHE_DURATION = 60 * 1000; // 1分钟 const RETRY_COUNT = 10; const REQUEST_INTERVAL = 100; const REQUEST_TIMEOUT = 10000; const POPUP_DURATION = 3000; const FADE_DURATION = 300; class CacheManager { constructor() { this.initialize(); } initialize() { this.token = this.loadToken(); this.pageCache = this.loadPageCache(); this.visitedCards = this.loadVisitedCards(); } loadToken() { return localStorage.getItem('token'); } loadApiSearchUrl() { const apiSearchUrl = localStorage.getItem('cuboxAssistant_apiSearchUrl'); if (!apiSearchUrl) { console.log(location.href); throw new Error('URL信息缺失'); } return apiSearchUrl; } loadPageCache() { const storedCacheJson = localStorage.getItem('cuboxAssistant_pageCache'); if (!storedCacheJson) return {}; const storedCache = JSON.parse(storedCacheJson); const now = Date.now(); // 清理过期缓存 const validCache = Object.fromEntries( Object.entries(storedCache).filter(([, value]) => now <= value.expirationTime ) ); if (Object.keys(validCache).length !== Object.keys(storedCache).length) { this.setPageCache(validCache); } return validCache; } loadVisitedCards() { const ids = localStorage.getItem('cuboxAssistant_visitedCards'); return ids ? ids.split(',') : []; } getToken() { return this.token; } getVisitedCards() { return this.visitedCards; } setApiSearchUrl(url) { localStorage.setItem('cuboxAssistant_apiSearchUrl', url); } setPageCache(cache = this.pageCache) { localStorage.setItem('cuboxAssistant_pageCache', JSON.stringify(cache)); } setVisitedCards(cards = this.visitedCards) { if (cards.length > MAX_VISITED_CARDS) { cards.splice(0, cards.length - MAX_VISITED_CARDS); } localStorage.setItem('cuboxAssistant_visitedCards', cards.join(',')); } cacheSinglePage(apiSearchUrl, pageCount, page, lastBookmarkId) { const key = `${Utils.hash(apiSearchUrl)}_${page}`; this.pageCache[key] = { lastBookmarkId, expirationTime: Date.now() + pageCount * PAGE_CACHE_DURATION }; this.setPageCache(); } markCardAsVisited(cardId) { this.visitedCards.push(cardId); this.setVisitedCards(); } findLatestPage(apiSearchUrl, targetPage) { const hashPrefix = Utils.hash(apiSearchUrl); let bestJump = { page: 1, lastBookmarkId: '' }; Object.entries(this.pageCache).forEach(([key, value]) => { if (key.startsWith(hashPrefix)) { const page = parseInt(key.split('_')[1]); if (page < targetPage && page > bestJump.page) { bestJump = { page, lastBookmarkId: value.lastBookmarkId }; } } }); return bestJump; } clear() { this.setPageCache({}); this.setVisitedCards([]); this.initialize(); } } class CuboxAssistant { constructor() { StyleManager.initialize(); this.bindEvents(); this.initialize(); } bindEvents() { const handleRandom = (e) => { e.preventDefault(); this.startRandomNavigation(); }; $(document).on('click', '#cubox-start-btn', handleRandom); document.addEventListener('keydown', (event) => { if (event.ctrlKey && event.key === 'r') handleRandom(event); }); // URL变化监听 new MutationObserver(() => this.initialize()) .observe(document, { subtree: true, childList: true }); } async initialize() { if (this.currentUrl === location.href) { return; } this.currentUrl = location.href; StyleManager.updatePosition(this.currentUrl); const currentApiSearchUrl = Utils.matchApiSearchUrl(this.currentUrl) || cacheManager.loadApiSearchUrl(); if (currentApiSearchUrl && currentApiSearchUrl !== this.apiSearchUrl) { this.apiSearchUrl = currentApiSearchUrl; cacheManager.setApiSearchUrl(currentApiSearchUrl); await this.preloadPageCount(); await this.preloadPageCache(); } } async startRandomNavigation() { StyleManager.updateState(true); try { let isSuccess = false; for (let i = 0; i < RETRY_COUNT && !isSuccess; i++) { const randomPage = Math.floor(Math.random() * this.pageCount) + 1; const response = await this.requestPageWithCache(randomPage); isSuccess = this.navigateToRandomCard(response.data); } if (!isSuccess) { cacheManager.clear(); StyleManager.showPopup('缓存过多,已自动清理,请重试'); } } catch (error) { console.log(error); StyleManager.showPopup(`操作失败: ${error.message}`); } finally { StyleManager.updateState(false); } } async preloadPageCount() { const response = await Utils.safeRequest(this.apiSearchUrl); this.pageCount = response.pageCount || 1; console.log(`[Cubox助手] 共${this.pageCount}页`); const lastBookmarkId = Utils.extractLastBookmarkId(response); cacheManager.cacheSinglePage(this.apiSearchUrl, this.pageCount, 1, lastBookmarkId); } async preloadPageCache() { const missingPages = []; const hashPrefix = Utils.hash(this.apiSearchUrl); for (let page = 2; page <= this.pageCount; page++) { if (!cacheManager.pageCache[`${hashPrefix}_${page}`]) { missingPages.push(page); } } console.log(`[Cubox助手] ${missingPages.length}页需要预加载`); for (const page of missingPages) { await this.requestPageWithCache(page); await new Promise(resolve => setTimeout(resolve, REQUEST_INTERVAL)); } } async requestPageWithCache(targetPage) { if (targetPage === 1) { return await Utils.safeRequest(this.apiSearchUrl); } let { page, lastBookmarkId } = cacheManager.findLatestPage(this.apiSearchUrl, targetPage); let response; page++; while (page <= targetPage) { const url = `${this.apiSearchUrl.replace('page=1', `page=${page}`)}${lastBookmarkId ? `&lastBookmarkId=${lastBookmarkId}` : ''}`; response = await Utils.safeRequest(url); lastBookmarkId = Utils.extractLastBookmarkId(response); cacheManager.cacheSinglePage(this.apiSearchUrl, this.pageCount, page, lastBookmarkId); console.info(`[Cubox助手] 加载页面${page}成功`); page++; } return response; } navigateToRandomCard(cards) { const cardIds = cards.map(item => item.userSearchEngineID); const visitedIds = cacheManager.getVisitedCards(); const unvisitedIds = cardIds.filter(id => !visitedIds.includes(id)); if (unvisitedIds.length === 0) { return false; } const cardId = unvisitedIds[Math.floor(Math.random() * unvisitedIds.length)]; cacheManager.markCardAsVisited(cardId); window.open(`https://${HOST}/my/card?id=${cardId}`, '_self'); return true; } } class StyleManager { static initialize() { $('body').append(` <div id="cubox-assistant-toolbar"> <button id="cubox-start-btn" type="button">随机漫游</button> </div> `); GM_addStyle(` #cubox-assistant-toolbar { position: fixed; z-index: 999999; width: 120px; opacity: 0.8; border: 1px solid #a38a54; border-radius: 6px; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); box-shadow: 0 2px 12px rgba(0,0,0,0.15); transition: all 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } #cubox-assistant-toolbar:hover { opacity: 1; transform: translateY(-1px); } #cubox-start-btn { display: block; width: 100%; padding: 8px 12px; margin: 0; border: none; background: transparent; color: #a38a54; text-align: center; font-size: 13px; font-weight: 500; cursor: pointer; border-radius: 4px; transition: all 0.2s ease; } #cubox-start-btn:hover:not(:disabled) { background: rgba(163, 138, 84, 0.1); } #cubox-start-btn:disabled { opacity: 0.6; cursor: not-allowed; } .cubox-popup { position: fixed; top: 50px; left: 50%; transform: translate(-50%, -50%); padding: 20px 30px; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); color: #333; font-size: 16px; border-radius: 8px; box-shadow: 0 8px 32px rgba(0,0,0,0.2); z-index: 1000000; max-width: 400px; text-align: center; animation: fadeInScale 0.3s ease; } @keyframes fadeInScale { from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } `); } static updatePosition(currentUrl) { const isCardPage = /.*cubox\.pro\/my\/card\?id=(\d+)/.test(currentUrl); const top = isCardPage ? 10 : 20; const left = isCardPage ? 120 : 180; GM_addStyle(`#cubox-assistant-toolbar { top: ${top}px; left: ${left}px; }`); } static updateState(isLoading) { $('#cubox-start-btn').text(isLoading ? '加载中...' : '随机漫游').prop('disabled', isLoading); } static showPopup(message) { $('.cubox-popup').remove(); const popup = $(`<div class="cubox-popup">${$('<div>').text(message).html()}</div>`); $('body').append(popup); setTimeout(() => popup.fadeOut(FADE_DURATION, () => popup.remove()), POPUP_DURATION); } } class Utils { static matchApiSearchUrl(url) { const baseParams = 'asc=false&page=1&filters=&archiving=false'; if (url.includes('cubox.pro/my/inbox')) { return `${API_ENDPOINTS.INBOX}?${baseParams}`; } if (url.includes('cubox.pro/my/all')) { return `${API_ENDPOINTS.MY}?${baseParams}`; } if (url.includes('cubox.pro/my/folder?id=')) { const folderId = url.match(/id=([^&]*)/)?.[1]; if (folderId) { return `${API_ENDPOINTS.MY}?${baseParams}&groupId=${folderId}`; } } if (url.includes('cubox.pro/my/tag?id=')) { const tagId = url.match(/id=([^&]*)/)?.[1]; if (tagId) { return `${API_ENDPOINTS.MY}?${baseParams}&tagId=${tagId}`; } } } static hash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash = hash & hash; } return Math.abs(hash).toString(36); } static async safeRequest(url) { const response = await fetch(url, { headers: { 'Authorization': cacheManager.getToken() }, signal: AbortSignal.timeout(REQUEST_TIMEOUT) }); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); } static extractLastBookmarkId(response) { return response.data?.at(-1)?.userSearchEngineID || (() => { throw new Error('响应数据为空') })(); } } const cacheManager = new CacheManager(); new CuboxAssistant(); });