幻想次元黑名单脚本

用于屏蔽幻想次元文章和评论

// ==UserScript==
// @name         幻想次元黑名单脚本
// @namespace    http://tampermonkey.net/
// @version      2025-04-08
// @description  用于屏蔽幻想次元文章和评论
// @author       Aerry
// @license      MIT
// @match        http*://hxcy.top/*
// @icon         https://hxcy.top/wp-content/themes/wpdx/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==

(function () {
	'use strict'
	// 初始化数据
	const initData = () => {
		let data = localStorage.getItem('blockAuthor')
		if (!data) {
			data = {
				categories: ['默认', '低质量', '广告'],
				authors: []
			}
			localStorage.setItem('blockAuthor', JSON.stringify(data))
		} else {
			try {
				const parsedData = JSON.parse(data)
				// 检查是否是对象且包含authors数组
				if (typeof parsedData === 'object' && parsedData !== null &&
					Array.isArray(parsedData.authors)) {
					data = parsedData
				} else {
					// 如果数据格式不正确,重置为默认值
					data = {
						categories: ['默认', '低质量', '广告'],
						authors: []
					}
					localStorage.setItem('blockAuthor', JSON.stringify(data))
				}
			} catch (e) {
				// 如果解析失败,也重置为默认值
				data = {
					categories: ['默认', '低质量', '广告'],
					authors: []
				}
				localStorage.setItem('blockAuthor', JSON.stringify(data))
			}
		}
		return typeof data === 'string' ? JSON.parse(data) : data
	}
	// 保存数据
	const saveData = (data) => {
		localStorage.setItem('blockAuthor', JSON.stringify(data))
	}
	// 创建控制按钮
	const createControlButton = () => {
		const btn = document.createElement('div')
		btn.id = 'togglePanelBtn'
		btn.innerHTML = '打开<br>面板'
		btn.style.position = 'fixed'
		btn.style.right = '20px'
		btn.style.bottom = '130px'
		btn.style.fontSize = '12px'
		btn.style.color = '#fff'
		btn.style.backgroundColor = '#323841'
		btn.style.width = '40px'
		btn.style.padding = '8px 0'
		btn.style.textAlign = 'center'
		btn.style.borderRadius = '2px'
		btn.style.zIndex = '1000'
		btn.style.cursor = 'pointer'
		document.body.appendChild(btn)
		return btn
	}
	// 创建面板
	const createPanel = () => {
		const panel = document.createElement('div')
		panel.id = 'blockAuthorPanel'
		panel.style.display = 'none' // 默认隐藏
		GM_addStyle(`
			@media screen and (max-width: 768px) {
				#togglePanelBtn {
					bottom: 85px !important;
				}
			}

			#blockAuthorPanel {
				position: fixed;
				top: 50px;
				right: 20px;
				width: 350px;
				background: #fff;
				border: 1px solid #ddd;
				box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
				z-index: 9999;
				padding: 15px;
				font-family: Arial, sans-serif;
				max-height: 80vh;
				overflow-y: auto;
			}

			#blockAuthorPanel h2 {
				margin-top: 0;
				padding-bottom: 10px;
				border-bottom: 1px solid #eee;
				font-size: 18px;
				display: flex;
				justify-content: space-between;
				align-items: center;
			}

			.close-panel {
				cursor: pointer;
				font-size: 20px;
				color: #999;
			}

			.close-panel:hover {
				color: #666;
			}

			.author-list {
				margin: 10px 0;
			}

			.author-item {
				display: flex;
				align-items: center;
				padding: 8px 0;
				border-bottom: 1px solid #f5f5f5;
			}

			.author-item:hover {
				background: #f9f9f9;
			}

			.author-name {
				flex-grow: 1;
				margin-left: 10px;
			}

			.category-tag {
				display: inline-block;
				padding: 2px 6px;
				background: #e1f5fe;
				border-radius: 3px;
				font-size: 12px;
				margin-left: 10px;
				color: #0288d1;
			}

			.panel-actions {
				display: flex;
				justify-content: space-between;
				margin-top: 15px;
				padding-top: 10px;
				border-top: 1px solid #eee;
			}

			.panel-btn {
				padding: 6px 12px;
				background: #f0f0f0;
				border: 1px solid #ddd;
				border-radius: 3px;
				cursor: pointer;
			}

			.panel-btn:hover {
				background: #e0e0e0;
			}

			.panel-btn.primary {
				background: #4CAF50;
				color: white;
				border-color: #4CAF50;
			}

			.panel-btn.danger {
				background: #f44336;
				color: white;
				border-color: #f44336;
			}

			.category-selector {
				margin-bottom: 15px;
			}

			.category-filter {
				margin-bottom: 10px;
			}

			.add-author-form {
				display: flex;
				margin-bottom: 15px;
			}

			.add-author-input {
				flex-grow: 1;
				padding: 6px;
				border: 1px solid #ddd;
				border-radius: 3px;
			}

			.add-author-btn {
				margin-left: 10px;
			}

			.import-export-area {
				width: 100%;
				height: 100px;
				margin-bottom: 10px;
				border: 1px solid #ddd;
				padding: 5px;
			}`)
		panel.innerHTML = `
		<h2>
			<span>作者屏蔽面板</span>
			<span class="close-panel" title="关闭面板">×</span>
		</h2>
		<div class="category-filter">
			<select id="categoryFilter" class="panel-btn">
				<option value="all">所有分类</option>
			</select>
		</div>
		<div class="add-author-form">
			<input type="text" id="newAuthorName" class="add-author-input" placeholder="作者名称">
			<select id="newAuthorCategory" class="panel-btn">
			</select>
			<button id="addAuthorBtn" class="panel-btn add-author-btn">添加</button>
		</div>
		<div class="author-list" id="authorList"></div>
		<div class="panel-actions">
			<div>
				<button id="selectAllBtn" class="panel-btn">全选</button>
				<button id="deselectAllBtn" class="panel-btn">取消</button>
			</div>
			<div>
				<button id="deleteSelectedBtn" class="panel-btn danger">删除</button>
			</div>
		</div>
		<div class="panel-actions">
			<div>
				<button id="exportBtn" class="panel-btn">导出数据</button>
				<button id="importBtn" class="panel-btn">导入数据</button>
			</div>
		</div>
		<div id="importExportArea" style="display: none">
			<textarea class="import-export-area" id="dataTextarea"></textarea>
			<div class="panel-actions">
				<button id="confirmImportBtn" class="panel-btn primary">确认导入</button>
				<button id="cancelImportBtn" class="panel-btn">取消</button>
			</div>
		</div>`
		document.body.appendChild(panel)
		return panel
	}
	// 渲染作者列表
	const renderAuthorList = (data, filter = 'all') => {
		const authorList = document.getElementById('authorList')
		authorList.innerHTML = ''
		const authors = filter === 'all'
			? data.authors
			: data.authors.filter(author => author.category === filter)
		if (authors.length === 0) {
			authorList.innerHTML = '<p>没有作者数据</p>'
			return
		}
		authors.forEach(author => {
			const authorItem = document.createElement('div')
			authorItem.className = 'author-item'
			authorItem.innerHTML = `
				<input type="checkbox" class="author-checkbox" data-id="${author.id}" ${author.selected ? 'checked' : ''}>
				<span class="author-name">${author.name}</span>
				<span class="category-tag">${author.category}</span>
			`
			authorList.appendChild(authorItem)
		})
		// 添加点击事件
		document.querySelectorAll('.author-checkbox').forEach(checkbox => {
			checkbox.addEventListener('change', function () {
				const id = parseInt(this.getAttribute('data-id'))
				data.authors = data.authors.map(author => {
					if (author.id === id) {
						author.selected = this.checked
					}
					return author
				})
				saveData(data)
			})
		})
	}
	// 渲染分类选择器
	const renderCategorySelectors = (data) => {
		const categoryFilter = document.getElementById('categoryFilter')
		const newAuthorCategory = document.getElementById('newAuthorCategory')
		// 清空现有选项
		categoryFilter.innerHTML = '<option value="all">所有分类</option>'
		newAuthorCategory.innerHTML = ''
		// 添加分类选项
		data.categories.forEach(category => {
			categoryFilter.innerHTML += `<option value="${category}">${category}</option>`
			newAuthorCategory.innerHTML += `<option value="${category}">${category}</option>`
		})
	}
	// 主函数
	const main = () => {
		let data = initData()
		addButton(data)
		const toggleBtn = createControlButton()
		const panel = createPanel()
		renderCategorySelectors(data)
		renderAuthorList(data)
		// 切换面板显示/隐藏
		const togglePanel = () => {
			if (panel.style.display === 'none') {
				panel.style.display = 'block'
				toggleBtn.innerHTML = '关闭<br>面板'
				// 刷新数据
				data = initData()
				renderCategorySelectors(data)
				renderAuthorList(data, document.getElementById('categoryFilter').value)
			} else {
				panel.style.display = 'none'
				toggleBtn.innerHTML = '打开<br>面板'
			}
		}
		// 控制按钮点击事件
		toggleBtn.addEventListener('click', togglePanel)
		// 关闭按钮点击事件
		panel.querySelector('.close-panel').addEventListener('click', function () {
			panel.style.display = 'none'
			toggleBtn.innerHTML = '打开<br>面板'
		})
		// 分类筛选事件
		document.getElementById('categoryFilter').addEventListener('change', function () {
			renderAuthorList(data, this.value)
		})
		// 添加作者事件
		document.getElementById('addAuthorBtn').addEventListener('click', function () {
			const nameInput = document.getElementById('newAuthorName')
			const categorySelect = document.getElementById('newAuthorCategory')
			const name = nameInput.value.trim()
			if (name) {
				// 检查是否已存在
				const exists = data.authors.some(author => author.name === name)
				if (exists) {
					alert('该作者已存在!')
					return
				}
				// 获取最大ID
				const maxId = data.authors.reduce((max, author) => Math.max(max, author.id), 0)
				// 添加新作者
				data.authors.push({
					id: maxId + 1,
					name: name,
					category: categorySelect.value,
					selected: false
				})
				saveData(data)
				renderAuthorList(data, document.getElementById('categoryFilter').value)
				nameInput.value = ''
			}
		})
		// 全选事件
		document.getElementById('selectAllBtn').addEventListener('click', function () {
			const filter = document.getElementById('categoryFilter').value
			const authorsToUpdate = filter === 'all'
				? data.authors
				: data.authors.filter(author => author.category === filter)

			data.authors = data.authors.map(author => {
				if (authorsToUpdate.some(a => a.id === author.id)) {
					author.selected = true
				}
				return author
			})
			saveData(data)
			renderAuthorList(data, filter)
		})
		// 取消全选事件
		document.getElementById('deselectAllBtn').addEventListener('click', function () {
			const filter = document.getElementById('categoryFilter').value
			const authorsToUpdate = filter === 'all'
				? data.authors
				: data.authors.filter(author => author.category === filter)
			data.authors = data.authors.map(author => {
				if (authorsToUpdate.some(a => a.id === author.id)) {
					author.selected = false
				}
				return author
			})
			saveData(data)
			renderAuthorList(data, filter)
		})
		// 删除选中事件
		document.getElementById('deleteSelectedBtn').addEventListener('click', function () {
			if (confirm('确定要删除选中的作者吗?')) {
				data.authors = data.authors.filter(author => !author.selected)
				saveData(data)
				renderAuthorList(data, document.getElementById('categoryFilter').value)
			}
		})
		// 导出数据事件
		document.getElementById('exportBtn').addEventListener('click', function () {
			const importExportArea = document.getElementById('importExportArea')
			const dataTextarea = document.getElementById('dataTextarea')
			dataTextarea.value = JSON.stringify(data, null, 2)
			importExportArea.style.display = 'block'
		})
		// 导入数据事件
		document.getElementById('importBtn').addEventListener('click', function () {
			const importExportArea = document.getElementById('importExportArea')
			const dataTextarea = document.getElementById('dataTextarea')
			dataTextarea.value = ''
			importExportArea.style.display = 'block'
		})
		// 确认导入事件
		document.getElementById('confirmImportBtn').addEventListener('click', function () {
			const dataTextarea = document.getElementById('dataTextarea')
			try {
				const newData = JSON.parse(dataTextarea.value)
				if (newData.categories && newData.authors) {
					data = newData
					saveData(data)
					renderCategorySelectors(data)
					renderAuthorList(data, document.getElementById('categoryFilter').value)
					document.getElementById('importExportArea').style.display = 'none'
				} else {
					alert('数据格式不正确!')
				}
			} catch (e) {
				alert('解析JSON失败: ' + e.message)
			}
		})
		// 取消导入/导出事件
		document.getElementById('cancelImportBtn').addEventListener('click', function () {
			document.getElementById('importExportArea').style.display = 'none'
		})
		// 添加拖拽功能
		let isDragging = false
		let offsetX, offsetY
		panel.querySelector('h2').addEventListener('mousedown', function (e) {
			if (e.target.className !== 'close-panel') {
				isDragging = true
				offsetX = e.clientX - panel.getBoundingClientRect().left
				offsetY = e.clientY - panel.getBoundingClientRect().top
				panel.style.cursor = 'grabbing'
			}
		})
		document.addEventListener('mousemove', function (e) {
			if (isDragging) {
				panel.style.left = (e.clientX - offsetX) + 'px'
				panel.style.top = (e.clientY - offsetY) + 'px'
			}
		})
		document.addEventListener('mouseup', function () {
			isDragging = false
			panel.style.cursor = ''
		})
	}
	// 加载相关按钮
	const addButton = (data) => {
		// 获取最大ID
		const maxId = data.authors.reduce((max, author) => Math.max(max, author.id), 0)
		// 判断是否文章内页
		if (document.querySelector('body.single')) {
			// 加入黑名单
			const blockAuthor = () => {
				const author = document.querySelector('.post-meta span:nth-child(1) a')
				if (!data.authors.some(i => i.name == author.innerText)) {
					data.authors.push({
						id: maxId + 1,
						name: author.innerText,
						category: '默认',
						selected: false
					})
				}
				const [blockAuthor, whiteAuthor] = document.querySelectorAll('#blockAuthor, #whiteAuthor')
				blockAuthor.style.display = 'none'
				whiteAuthor.style.display = ''
				saveData(data)
				renderAuthorList(data, document.getElementById('categoryFilter').value)
				alert(`已屏蔽作者 ${author.innerText} 的文章, 刷新页面后生效`)
			}
			// 移出黑名单
			const whiteAuthor = () => {
				const author = document.querySelector('.post-meta span:nth-child(1) a')
				data.authors = data.authors.filter(i => i.name != author.innerText)
				saveData(data)
				renderAuthorList(data, document.getElementById('categoryFilter').value)
				const [blockAuthor, whiteAuthor] = document.querySelectorAll('#blockAuthor, #whiteAuthor')
				blockAuthor.style.display = ''
				whiteAuthor.style.display = 'none'
				alert(`已解除对于作者 ${author.innerText} 文章的屏蔽`)
			}
			const name = document.querySelector('#post-header .post-meta span:nth-child(1) a')
			const newListItem = document.createElement('li')
			const newButton1 = document.createElement('span')
			newButton1.onclick = blockAuthor
			newButton1.textContent = '屏蔽他的文章'
			newButton1.id = 'blockAuthor'
			newButton1.classList.add('badge')
			newButton1.style.display = data.authors.some(i => i.name == name.innerText) ? 'none' : ''
			const newButton2 = document.createElement('span')
			newButton2.id = 'whiteAuthor'
			newButton2.classList.add('badge')
			newButton2.onclick = whiteAuthor
			newButton2.textContent = '取消屏蔽他的文章'
			newButton2.style.display = data.authors.some(i => i.name == name.innerText) ? '' : 'none'
			newListItem.appendChild(newButton1)
			newListItem.appendChild(newButton2)
			const socialList = document.querySelector('.author-social')
			socialList.appendChild(newListItem)
			// 美化按钮
			const styleElement = document.createElement('style')
			styleElement.innerHTML = `
			.author-social .badge {
				border-radius: 0;
				line-height: 24px;
				background-color: #fe9a4b;
			}
			.author-social .badge:hover {
				background-color: #FF7000;
			}
			.badge.block {
				font-size: 11px;
			}`
			document.head.appendChild(styleElement)
		}
		// 判断是否分类页或标签
		if (document.querySelector('body.category') || document.querySelector('body.tag')) {
			// 文章列表
			const authors = document.querySelectorAll('.post-meta span:nth-child(1) a')
			const posts = document.querySelectorAll('.posts-ul li')
			authors.forEach((author, index) => {
				if (data.authors.some(i => i.name == author.innerText)) posts[index].remove()
			})
		}
		// 判断是否用户中心
		if (document.querySelector('body.author')) {
			if (data.authors.some(i => i.name == document.querySelector('.user-avatar img').alt)) document.querySelector('.widget-content').remove()
		}
		// 判断是否含有网站公告
		if (document.querySelectorAll('.widget-box.widget.category-posts')) {
			// 网站公告
			const categoryPosts = document.querySelectorAll('.widget-box.widget.category-posts li')
			categoryPosts.forEach((item, i) => {
				if (item.childNodes[1].childNodes[1]?.dataset.author && data.authors.some(i => i.name == item.childNodes[1].childNodes[1]?.dataset.author)) item.remove()
			})
		}
		//判断是否首页
		if (document.querySelector('body.home')) {
			// 最新文章 || 点击排行 || 最新资讯
			const recentPosts = document.querySelectorAll('.home-recent li')
			recentPosts.forEach((Item, i) => {
				if (Item.childNodes[2]?.dataset?.author && data.authors.some(i => i.name == Item.childNodes[2].dataset.author)) Item.remove()
			})
			// 图包分享 || Cosplay写真
			const picBoxPosts = document.querySelectorAll('.pic-box li')
			picBoxPosts.forEach((Item, i) => {
				if (Item.childNodes[1]?.dataset?.author && data.authors.some(i => i.name == Item.childNodes[1].dataset.author)) Item.remove()
			})
			// ACG资源聚合
			const threePosts = document.querySelectorAll('.three-row li')
			threePosts.forEach((Item, i) => {
				if (Item.childNodes[1].childNodes[1]?.dataset?.author && data.authors.some(i => i.name == Item.childNodes[1].childNodes[1].dataset.author)) Item.remove()
			})
			// 音乐分享 || 游戏分享 || 娱乐生活 || 工具&技巧
			const columnPosts = document.querySelectorAll('.column2 li')
			columnPosts.forEach((Item, i) => {
				if (Item.childNodes[1].childNodes[1] && Item.childNodes[1].childNodes[1]?.dataset?.author && data.authors.some(i => i.name == Item.childNodes[1].childNodes[1].dataset.author)) Item.remove()
				if (Item.childNodes[3] && Item.childNodes[3]?.dataset.author && data.authors.some(i => i.name == Item.childNodes[3].dataset.author)) Item.remove()
			})
		}
		//判断当前页面是否含有评论模块
		if (document.querySelector('.comment-box')) {
			const commentList = document.querySelectorAll('.commentlist .comment')
			const commentIdList = document.querySelectorAll('.conment_id')
			// 加入黑名单
			const blockCommentAuthor = (vl) => {
				if (!data.authors.some(i => i.name == vl.target.name)) {
					data.authors.push({
						id: maxId + 1,
						name: vl.target.name,
						category: '默认',
						selected: false
					})
				}
				const whiteClass = vl.target.id.replace('block', 'white')
				const whiteAuthor = document.querySelector(`#${whiteClass}`)
				vl.target.style.display = 'none'
				whiteAuthor.style.display = ''
				saveData(data)
				renderAuthorList(data, document.getElementById('categoryFilter').value)
				alert(`已屏蔽 ${vl.target.name} 的评论, 刷新页面后生效`)
			}
			// 移出黑名单
			const whiteCommentAuthor = (vl) => {
				const name = document.querySelector('.comment-author .conment_id a:nth-child(1)').innerText
				data.authors = data.authors.filter(i => i.name != name)
				saveData(data)
				renderAuthorList(data, document.getElementById('categoryFilter').value)
				const blockClass = vl.target.id.replace('white', 'block')
				const blockAuthor = document.querySelector(`#${blockClass}`)
				blockAuthor.style.display = ''
				vl.target.style.display = 'none'
				alert(`已解除对于 ${vl.target.name} 评论的屏蔽`)
			}
			// 如果有评论的话
			if (commentIdList.length > 0) {
				commentIdList.forEach((author, index) => {
					const commentButton1 = document.createElement('span')
					commentButton1.onclick = blockCommentAuthor
					commentButton1.textContent = '屏蔽他的评论'
					commentButton1.id = `blockCommentAuthor-${index}`
					commentButton1.classList = 'badge block'
					commentButton1.style.display = data.authors.some(i => i.name == author.childNodes[0].innerText) ? 'none' : ''
					commentButton1.name = author.childNodes[0].innerText
					const commentButton2 = document.createElement('span')
					commentButton2.id = `whiteCommentAuthor-${index}`
					commentButton2.classList = 'badge block'
					commentButton2.onclick = whiteCommentAuthor
					commentButton2.textContent = '取消屏蔽他的评论'
					commentButton2.style.display = data.authors.some(i => i.name == author.childNodes[0].innerText) ? '' : 'none'
					commentButton2.name = author.childNodes[0].innerText
					author.appendChild(commentButton1)
					author.appendChild(commentButton2)
					commentList[index].title = author.childNodes[0].innerText
					// 隐藏评论
					if (data.authors.some(i => i.name == author.childNodes[0].innerText)) commentList[index].remove()
				})
			}
		}
	}
	// 启动脚本
	main()
})()