您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在小组话题/条目讨论/日志/目录页右侧添加推荐面板;在首页右侧显示「Bangumi社区月刊」卡片
// ==UserScript== // @name Bangumi 社区月刊组件[测试] // @version 1.2.3 // @description 在小组话题/条目讨论/日志/目录页右侧添加推荐面板;在首页右侧显示「Bangumi社区月刊」卡片 // @author zin // @match https://bgm.tv/* // @match https://bangumi.tv/* // @match https://chii.in/* // @connect raw.githubusercontent.com // @license MIT // @namespace https://greasyfork.org/users/1386262 // ==/UserScript== (async function () { 'use strict'; const JSON_URL = 'https://raw.githubusercontent.com/zintop/bangumi_monthly/main/bangumi_monthly_1.json'; let COVER_IMG = '', COVER_THUMB = '', ISSUE_TITLE = '', ISSUE_VOL = '', ISSUE_URL_CHAHUA = '', ISSUE_URL_YUEKAN = '', RECOMMEND_LOG_ID = ''; // 获取设置项的值 const getSetting = (name, defaultValue) => { const value = localStorage.getItem(`bangumi_monthly_${name}`); return value !== null ? value === 'true' : defaultValue; }; // 保存设置项的值 const setSetting = (name, value) => { localStorage.setItem(`bangumi_monthly_${name}`, value); }; // 初始化设置 let homeCardEnabled = getSetting('homeCardEnabled', true); let recommendPanelEnabled = getSetting('recommendPanelEnabled', true); try { const res = await fetch(JSON_URL); const data = await res.json(); ({ coverImg: COVER_IMG = '', coverThumb: COVER_THUMB = '', issueTitle: ISSUE_TITLE = '', issueVol: ISSUE_VOL = '', issueUrlChahua: ISSUE_URL_CHAHUA = '', issueUrlYuekan: ISSUE_URL_YUEKAN = '', recommendLogId: RECOMMEND_LOG_ID = '' } = data); } catch (err) { console.error('无法加载 Bangumi 月刊 JSON:', err); return; } const injectStyles = () => { const styles = ` #monthly-modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.15);z-index:9999;justify-content:center;align-items:center;backdrop-filter:blur(8px);animation:fadeIn .3s} #monthly-modal.active{display:flex} #monthly-modal .modal-content{position:relative;border-radius:20px;padding:8px;background:rgba(255,255,255,0.02);backdrop-filter:blur(25px);max-width:90%;max-height:90%;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 12px 40px rgba(0,0,0,.35);animation:slideUp .35s} #monthly-modal .inner-frame{border-radius:16px;border:6px solid rgba(255,255,255,0.12);padding:4px;background:rgba(255,255,255,0.02);display:flex;justify-content:center;align-items:center} #monthly-modal .inner-frame img{border-radius:12px;max-width:100%;max-height:80vh;object-fit:contain} #monthly-modal .footer{padding:10px 12px;text-align:center;font-size:1em;color:#e0e0e0;font-weight:700} #monthly-modal .footer a{color:#a0d8ff;text-decoration:none;margin:0 6px;transition:color .2s,text-shadow .2s} #monthly-modal .footer a:hover{color:#66bfff;text-shadow:0 0 4px rgba(0,0,0,.5)} @keyframes fadeIn{from{opacity:0}to{opacity:1}} @keyframes slideUp{from{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}} #bangumi-recommend-panel { margin-top: 15px; } #bangumi-recommend-panel .SidePanel { padding: 10px; } #bangumi-recommend-panel h2 { margin-bottom: 10px; font-size: 13px; font-weight: 400; line-height: 1.5; text-align: left; } #bangumi-recommend-panel h2 a { text-decoration: none; } #bangumi-recommend-panel h2 a:hover { color: #f09199; } #bangumi-recommend-panel textarea { width: 100%; min-height: 80px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical; box-sizing: border-box; margin-bottom: 8px; } #bangumi-recommend-panel button { width: 100%; padding: 8px 0; background-color: #f09199; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; } #bangumi-recommend-panel button:hover { background-color: #e07179; } `; document.head.appendChild(Object.assign(document.createElement("style"), { textContent: styles })); }; const injectCard = () => { if (window.location.pathname !== "/" || !homeCardEnabled) return; setTimeout(() => { const info = { id: "bangumi_monthly", title: "Bangumi 社区月刊", desc: `${ISSUE_TITLE} ${ISSUE_VOL}`, banner_url: COVER_THUMB || COVER_IMG, url: "javascript:void(0)" }; const html = `<div class="featuredItems"><div id="${info.id}" class="appItem" style="background-image:linear-gradient(to right,rgba(0,0,0,.6),transparent 60%),url('${info.banner_url}');background-size:cover"><a href="${info.url}"><p class="title">${info.title}</p><p>${info.desc}</p></a></div></div>`; $("#columnHomeB .featuredItems").length ? $("#columnHomeB .featuredItems").first().after(html) : $("#columnHomeB").prepend(html); const modalHTML = ` <div id="monthly-modal"> <div class="modal-content"> <div class="inner-frame"><img src="${COVER_IMG}" alt="社区月刊封面" /></div> <div class="footer"> 月刊上新 点击直达: <a href="${ISSUE_URL_CHAHUA}" target="_blank">靠谱人生茶话会</a> | <a href="${ISSUE_URL_YUEKAN}" target="_blank">Bangumi社区月刊</a> </div> </div> </div>`; document.body.insertAdjacentHTML("beforeend", modalHTML); const banner = document.getElementById("bangumi_monthly"); const modal = document.getElementById("monthly-modal"); banner.addEventListener("click", () => modal.classList.add("active")); modal.addEventListener("click", (e) => { if (e.target === modal) modal.classList.remove("active"); }); }, 0); }; const path = location.pathname, BASE_URL = location.hostname; const shouldShowRecommendPanel = () => RECOMMEND_LOG_ID && recommendPanelEnabled && (/^\/group\/topic\/\d+$/.test(path) || /^\/blog\/\d+$/.test(path) || /^\/subject\/topic\/\d+$/.test(path) || /^\/index\/\d+$/.test(path)); const getPostIdFromUrl = () => path.split('/').pop(); const getAuthorInfo = () => { if (document.querySelector('.postTopic')) { const userLink = document.querySelector('.postTopic .inner strong a'); return { id: document.querySelector('.postTopic').getAttribute('data-item-user') || userLink?.href?.split('/').pop() || '用户', name: userLink?.textContent.trim() || '用户' }; } if (path.startsWith('/blog/')) { const authorLink = document.querySelector('#pageHeader h1 a[href^="/user/"], .blog_info a[href^="/user/"]'); return { id: authorLink?.href.split('/').pop() || '用户', name: authorLink?.textContent.trim() || '用户' }; } if (path.startsWith('/index/')) { const authorLink = document.querySelector('a.l.bve-processed[href^="/user/"]'); return { id: authorLink?.href.split('/').pop() || '用户', name: authorLink?.textContent.trim() || '用户' }; } return { id: '用户', name: '用户' }; }; const getSubjectInfo = () => { const subjectLink = document.querySelector('#subject_inner_info > a.avatar[href^="/subject/"]'); return { id: subjectLink?.href.split('/')[4] || null, name: subjectLink?.title || subjectLink?.textContent.trim() || null }; }; const getBlogFormData = async (blogId) => { const response = await fetch(`https://${BASE_URL}/blog/${blogId}`); if (!response.ok) throw new Error('无法获取日志'); const doc = new DOMParser().parseFromString(await response.text(), 'text/html'); const formhash = doc.querySelector('input[name="formhash"]')?.value; const lastview = doc.querySelector('input[name="lastview"]')?.value; if (!formhash || !lastview) throw new Error('找不到日志信息'); return { formhash, lastview }; }; const submitComment = async (blogId, formData, content) => { const response = await fetch(`https://${BASE_URL}/blog/entry/${blogId}/new_reply`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ formhash: formData.formhash, lastview: formData.lastview, content, submit: '加上去' }), }); return response.ok || response.status === 302; }; const handleRecommend = async (recommendReason = '') => { try { if (!RECOMMEND_LOG_ID) return alert('推荐日志ID未配置'); const isBlog = path.startsWith('/blog/'), isSubjectTopic = path.startsWith('/subject/topic/'), isIndex = path.startsWith('/index/'), postId = getPostIdFromUrl(), authorInfo = getAuthorInfo(), formData = await getBlogFormData(RECOMMEND_LOG_ID); let postUrl = location.href, postTitle, bbcode; if (isBlog) { postTitle = (document.querySelector('#pageHeader h1')?.innerHTML.split('<br>')[1] || document.title).trim(); bbcode = `[url=https://${BASE_URL}/blog/${postId}]${postTitle}[/url]\n[日志] ${authorInfo.name}([url=https://${BASE_URL}/user/${authorInfo.id}]${authorInfo.id}[/url])`; } else if (isSubjectTopic) { postTitle = document.querySelector('#header h1')?.textContent.trim() || document.title.replace(' - Bangumi', ''); const subjectInfo = getSubjectInfo(); bbcode = `[url=https://${BASE_URL}/subject/topic/${postId}]${postTitle}[/url]\n${subjectInfo.id ? `[[url=https://${BASE_URL}/subject/${subjectInfo.id}]${subjectInfo.name}[/url]] ` : ''}${authorInfo.name}([url=https://${BASE_URL}/user/${authorInfo.id}]${authorInfo.id}[/url])`; } else if (isIndex) { postTitle = document.querySelector('#pageHeader h1')?.textContent.trim() || document.title.replace(' - Bangumi', ''); bbcode = `[url=https://${BASE_URL}/index/${postId}]${postTitle}[/url]\n[目录] ${authorInfo.name}([url=https://${BASE_URL}/user/${authorInfo.id}]${authorInfo.id}[/url])`; } else { postTitle = document.title.replace(' - Bangumi', ''); const groupName = document.querySelector('#pageHeader h1 a')?.textContent.trim() || '未知小组'; bbcode = `[url=https://${BASE_URL}/group/topic/${postId}]${postTitle}[/url]\n[${groupName}] ${authorInfo.name}([url=https://${BASE_URL}/user/${authorInfo.id}]${authorInfo.id}[/url])`; } if (recommendReason.trim()) bbcode += `\n\n推荐理由:${recommendReason.trim()}`; if (!await submitComment(RECOMMEND_LOG_ID, formData, bbcode)) throw new Error('提交失败'); alert('推荐成功!'); document.getElementById('bangumi-recommend-reason-textarea').value = ''; } catch (err) { alert('推荐失败: ' + (err.message || '未知错误')); } }; const updateTextColor = () => { const theme = document.documentElement.getAttribute('data-theme'); const textElement = document.querySelector('#bangumi-recommend-panel h2'); if (textElement) { textElement.style.color = theme === 'dark' ? '#fff' : '#444'; } }; const addRecommendPanel = () => { const panelHTML = ` <div id="bangumi-recommend-panel"> <div class="SidePanel png_bg clearit"> <h2>推荐到 <a href="https://${BASE_URL}/blog/${RECOMMEND_LOG_ID}" target="_blank">社区月刊协力楼</a></h2> <textarea id="bangumi-recommend-reason-textarea" placeholder="推荐原因(可选,写了更好)"></textarea> <button id="bangumi-recommend-submit-button">确认推荐</button> </div> </div> `; // v1.1.1:适配目录页 if (path.startsWith('/index/')) { const columnElement = document.getElementById('columnSubjectBrowserB'); if (columnElement) { const lastMenuInner = columnElement.querySelector('.menu_inner:last-child'); if (lastMenuInner) { lastMenuInner.insertAdjacentHTML('afterend', panelHTML); } else { columnElement.insertAdjacentHTML('beforeend', panelHTML); } } else { console.warn('找不到目录页侧栏,推荐面板未添加'); return; } } else { const ANCHOR_1 = '#columnB'; const ANCHOR_2 = '#columnInSubjectB'; const ANCHOR_3 = 'div.columns'; if ($(ANCHOR_1).length) { $(ANCHOR_1).append(panelHTML); } else if ($(ANCHOR_2).length) { $(ANCHOR_2).append(panelHTML); } else if ($(ANCHOR_3).length) { $(ANCHOR_3).append(panelHTML); } else { console.warn('找不到合适的插入位置,推荐面板未添加'); return; } } updateTextColor(); const observer = new MutationObserver(updateTextColor); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); document.getElementById('bangumi-recommend-submit-button').addEventListener('click', () => { handleRecommend(document.getElementById('bangumi-recommend-reason-textarea').value); }); }; // v1.1.2:添加个性化面板设置 const addCustomPanelTab = () => { if (typeof chiiLib === 'undefined' || typeof chiiLib.ukagaka === 'undefined' || typeof chiiLib.ukagaka.addPanelTab !== 'function') { return; } chiiLib.ukagaka.addPanelTab({ tab: 'bangumi_monthly', label: '社区月刊', type: 'options', config: [ { title: '首页卡片', name: 'homeCardEnabled', type: 'radio', defaultValue: 'true', getCurrentValue: function() { return homeCardEnabled.toString(); }, onChange: function(value) { const enabled = value === 'true'; setSetting('homeCardEnabled', enabled); homeCardEnabled = enabled; // 立即生效 const card = document.getElementById('bangumi_monthly'); if (card) { card.style.display = enabled ? 'block' : 'none'; } else if (enabled && window.location.pathname === '/') { // 如果卡片不存在但需要显示,重新注入 injectCard(); } }, options: [ { value: 'true', label: '开启' }, { value: 'false', label: '关闭' } ] }, { title: '推荐面板', name: 'recommendPanelEnabled', type: 'radio', defaultValue: 'true', getCurrentValue: function() { return recommendPanelEnabled.toString(); }, onChange: function(value) { const enabled = value === 'true'; setSetting('recommendPanelEnabled', enabled); recommendPanelEnabled = enabled; // 立即生效 const panel = document.getElementById('bangumi-recommend-panel'); if (panel) { panel.style.display = enabled ? 'block' : 'none'; } else if (enabled && shouldShowRecommendPanel()) { // 如果面板不存在但需要显示,重新注入 addRecommendPanel(); } }, options: [ { value: 'true', label: '开启' }, { value: 'false', label: '关闭' } ] } ] }); }; injectStyles(); injectCard(); if (shouldShowRecommendPanel()) addRecommendPanel(); addCustomPanelTab(); })();