您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
站内收藏管理功能(支持集数保存)
// ==UserScript== // @name Anime1 本地收藏夹 // @namespace http://tampermonkey.net/ // @version 1.2 // @description 站内收藏管理功能(支持集数保存) // @author zhist // @match https://anime1.me/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function () { 'use strict'; // 样式配置 GM_addStyle(` .anime1-collect-btn { position: fixed; top: 20px; right: 20px; z-index: 9999; padding: 10px 15px; background: #ff4757; color: white; border: none; border-radius: 25px; cursor: pointer; box-shadow: 0 3px 10px rgba(255,71,87,0.4); transition: all 0.3s; font-weight: bold; } .anime1-collect-btn:hover { transform: scale(1.05); background: #ff6b81; } .bookmarks-panel { position: fixed; top: 70px; right: 20px; width: 350px; background: rgba(255,255,255,0.95); border-radius: 10px; box-shadow: 0 8px 30px rgba(0,0,0,0.12); backdrop-filter: blur(10px); padding: 15px; display: none; max-height: 70vh; overflow-y: auto; } .bookmark-item { display: flex; align-items: center; padding: 12px; margin: 8px 0; background: #fff; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.08); transition: transform 0.2s; } .bookmark-item:hover { transform: translateX(5px); } .bookmark-content { flex: 1; overflow: hidden; } .bookmark-link { display: block; color: #2f3542; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; } .bookmark-episode { font-size: 0.85em; color: #ff6b81; margin-top: 2px; font-weight: bold; } .bookmark-time { font-size: 0.75em; color: #999; margin-top: 2px; } .delete-btn { color: #ff4757; cursor: pointer; margin-left: 10px; padding: 3px; border-radius: 50%; width: 22px; height: 22px; text-align: center; line-height: 22px; flex-shrink: 0; } .delete-btn:hover { background: #ffe4e6; } .episode-indicator { background: #ff6b81; color: white; padding: 2px 6px; border-radius: 10px; font-size: 0.75em; margin-left: 8px; font-weight: bold; } `); const watchedSet = new Set(); // 存储系统初始化 const STORAGE_KEY = 'anime1_bookmarks'; let bookmarks = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); let currentEpisode = null; const btn = createCollectButton(); // 保存收藏数据 function saveBookmarks() { localStorage.setItem(STORAGE_KEY, JSON.stringify(bookmarks)); } function setCurrentEpisode(episode) { if (!watchedSet.has(episode)) { watchedSet.add(episode); currentEpisode = episode; console.log('更新currentEpisode:', episode); updateButtonText(btn); updateEpisode(btn) } } // 从URL中提取集数 // function extractEpisodeFromUrl(url) { // // 匹配类似 https://hajime.v.anime1.me/1623/9.mp4 的格式 // const match = url.match(/\/(\d+)\.mp4$/); // return match ? parseInt(match[1]) : null; // } // 从URL中提取集数 function extractEpisodeFromUrl(url) { // 匹配类似以下格式的URL: // https://hajime.v.anime1.me/1623/9.mp4 → 集数: 9 // https://bocchi.v.anime1.me/1655/8b.mp4 → 集数: 8 // 格式: /数字/集数数字+其它什么.mp4,但只提取开头的数字部分 const match = url.match(/\/\d+\/(\d+)[^\/]*\.mp4$/); return match ? parseInt(match[1]) : null; } // 监听所有网络请求来捕获视频URL function interceptNetworkRequests() { // 拦截XMLHttpRequest const originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url, ...args) { if (url.includes('.mp4') && url.includes('anime1.me')) { const episode = extractEpisodeFromUrl(url); if (episode !== null) { setCurrentEpisode(episode); console.log('检测到视频集数:', episode); } } return originalXHROpen.call(this, method, url, ...args); }; // 拦截fetch请求 const originalFetch = window.fetch; window.fetch = function (url, ...args) { if (typeof url === 'string' && url.includes('.mp4') && url.includes('anime1.me')) { const episode = extractEpisodeFromUrl(url); if (episode !== null) { setCurrentEpisode(episode); console.log('检测到视频集数:', episode); updateEpisodeIfSaved(episode) } } return originalFetch.call(this, url, ...args); }; } // 监听所有网络流量(包括video标签的src变化) function monitorVideoElements() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName === 'src') { const target = mutation.target; if (target.tagName === 'VIDEO' || target.tagName === 'SOURCE') { const src = target.src || target.getAttribute('src'); if (src && src.includes('.mp4') && src.includes('anime1.me')) { const episode = extractEpisodeFromUrl(src); if (episode !== null) { setCurrentEpisode(episode); console.log('检测到视频集数:', episode); } } } } // 检查新增的video元素 if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // Element node const videos = node.tagName === 'VIDEO' ? [node] : node.querySelectorAll('video'); videos.forEach((video) => { if (video.src && video.src.includes('.mp4') && video.src.includes('anime1.me')) { const episode = extractEpisodeFromUrl(video.src); if (episode !== null) { setCurrentEpisode(episode); console.log('检测到视频集数:', episode); } } }); } }); } }); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] }); } // 定期检查页面中的视频元素 function checkExistingVideos() { const videos = document.querySelectorAll('video, source'); videos.forEach((video) => { const src = video.src || video.getAttribute('src'); if (src && src.includes('.mp4') && src.includes('anime1.me')) { const episode = extractEpisodeFromUrl(src); if (episode !== null) { setCurrentEpisode(episode); // console.log('检测到视频集数:', episode); } } }); } // 判断当前是否视频页面 function isVideoPage() { return location.pathname.includes('/category'); } // 判断当前视频是否被藏 function isFavorited() { const currentUrl = location.href; const existingIndex = bookmarks.findIndex(b => b.url === currentUrl); return existingIndex; } // 创建收藏按钮 function createCollectButton() { const btn = document.createElement('button'); btn.className = 'anime1-collect-btn'; updateButtonText(btn); return btn; } // 更新按钮文本 function updateButtonText(btn) { if (isVideoPage()) { if (isFavorited() === -1) { const episodeText = currentEpisode ? ` (第${currentEpisode}集)` : ''; btn.innerHTML = `⭐ 收藏本视频${episodeText}`; } else { btn.innerHTML = '📚 查看收藏夹'; } } else { btn.innerHTML = '📚 查看收藏夹'; } } // 创建收藏面板 function createBookmarkPanel() { const panel = document.createElement('div'); panel.className = 'bookmarks-panel'; return panel; } // 格式化时间 function formatTime(timeString) { const date = new Date(timeString); return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); } // 渲染收藏列表 function renderBookmarks(panel) { panel.innerHTML = bookmarks.length ? bookmarks.map((b, i) => ` <div class="bookmark-item"> <div class="bookmark-content"> <a href="${b.url}" class="bookmark-link" target="_blank">${b.title}</a> ${b.episode ? `<div class="bookmark-episode">第 ${b.episode} 集</div>` : ''} <div class="bookmark-time">${formatTime(b.time)}</div> </div> <div class="delete-btn" data-index="${i}">×</div> </div> `).join('') : '<div style="text-align:center; color:#666; padding: 20px;">暂无收藏内容</div>'; } // 更新集数 function updateEpisode(btn) { let existingIndex, r1, r2, r3, r4; // 更新现有收藏的集数信息 // console.log('try to update episode : ' + currentEpisode) if ((r1 = currentEpisode) && // (r2=!watchedSet.has(currentEpisode)) && (r3 = (existingIndex = isFavorited()) !== -1) && (r4 = bookmarks[existingIndex].episode !== currentEpisode)) { bookmarks[existingIndex].episode = currentEpisode; bookmarks[existingIndex].time = new Date().toISOString(); // 更新时间 saveBookmarks(); btn.innerHTML = `🔄 已更新至第${currentEpisode}集!`; setTimeout(() => updateButtonText(btn), 2000); } console.log(r1, r2, r3, r4) // else { // btn.innerHTML = '⚠️ 已存在!'; // setTimeout(() => updateButtonText(btn), 1000); // } } // 主逻辑 function init() { // 启动网络监听 interceptNetworkRequests(); monitorVideoElements(); // 定期检查视频元素 setInterval(checkExistingVideos, 2000); const container = document.createElement('div'); const panel = createBookmarkPanel(); document.body.appendChild(btn); document.body.appendChild(panel); // 定期更新按钮文本(当检测到新集数时) // setInterval(() => { updateButtonText(btn); updateEpisode(btn) // }, 1000); // 按钮点击事件 btn.addEventListener('click', () => { if (isVideoPage()) { // 收藏当前页面 const currentUrl = location.href; const currentTitle = document.title; const existingIndex = bookmarks.findIndex(b => b.url === currentUrl); if (existingIndex === -1) { // 新增收藏 bookmarks.unshift({ title: currentTitle, url: currentUrl, episode: currentEpisode, time: new Date().toISOString() }); saveBookmarks(); btn.innerHTML = `✅ 已收藏!${currentEpisode ? ` (第${currentEpisode}集)` : ''}`; setTimeout(() => updateButtonText(btn), 1500); } else { panel.style.display = panel.style.display === 'block' ? 'none' : 'block'; if (panel.style.display === 'block') renderBookmarks(panel); } } else { // 切换收藏面板 panel.style.display = panel.style.display === 'block' ? 'none' : 'block'; if (panel.style.display === 'block') renderBookmarks(panel); } }); // 删除功能 panel.addEventListener('click', (e) => { if (e.target.classList.contains('delete-btn')) { const index = parseInt(e.target.dataset.index); bookmarks.splice(index, 1); saveBookmarks(); renderBookmarks(panel); updateButtonText(btn); } }); // 点击外部关闭 document.addEventListener('click', (e) => { if (!btn.contains(e.target) && !panel.contains(e.target)) { panel.style.display = 'none'; } }); } // 等待页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();