// 接管手机端底部音乐按钮 function modify_m_bottom_music_button_link() { const music = document.querySelector('a[href*="#m_bottom_music_button"]'); if (!music) return; music.addEventListener('click', function (event) { event.preventDefault(); if (!box_up) mu_box_show(); else mu_box_hide(0); }); } // 登录逻辑 let csrfToken = ''; let publicKey = ''; let loginTimer = null; let countdownSeconds = 3; let closeLoading = null; const login_modal = document.getElementById('customLoginModal'); const login_closeBtn = document.getElementById('closeLoginModal'); const login_loginBtn = document.getElementById('customLoginBtn'); // 获取令牌及公钥 async function fetchCSRFToken() { login_modal.classList.add('show'); login_loginBtn.disabled = true; login_loginBtn.textContent = '加载中...'; closeLoading = cocoMessage.loading('正在加载署名信息...'); try { const response = await fetch('/login', { credentials: 'include' }); if (response.url.includes('/uc')) { return 'logged'; } const htmlText = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(htmlText, 'text/html'); // 获取 CSRF Token const csrfInput = doc.querySelector('input[name="_csrf"]'); if (!csrfInput || !csrfInput.value) { throw new Error('页面未找到 CSRF 令牌'); } csrfToken = csrfInput.value; // 获取 PublicKey const publicKeyMatch = htmlText.match(/const publicKey = "([^"]+)";/); if (!publicKeyMatch || !publicKeyMatch[1]) { throw new Error('页面未找到 PublicKey'); } publicKey = publicKeyMatch[1].replace(/\\\//g, '/'); return 'ok'; } catch (err) { closeLoading?.() cocoMessage.error('署名信息加载失败,请刷新'); if (loginTimer) { clearInterval(loginTimer) } login_loginBtn.textContent = '错误!请刷新'; login_loginBtn.disabled = true; return 'error'; } } // 打开弹窗逻辑 async function open_login_box() { if (loginTimer) { clearInterval(loginTimer) } countdownSeconds = 3; const success = await fetchCSRFToken(); if (success === 'logged') { closeLoading?.() cocoMessage.success('您已署名成功,请刷新'); login_modal.classList.remove('show'); } else if (success === 'ok') { loginTimer = setInterval(() => { if (countdownSeconds > 0) { login_loginBtn.textContent = '请稍等...' + countdownSeconds; } else { clearInterval(loginTimer); closeLoading?.() cocoMessage.success('署名信息加载完成'); login_loginBtn.textContent = '署 名'; login_loginBtn.disabled = false; } countdownSeconds--; }, 1000); } else { closeLoading?.() cocoMessage.error('服务器错误,请刷新'); login_modal.classList.remove('show'); } } // 关闭弹窗 login_closeBtn.addEventListener('click', () => { closeLoading?.() login_modal.classList.remove('show'); if (loginTimer) { clearInterval(loginTimer) } }); // 加密逻辑 function encryptPassword(password) { try { const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); return encrypt.encrypt(password); } catch (e) { return null; } } // 提交登录 login_loginBtn.addEventListener('click', async () => { let islogin = false; const username = document.getElementById('customUsername').value.trim(); const password = document.getElementById('customPassword').value.trim(); if (!username || !password) { cocoMessage.warning('请先填写信息'); return; } const encryptedPwd = encryptPassword(password); login_loginBtn.disabled = true; login_closeBtn.disabled = true; login_loginBtn.textContent = '验证中...'; closeLoading = cocoMessage.loading('正在验证信息...'); try { countdownSeconds = 3 const response = await fetch('/login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, credentials: 'include', body: new URLSearchParams({ _csrf: csrfToken, username: username, password: encryptedPwd, 'remember-me': 'true' }) }); // 核心细节:判断最终落点 URL const finalUrl = response.url; if (loginTimer) { clearInterval(loginTimer); } if (finalUrl.includes('/uc')) { islogin = true; closeLoading?.() cocoMessage.success('署名成功,正在跳转...'); login_loginBtn.textContent = '跳转中...'; const currentScrollTop = window.scrollY || document.documentElement.scrollTop; $.cookie('load_type', 'login', { path: '/' }); window.location.reload(); } else if (finalUrl.includes('error=invalid-credential')) { closeLoading?.() cocoMessage.warning('署名人与心上锁不匹配'); } else { closeLoading?.() cocoMessage.error('速度过快,请稍后重试'); } } catch (err) { closeLoading?.() cocoMessage.error('服务器连接失败'); } finally { if (!islogin) { fetchCSRFToken(); login_closeBtn.disabled = false; loginTimer = setInterval(() => { if (countdownSeconds > 0) { login_loginBtn.textContent = '请稍等...' + countdownSeconds; } else { clearInterval(loginTimer); closeLoading?.() cocoMessage.success('请重新尝试署名'); login_loginBtn.textContent = '署 名'; login_loginBtn.disabled = false; } countdownSeconds--; }, 1000); } } }); // 回车登录 document.addEventListener("keydown", function (e) { if (e.key === "Enter") { const modal = document.getElementById("customLoginModal"); if (modal.classList.contains("show")) { document.getElementById("customLoginBtn").click(); } } }); // 退出登录 async function customLogout() { $('.pixar-notice-btn-confirm, .pixar-notice-btn-close').prop('disabled', true); let closeLoading = cocoMessage.loading('退出登录中...'); try { const getResponse = await fetch('/logout', { credentials: 'include' }); const htmlText = await getResponse.text(); const parser = new DOMParser(); const doc = parser.parseFromString(htmlText, 'text/html'); const logoutCsrfToken = doc.querySelector('input[name="_csrf"]')?.value; if (!logoutCsrfToken) { cocoMessage.error('页面未找到 CSRF 令牌'); $('.pixar-notice-btn-confirm, .pixar-notice-btn-close').prop('disabled', false); closeLoading(); return; } const postResponse = await fetch('/logout', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, credentials: 'include', body: new URLSearchParams({ '_csrf': logoutCsrfToken }) }); if (postResponse.ok) { closeLoading(); cocoMessage.success('您已成功退出登录'); const currentScrollTop = window.scrollY || document.documentElement.scrollTop; $.cookie('load_type', 'logout', { path: '/' }); window.location.reload(); } else { closeLoading(); $('.pixar-notice-btn-confirm, .pixar-notice-btn-close').prop('disabled', false); throw new Error('退出请求被拒绝'); } } catch (err) { closeLoading(); $('.pixar-notice-btn-confirm, .pixar-notice-btn-close').prop('disabled', false); console.error('Logout Error:', err); cocoMessage.error('退出失败,请手动刷新页面'); } } function open_logout_box() { const wrapper = document.getElementById("pixarNoticeWrapper"); wrapper.style.display = "flex"; } function hidePixarCard() { const wrapper = document.getElementById("pixarNoticeWrapper"); wrapper.style.display = "none"; } function confirmPixarAction() { customLogout(); } // 豆瓣页:分类筛选 + AJAX加载(不依赖 finder API) function init_douban_page() { var $page = $('.douban_page'); if ($page.length === 0) { return; } var $list = $('#douban_item'); var $pagination = $('#douban_pagination'); var $paginationBtn = $('#douban_pagination a'); if ($list.length === 0 || $paginationBtn.length === 0) { return; } var state = { page: 1, size: 10, type: '', loading: false }; function escapeHtml(input) { if (input === null || input === undefined) { return ''; } return String(input) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatTime(isoTime) { if (!isoTime) { return '--'; } var date = new Date(isoTime); if (Number.isNaN(date.getTime())) { return '--'; } var year = date.getFullYear(); var month = String(date.getMonth() + 1).padStart(2, '0'); var day = String(date.getDate()).padStart(2, '0'); return year + '-' + month + '-' + day; } function buildRequestUrl(page, type) { var query = new URLSearchParams({ page: String(page), size: String(state.size) }); if (type) { query.append('type', type); } return '/apis/api.douban.moony.la/v1alpha1/doubanmovies?' + query.toString(); } function renderEmpty() { return '
暂无内容
'; } function parseScoreValue(rawScore) { if (rawScore === null || rawScore === undefined || rawScore === '') { return null; } var scoreText = String(rawScore); var matched = scoreText.match(/(\d+(?:\.\d+)?)/); if (!matched || !matched[1]) { return null; } var scoreNumber = parseFloat(matched[1]); if (Number.isNaN(scoreNumber)) { return null; } if (scoreNumber < 0) { scoreNumber = 0; } if (scoreNumber > 5) { scoreNumber = 5; } return scoreNumber; } function renderTypeNav(typeList) { var $navList = $('.douban_cat_nav ul'); if ($navList.length === 0) { return; } var navHtml = ['
  • 全部
  • ']; var seen = {}; if (Array.isArray(typeList)) { typeList.forEach(function (item) { var key = item && item.key !== undefined && item.key !== null ? String(item.key).trim() : ''; if (!key || seen[key]) { return; } seen[key] = true; var safeKey = escapeHtml(key); navHtml.push('
  • ' + safeKey + '
  • '); }); } $navList.html(navHtml.join('')); } function renderScore(rawScore) { var scoreValue = parseScoreValue(rawScore); if (scoreValue === null) { return '暂无评分'; } var formatted = scoreValue.toFixed(1); return formatted + ' / 5.0'; } function renderItem(item) { var spec = item && item.spec ? item.spec : {}; var faves = item && item.faves ? item.faves : {}; var name = escapeHtml(spec.name || '未命名条目'); var link = escapeHtml(spec.link || 'javascript:void(0);'); var poster = escapeHtml(spec.poster || '/themes/theme-pix/assets/img/theme/banner-all.webp'); var rawScore = faves.score || spec.score || ''; var createTime = escapeHtml(formatTime(faves.createTime)); var dataType = escapeHtml(spec.type || '--'); var contentText = escapeHtml(spec.cardSubtitle || '暂无内容简介'); var pid = escapeHtml((item.metadata && item.metadata.name) || ('douban-' + Date.now())); var scoreText = renderScore(rawScore); return '' + '
    ' + '
    ' + '
    ' + ' ' + '
    ' + ' ' + name + '' + '
    ' + '
    ' + ' ' + ' ' + dataType + '' + ' ' + escapeHtml(scoreText) + '' + '
    ' + '
    ' + '

    ' + contentText + '

    ' + ' ' + '
    ' + '
    ' + '
    ' + '
    '; } function updatePagination(hasNext, nextPage) { if (hasNext) { $paginationBtn .attr('data-next-page', String(nextPage)) .attr('data', String(nextPage)) .text((window.Theme && Theme.site_page) ? Theme.site_page : '翻阅更多') .show(); $pagination.removeClass('u-hide').css('display', 'flex'); } else { $paginationBtn.removeAttr('data-next-page').removeAttr('data'); $paginationBtn.hide(); $pagination.hide(); } } function renderList(data, isAppend) { var items = (data && data.items) ? data.items : []; if (!isAppend) { $list.empty(); } if (items.length === 0 && !isAppend) { $list.html(renderEmpty()); updatePagination(false, 1); return; } var html = items.map(renderItem).join(''); $list.append($(html).fadeIn(300)); if (typeof lazyLoadInstance !== 'undefined') { lazyLoadInstance.update(); } if (typeof initializeMomentFold === 'function') { initializeMomentFold(); } initializeDoubanExpand(); updatePagination(!!data.hasNext, (data.page || state.page) + 1); } function initializeDoubanExpand() { var collapsedHeight = 89.6; $(document).off('click.doubanExpand', '.douban_expand_btn').on('click.doubanExpand', '.douban_expand_btn', function (e) { e.preventDefault(); var $btn = $(this); var $content = $btn.prev('.douban_content_main'); if ($content.length === 0) { return; } if ($content.hasClass('is-expanded')) { $content.removeClass('is-expanded'); $content.css('max-height', collapsedHeight + 'px'); $btn.text('展开'); } else { var contentEl = $content[0]; var fullHeight = $content.data('fullHeight') || (contentEl ? contentEl.scrollHeight : 0); $content.addClass('is-expanded'); $content.css('max-height', fullHeight + 'px'); $btn.text('收起'); } }); $('.douban_content_main').each(function () { var $content = $(this); var $btn = $content.next('.douban_expand_btn'); if ($btn.length === 0) { return; } var scrollHeight = this.scrollHeight; var clientHeight = this.clientHeight; if (scrollHeight > clientHeight) { $btn.show(); $content.data('fullHeight', scrollHeight); $content.css('max-height', collapsedHeight + 'px'); } }); } function requestList(page, type, isAppend, hooks) { hooks = hooks || {}; if (state.loading) { return; } state.loading = true; var appendStartIndex = isAppend ? $list.children('.p_item').length : 0; var oldState = { listHtml: $list.html(), paginationVisible: $pagination.is(':visible'), btnVisible: $paginationBtn.is(':visible'), btnText: $paginationBtn.text(), btnNextPage: $paginationBtn.attr('data-next-page') }; var apiUrl = buildRequestUrl(page, type); $.ajax({ type: 'GET', url: apiUrl, dataType: 'json', beforeSend: function () { if (!isAppend) { $list.html('
    '); $pagination.hide(); } else { $paginationBtn.hide(); if ($('#douban_pagination .douban-more-loading').length === 0) { $('#douban_pagination .post-paging').append('
    '); } $pagination.css('display', 'flex'); } }, success: function (data) { state.page = page; renderList(data || {}, !!isAppend); if (typeof hooks.onSuccess === 'function') { hooks.onSuccess(data || {}); } if (isAppend) { var $newItems = $list.children('.p_item').slice(appendStartIndex); if ($newItems.length > 0) { var $scrollBody = (typeof $body !== 'undefined') ? $body : $('html,body'); var targetTop = $newItems.first().offset().top - 58; if (!window.scroll_param || (!window.loadMoreTimeout && window.scroll_param > targetTop)) { $scrollBody.animate({ scrollTop: targetTop }, 500); } else { window.forceStopLoading = true; } } } }, error: function () { cocoMessage.error('数据获取失败'); if (!isAppend) { if ($.trim(oldState.listHtml || '') !== '') { $list.html(oldState.listHtml); } else { $list.html(renderEmpty()); } } if (oldState.paginationVisible) { $pagination.css('display', 'flex'); } else { $pagination.hide(); } if (oldState.btnVisible) { $paginationBtn.show(); } else { $paginationBtn.hide(); } if (oldState.btnText) { $paginationBtn.text(oldState.btnText); } if (oldState.btnNextPage) { $paginationBtn.attr('data-next-page', oldState.btnNextPage); $paginationBtn.attr('data', oldState.btnNextPage); } if (typeof hooks.onError === 'function') { hooks.onError(); } }, complete: function () { state.loading = false; $('#douban_pagination .douban-more-loading').remove(); $('.douban_cat_nav .cat-link').removeClass('disabled'); $paginationBtn.text((window.Theme && Theme.site_page) ? Theme.site_page : '翻阅更多'); } }); } $(document).off('click.doubanCat', '.douban_cat_nav .cat-link').on('click.doubanCat', '.douban_cat_nav .cat-link', function () { var $this = $(this); if ($this.hasClass('disabled')) { return false; } if ($this.hasClass('active')) { if (window._tagSwitchCallback) { window._tagSwitchCallback.resolve(); window._tagSwitchCallback = null; } return false; } $('.douban_cat_nav .cat-link').addClass('disabled'); var $activeBefore = $('.douban_cat_nav .cat-link.active'); var oldType = state.type; var targetType = $this.attr('data-type') || ''; var targetTag = $this.clone().find('span').remove().end().text().trim(); state.page = 1; state.type = targetType; requestList(1, state.type, false, { onSuccess: function () { $this.addClass('active').parent().siblings().children('.cat-link').removeClass('active'); var currentUrl = new URL(window.location.href); if (!targetTag || targetTag === '全部') { currentUrl.searchParams.delete('tag'); } else { currentUrl.searchParams.set('tag', targetTag); } history.pushState({ url: currentUrl.pathname + currentUrl.search }, '', currentUrl.pathname + currentUrl.search); // 分类切换后重置分页记录,避免自动恢复加载旧分类页数 $.removeCookie('page', { path: '/' }); if (window._tagSwitchCallback) { window._tagSwitchCallback.resolve(); window._tagSwitchCallback = null; } }, onError: function () { state.type = oldType; if ($activeBefore.length > 0) { $activeBefore.addClass('active').parent().siblings().children('.cat-link').removeClass('active'); } if (window._tagSwitchCallback) { window._tagSwitchCallback.reject(); window._tagSwitchCallback = null; } } }); return false; }); $(document).off('click.doubanMore', '#douban_pagination a').on('click.doubanMore', '#douban_pagination a', function () { if (state.loading) { return false; } var pageAttr = $(this).attr('data-next-page') || $(this).attr('data'); var nextPage = parseInt(pageAttr, 10); if (!nextPage || Number.isNaN(nextPage)) { return false; } requestList(nextPage, state.type, true, { onSuccess: function () { // 与 app.js 现有逻辑保持一致:记录当前已加载到的页码 $.cookie('page', nextPage, { path: '/' }); } }); return false; }); function applyUrlTagState() { var urlTag = new URLSearchParams(window.location.search).get('tag'); if (urlTag && urlTag.trim() !== '') { var $matchedTag = $('.douban_cat_nav .cat-link').filter(function () { var tagText = $(this).clone().find('span').remove().end().text().trim(); return tagText === urlTag; }).first(); if ($matchedTag.length > 0) { $matchedTag.addClass('active').parent().siblings().children('.cat-link').removeClass('active'); state.type = $matchedTag.attr('data-type') || ''; } else { state.type = $('.douban_cat_nav .cat-link.active').attr('data-type') || ''; } } else { state.type = $('.douban_cat_nav .cat-link.active').attr('data-type') || ''; } } $.ajax({ type: 'GET', url: '/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/types', dataType: 'json', success: function (data) { var typeList = Array.isArray(data) ? data : ((data && Array.isArray(data.items)) ? data.items : []); renderTypeNav(typeList); }, complete: function () { applyUrlTagState(); requestList(1, state.type, false); } }); } // 打开确认模态框 function openModal(title, msg, type) { var modal = $('#momentsDeleteModal'); var card = modal.find('.moments-card-widget'); modal.find('.moments-title-text').text(title); modal.find('.moments-message-text').text(msg); card.removeClass('theme-red theme-yellow theme-green').addClass('theme-' + type); $('.moments-btn-cancel, .moments-btn-confirm').prop('disabled', false); $('body').off('click', '#momentsCancelBtn'); $('body').off('click', '#momentsConfirmBtn'); modal.fadeIn(200); } // = = = = = = = = = = = = = = = = 触发器 = = = = = = = = = = = = = = = = // 集合:PJAX(初始化执行+PJAX执行) function init_anian_pjax() { // modify_moment_video_size(); init_douban_page(); } // 集合:初始化(仅初始化执行一次) function init_anian_inport() { init_anian_pjax(); modify_m_bottom_music_button_link(); } // PJAX监听 document.addEventListener('pjax:complete', function () { init_anian_pjax(); }) // 入口 init_anian_inport();