// ==UserScript== // @name 豆瓣影视移动端适配 // @namespace https://scriptcat.org/zh-CN/ // @version 2.0 // @description tv/explore页强制保留桌面端不跳转m站,subject详情页自动跳转对应m站,tv页禁止横向滚动+滑到底部自动加载更多;m站tv/movie列表页自动跳转movie站tv#douban-desktop-adapt,搜索功能自动跳转移动版网页;movie.douban.com首页隐藏右侧榜单,仅留左侧推荐;自动加载稳定性大幅增强 // @author 失辛向南 // @match *://movie.douban.com/tv* // @match *://movie.douban.com/explore* // @match *://movie.douban.com/subject/* // @match *://movie.douban.com/ // @match *://m.douban.com/tv* // @match *://m.douban.com/movie* // @match *://search.douban.com/movie/subject_search* // @grant none // @run-at document-start // @license MIT // @supportURL https://scriptcat.org/zh-CN/script-show-page/5802/issue // ==/UserScript== (function() { 'use strict'; let isLoading = false; let loadObserver = null; let retryTimer = null; const CONFIG = { DESKTOP_UA: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", SUBJECT_REDIRECT: { enable: true, originHost: "movie.douban.com", targetHost: "m.douban.com", pathPrefix: "/movie" }, SEARCH_REDIRECT: { enable: true, originHost: "search.douban.com", originPath: "/movie/subject_search", targetHost: "m.douban.com", targetPath: "/search/", searchParam: "search_text", targetParam: "query" }, AUTO_LOAD: { enable: true, triggerDistance: 250, throttleDelay: 100, lockResetTime: 500, retryInterval: 1500 }, ADAPT_ENABLE: true, M_TO_MOVIE_REDIRECT: { enable: true, originHost: "m.douban.com", targetHost: "movie.douban.com", targetPath: "/tv", excludePathKeyword: "/subject/", matchListPaths: ["/tv", "/movie"] } }; const currentLocation = window.location; const currentHostname = currentLocation.hostname; const currentPathname = currentLocation.pathname; const currentHref = currentLocation.href; const isMovieDoubanDomain = currentHostname === CONFIG.SUBJECT_REDIRECT.originHost; const isTvPage = isMovieDoubanDomain && (currentPathname.startsWith('/tv') || currentPathname === '/tv'); const isExplorePage = isMovieDoubanDomain && (currentPathname.startsWith('/explore') || currentPathname === '/explore'); const needAdaptPage = isTvPage || isExplorePage; if (isTvPage && currentLocation.hash !== '#douban-desktop-adapt') { window.location.replace(`${currentLocation.origin}${currentPathname}#douban-desktop-adapt`); return; } if (needAdaptPage) { const rewriteNavigatorProp = (prop, value) => { Object.defineProperty(navigator, prop, { get: () => value, configurable: true, enumerable: true }); }; rewriteNavigatorProp('userAgent', CONFIG.DESKTOP_UA); rewriteNavigatorProp('appVersion', CONFIG.DESKTOP_UA.replace('Mozilla/', '')); rewriteNavigatorProp('platform', 'Win64'); rewriteNavigatorProp('oscpu', 'Windows NT 10.0; Win64; Win64; x64'); Object.defineProperty(window.screen, 'width', { get: () => 1920, configurable: true }); Object.defineProperty(window.screen, 'height', { get: () => 1080, configurable: true }); Object.defineProperty(window.screen, 'availWidth', { get: () => 1920, configurable: true }); Object.defineProperty(window.screen, 'availHeight', { get: () => 1040, configurable: true }); } const isSearchPage = currentHostname === CONFIG.SEARCH_REDIRECT.originHost && currentPathname.startsWith(CONFIG.SEARCH_REDIRECT.originPath); if (CONFIG.SEARCH_REDIRECT.enable && isSearchPage) { try { const urlParams = new URLSearchParams(currentLocation.search); const searchKeyword = urlParams.get(CONFIG.SEARCH_REDIRECT.searchParam); if (searchKeyword) { const targetUrl = new URL(currentLocation.origin + CONFIG.SEARCH_REDIRECT.targetPath); targetUrl.hostname = CONFIG.SEARCH_REDIRECT.targetHost; targetUrl.searchParams.set(CONFIG.SEARCH_REDIRECT.targetParam, searchKeyword); if (targetUrl.href !== currentHref) { window.location.replace(targetUrl.href); return; } } } catch (e) { console.error('搜索页跳转失败:', e); } } const isSubjectPage = isMovieDoubanDomain && currentPathname.startsWith('/subject/'); if (CONFIG.SUBJECT_REDIRECT.enable && isSubjectPage) { try { const targetUrl = new URL(currentHref); targetUrl.hostname = CONFIG.SUBJECT_REDIRECT.targetHost; targetUrl.pathname = CONFIG.SUBJECT_REDIRECT.pathPrefix + targetUrl.pathname; if (targetUrl.href !== currentHref) { window.location.replace(targetUrl.href); return; } } catch (e) { console.error('subject页面跳转失败:', e); } } const isMDoubanDomain = currentHostname === CONFIG.M_TO_MOVIE_REDIRECT.originHost; const isExcludeSubjectPage = currentPathname.includes(CONFIG.M_TO_MOVIE_REDIRECT.excludePathKeyword); const isMatchListPage = CONFIG.M_TO_MOVIE_REDIRECT.matchListPaths.some(path => currentPathname.startsWith(path) || currentPathname === path ); if (CONFIG.M_TO_MOVIE_REDIRECT.enable && isMDoubanDomain && isMatchListPage && !isExcludeSubjectPage) { try { const targetUrl = new URL(currentHref); targetUrl.hostname = CONFIG.M_TO_MOVIE_REDIRECT.targetHost; targetUrl.pathname = CONFIG.M_TO_MOVIE_REDIRECT.targetPath; targetUrl.hash = 'douban-desktop-adapt'; if (targetUrl.href !== currentHref) { window.location.replace(targetUrl.href); return; } } catch (e) { console.error('m站列表页跳转失败:', e); } } function throttle(fn, delay) { let lastTime = 0; return function(...args) { const now = Date.now(); if (now - lastTime >= delay) { lastTime = now; fn.apply(this, args); } }; } function getLoadMoreBtn() { const isValidBtn = (el) => { if (!el) return false; if (el.disabled) return false; if (el.offsetParent === null) { const rect = el.getBoundingClientRect(); if (rect.width === 0 && rect.height === 0) return false; } const text = (el.textContent || '').trim(); return text.includes('加载更多') || text.includes('Load more') || text.includes('更多'); }; const selectors = [ '.more a', '.list-more a', '.load-more a', '.paginator .next a', '.next a', '.load-more-btn', '.btn-more', '.more-btn', 'a.more', 'button.more', '[class*="load-more"]', '[class*="loadmore"]', '[class*="load_more"]' ]; for (const selector of selectors) { try { const elements = document.querySelectorAll(selector); for (const el of elements) { if (isValidBtn(el)) return el; } } catch (e) {} } try { const xpath = "//*[contains(text(),'加载更多') and not(ancestor::*[contains(@style,'display:none')])]"; const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); const node = result.singleNodeValue; if (node && isValidBtn(node)) return node; } catch (e) {} const all = document.querySelectorAll('a, button, .more, .load-more, [role="button"]'); for (const el of all) { if (isValidBtn(el)) return el; } return null; } function getScrollInfo() { const docEl = document.documentElement; const scrollTop = docEl.scrollTop || window.scrollY || 0; const windowHeight = docEl.clientHeight || window.innerHeight || 0; const documentHeight = docEl.scrollHeight || docEl.offsetHeight || 0; return { scrollTop, windowHeight, documentHeight }; } function handleAutoLoadMore() { if (!CONFIG.AUTO_LOAD.enable) return; if (isLoading) return; const { scrollTop, windowHeight, documentHeight } = getScrollInfo(); if (scrollTop + windowHeight >= documentHeight - CONFIG.AUTO_LOAD.triggerDistance) { const loadBtn = getLoadMoreBtn(); if (loadBtn) { isLoading = true; loadBtn.click(); setTimeout(() => { isLoading = false; setTimeout(() => handleAutoLoadMore(), 200); }, CONFIG.AUTO_LOAD.lockResetTime); } else { if (retryTimer) clearTimeout(retryTimer); retryTimer = setTimeout(() => { if (!isLoading) handleAutoLoadMore(); }, CONFIG.AUTO_LOAD.retryInterval); } } } function injectAdaptStyle() { const style = document.createElement('style'); style.type = 'text/css'; style.textContent = ` html, body { width: 100% !important; max-width: 100vw !important; overflow-x: hidden !important; overflow-y: auto !important; font-size: 14px !important; padding: 0 !important; margin: 0 !important; touch-action: pan-y !important; } * { box-sizing: border-box !important; max-width: 100vw !important; overflow-x: hidden !important; } @media screen and (max-width: 768px) { .container, .wrapper, #wrapper, .main, .content, #db-global-nav, .top-nav, .nav-container, .explore-container, .tv-list-container, .article, .grid-view, .list-view { width: 100% !important; max-width: 100% !important; min-width: unset !important; padding: 0 8px !important; margin: 0 auto !important; } .nav-items, .nav, .header-nav, .top-nav-items { flex-wrap: wrap !important; gap: 8px !important; justify-content: flex-start !important; } .filter, .filters, .search-filter, .filter-bar, .explore-filter, .tv-filter, .tags, .filter-tags { width: 100% !important; flex-wrap: wrap !important; gap: 6px !important; padding: 8px 0 !important; } .filter-item, .tag, .filter-tag, .tab-item { flex-shrink: 0 !important; font-size: 13px !important; padding: 4px 8px !important; white-space: nowrap !important; } .grid, .items-grid, .movie-list, .list-container, #explore-items, .explore-list, .grid-list, .tv-list, .list-view, .album-list { display: grid !important; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)) !important; gap: 12px !important; width: 100% !important; padding: 0 !important; } .item, .card, .movie-item, .list-item, .tv-item, .subject-item { width: 100% !important; margin: 0 !important; } img, .cover, .poster { max-width: 100% !important; height: auto !important; } .aside, .sidebar, .ad, .global-sidebar, .right-col, .banner-ad { display: none !important; } .section, .module, .panel { padding: 10px 0 !important; } button, .btn, a, .tab-item, .filter-item { min-height: 44px !important; min-width: 44px !important; line-height: 44px !important; } } `; document.head.appendChild(style); } function setViewport() { let meta = document.querySelector('meta[name="viewport"]'); if (!meta) { meta = document.createElement('meta'); meta.name = 'viewport'; document.head.appendChild(meta); } meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; } function createListObserver() { if (!needAdaptPage || !CONFIG.AUTO_LOAD.enable) return; const listContainer = document.querySelector('.list-view, .grid-view, .tv-list, .explore-list, #content, .article'); if (listContainer && !loadObserver) { loadObserver = new MutationObserver(() => { setTimeout(() => { if (!isLoading) handleAutoLoadMore(); }, 200); }); loadObserver.observe(listContainer, { childList: true, subtree: true }); } else if (!listContainer) { setTimeout(createListObserver, 1000); } } function adaptHomePage() { const isHomePage = currentHostname === 'movie.douban.com' && (currentPathname === '/' || currentPathname === ''); if (!isHomePage) return; const homeStyle = document.createElement('style'); homeStyle.textContent = ` .grid-8, .aside, .recommendations, .sidebar, .right-col, .rankings, .top250, .reviews, .news, .ad, .global-sidebar, .banner-ad { display: none !important; } .grid-16, .main, .content, .screening, .recommend, .feed { width: 100% !important; max-width: 100% !important; margin-right: 0 !important; float: none !important; } .grid-16-8, .container, .wrapper { width: 100% !important; max-width: 100% !important; padding: 0 !important; margin: 0 !important; display: block !important; } body, html { overflow-x: hidden !important; width: 100% !important; position: relative !important; } .items, .movie-list, .list, .grid, .screening-list { display: grid !important; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)) !important; gap: 12px !important; } .item, .movie-item, .list-item { width: 100% !important; margin: 0 !important; } img { max-width: 100% !important; height: auto !important; } `; document.head.appendChild(homeStyle); setViewport(); const observer = new MutationObserver(() => { const rightSide = document.querySelector('.grid-8, .aside, .recommendations, .sidebar'); if (rightSide && rightSide.style.display !== 'none') { rightSide.style.display = 'none'; } const leftMain = document.querySelector('.grid-16, .main, .content'); if (leftMain) { leftMain.style.width = '100%'; leftMain.style.maxWidth = '100%'; } }); observer.observe(document.body, { childList: true, subtree: true }); } function initAfterDOMLoad() { adaptHomePage(); if (!needAdaptPage) return; if (CONFIG.ADAPT_ENABLE) { setViewport(); injectAdaptStyle(); } if (CONFIG.AUTO_LOAD.enable) { window.addEventListener('scroll', throttle(handleAutoLoadMore, CONFIG.AUTO_LOAD.throttleDelay), { passive: true }); window.addEventListener('load', () => { setTimeout(handleAutoLoadMore, 500); }); window.addEventListener('resize', throttle(handleAutoLoadMore, 150), { passive: true }); createListObserver(); handleAutoLoadMore(); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAfterDOMLoad); } else { initAfterDOMLoad(); } })();