某平台增强排序脚本

news/2025/10/8 18:15:31/文章来源:https://www.cnblogs.com/zqingyang/p/19129941

通过左下角悬浮按钮,在弹窗中展示某乎内容的赞同数降序排序结果

点击查看代码
// ==UserScript==
// @name         知乎排序增强
// @namespace    https://github.com/
// @version      1.0
// @description  通过左下角悬浮按钮,在弹窗中展示知乎内容的赞同数降序排序结果。
// @author       User
// @match        https://www.zhihu.com/
// @match        https://www.zhihu.com/search*
// @match        https://www.zhihu.com/question/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==
// 本脚本仅供个人学习和技术交流使用,请勿用于商业目的。所有数据的版权归原作者和知乎所有。因使用本脚本产生的一切后果由使用者自行承担。
(function () {'use strict'; // 启用 JavaScript 的严格模式,这是一种更安全、更规范的编码方式。// 定义一个日志前缀,方便在浏览器的开发者控制台(F12)中过滤和识别本脚本的输出信息。const LOG_PREFIX = "知乎排序增强 v0.9.0:";console.log(`${LOG_PREFIX} 脚本已启动。`);/*** @grant GM_addStyle* 使用油猴提供的 GM_addStyle 函数向页面注入CSS样式。* 这样做的好处是样式代码和逻辑代码分离,并且能确保样式被正确应用。* 这里定义了排序结果弹窗的所有外观,包括遮罩层、弹窗主体、标题、列表项、按钮等。*/GM_addStyle(`/* 半透明的黑色背景遮罩层 */.sorter-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 9998; display: flex; align-items: center; justify-content: center; }/* 弹窗主体内容框 */.sorter-modal-content { background-color: #fff; color: #121212; border-radius: 8px; width: 80%; max-width: 750px; height: 80%; max-height: 80vh; display: flex; flex-direction: column; box-shadow: 0 5px 15px rgba(0,0,0,0.3); }/* 弹窗头部 */.sorter-modal-header { padding: 12px 16px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }.sorter-modal-title { font-size: 16px; font-weight: 600; }/* 关闭按钮 */.sorter-modal-close { font-size: 24px; font-weight: bold; cursor: pointer; border: none; background: none; padding: 0 8px; }/* 可滚动的结果列表区域 */.sorter-modal-body { overflow-y: auto; padding: 8px 16px; }/* 每一个排序结果条目 */.sorted-item { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #f0f0f0; }/* 赞同数样式 */.sorted-item-votes { font-size: 14px; font-weight: bold; color: #1772F6; flex-shrink: 0; width: 90px; }/* 标题和按钮的容器 */.sorted-item-details { flex-grow: 1; min-width: 0; }/* 标题链接样式,超出部分会显示省略号 */.sorted-item-title { font-size: 15px; color: #121212; text-decoration: none; display: block; margin-bottom: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.sorted-item-title:hover { color: #0084ff; }/* 按钮区域 */.sorted-item-actions { margin-top: 5px; }/* 按钮通用样式 */.sorted-item-button { font-size: 12px; padding: 3px 8px; margin-right: 8px; border: 1px solid #ccc; border-radius: 3px; background: #f9f9f9; cursor: pointer; text-decoration: none; color: #333; }.sorted-item-button:hover { background: #eee; border-color: #bbb; }`);/*** [核心配置]* voteSelector: 这是整个脚本最关键的修正。我们不再使用易变的CSS类名,而是使用属性选择器。* 'button[aria-label^="赞同"]' 的意思是:找到一个 <button> 元素,它的 "aria-label" 属性值是以 "赞同" 这两个字开头的。* 这种方法非常稳定,因为 aria-label 是为辅助功能服务的,其内容通常不会轻易改变。*/const voteSelector = 'button[aria-label^="赞同"]';/*** 页面配置中心。* 存储不同页面上“内容项”的独特选择器。脚本通过这些选择器来识别需要抓取和排序的目标。*/const pageConfigs = {// 问题回答页question: {itemSelector: '.Question-main .List-item', // 每个回答的最外层包裹元素voteSelector: voteSelector,titleSelector: null, // 问题页的所有回答都属于同一个问题,标题是固定的,所以在此特殊处理为null},// 搜索结果页search: {itemSelector: '.SearchResult-Card', // 每个搜索结果卡片voteSelector: voteSelector,titleSelector: 'h2.ContentItem-title a', // 结果卡片中的标题链接},// 首页推荐流feed: {itemSelector: '.TopstoryItem', // 首页推荐流中的每个内容项voteSelector: voteSelector,titleSelector: '.ContentItem-title a', // 内容项中的标题链接}};/*** 检测当前页面属于哪种类型(问题、搜索、首页)。* @returns {string|null} 返回页面类型字符串或null。*/function detectPageType() {const { hostname, pathname } = window.location;if (hostname === 'www.zhihu.com') {if (pathname.startsWith('/question/')) return 'question';if (pathname.startsWith('/search')) return 'search';if (pathname === '/' || pathname.startsWith('/follow')) return 'feed';}return null;}/*** 解析赞同数字符串 (例如 "1.2 万", "5,432", "3k") 为纯数字。* @param {string} voteText - 包含赞同数的文本。* @returns {number} - 解析后的数字。*/function parseVoteCount(voteText) {if (!voteText || typeof voteText !== 'string') return 0;// 正则表达式匹配数字(可能带逗号)和单位(k, w, 万)const match = voteText.replace(/,/g, '').match(/([\d.]+)\s*([kKwW万]?)/);if (!match) return 0;let num = parseFloat(match[1]);const unit = match[2] ? match[2].toLowerCase() : '';if (unit === 'k') num *= 1000;else if (unit === 'w' || unit === '万') num *= 10000;return isNaN(num) ? 0 : Math.round(num);}/*** 辅助函数:关闭并从页面上移除弹窗。*/function closeModal() {const modal = document.getElementById('sorter-modal');if (modal) { document.body.removeChild(modal); }}/*** 在弹窗中动态生成并显示排序结果列表。* @param {Array} sortedItems - 已排序的项目数据数组。* @param {HTMLElement} button - 主排序按钮,用于更新其状态。*/function displayResultsInModal(sortedItems, button) {closeModal(); // 如果已存在弹窗,先关闭const overlay = document.createElement('div');overlay.id = 'sorter-modal';overlay.className = 'sorter-modal-overlay';// 使用模板字符串构建弹窗的HTML结构let modalHtml = `<div class="sorter-modal-content"><div class="sorter-modal-header"><span class="sorter-modal-title">排序结果 (${sortedItems.length} 条)</span><button class="sorter-modal-close">&times;</button></div><div class="sorter-modal-body">`;// 如果没有找到任何可排序内容,显示提示信息if (sortedItems.length === 0) {modalHtml += '<p style="text-align: center; padding: 20px;">未能找到任何可排序的内容。请尝试向下滚动页面加载更多内容后,再点击排序。</p>';} else {// 遍历排序后的数据,生成每一行列表项sortedItems.forEach((item, index) => {modalHtml += `<div class="sorted-item"><div class="sorted-item-votes">👍 ${item.votesText}</div><div class="sorted-item-details"><a class="sorted-item-title" href="${item.url}" target="_blank" title="${item.title.replace(/"/g, '&quot;')}">${item.title}</a><div class="sorted-item-actions"><a href="${item.url}" target="_blank" class="sorted-item-button">新窗口打开</a><button class="sorted-item-button scroll-to" data-item-id="${index}">滚动到原文</button></div></div></div>`;});}modalHtml += `</div></div>`;// 将HTML注入弹窗并添加到页面overlay.innerHTML = modalHtml;document.body.appendChild(overlay);// 为所有“滚动到原文”按钮绑定点击事件overlay.querySelectorAll('.scroll-to').forEach(btn => {btn.addEventListener('click', () => {const itemId = parseInt(btn.dataset.itemId, 10);const originalElement = sortedItems[itemId].element;closeModal();// 使用 scrollIntoView 实现平滑滚动定位originalElement.scrollIntoView({ behavior: 'smooth', block: 'center' });// 给原文添加一个短暂的黄色高亮背景,方便用户识别originalElement.style.transition = 'all 0.3s ease-in-out';originalElement.style.backgroundColor = 'rgba(255, 255, 0, 0.5)';setTimeout(() => { originalElement.style.backgroundColor = ''; }, 1500);});});// 绑定关闭事件overlay.querySelector('.sorter-modal-close').addEventListener('click', closeModal);overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModal(); }); // 点击遮罩层关闭// 更新主按钮状态button.textContent = '排序完成!';setTimeout(() => { button.textContent = '排序'; button.disabled = false; }, 2000);}/*** 主流程函数:当用户点击排序按钮时执行。* @param {object} config - 当前页面的配置对象。* @param {HTMLElement} button - 主排序按钮。*/function processAndShowSortedList(config, button) {button.textContent = '抓取中...';button.disabled = true;console.log(`${LOG_PREFIX} [全局搜索模式] 寻找项目: ${config.itemSelector}`);// [核心改动] 直接在整个 document 对象上进行全局搜索,绕开容器查找失败的问题。const items = Array.from(document.querySelectorAll(config.itemSelector));if (items.length === 0) {console.warn(`${LOG_PREFIX} 全局搜索未能找到任何可排序的项目。`);displayResultsInModal([], button);return;}console.log(`${LOG_PREFIX} 全局搜索成功找到 ${items.length} 个项目。`);const itemsData = [];items.forEach(item => {// 关键过滤步骤:在每个找到的项目内部,再次用精确的属性选择器寻找赞同按钮const voteElement = item.querySelector(config.voteSelector);if (!voteElement) return; // 如果找不到赞同按钮(比如广告),就跳过这个项目// 解析赞同数和显示的文本const votes = parseVoteCount(voteElement.getAttribute('aria-label') || voteElement.innerText);const votesText = (voteElement.innerText.replace('赞同', '').trim() || '0');let title = '无标题';let url = '#';// 对问题回答页进行特殊处理,因为它的标题是固定的问题标题if (config.titleSelector === null) {const questionTitleEl = document.querySelector('.QuestionHeader-title');title = questionTitleEl ? `回答: ${questionTitleEl.innerText}` : '回答';const answerLinkEl = item.querySelector('meta[itemprop="url"]');url = answerLinkEl ? answerLinkEl.content : item.querySelector('a[data-za-detail-view-element_name="Title"]')?.href || '#';} else { // 处理首页和搜索页const titleElement = item.querySelector(config.titleSelector);if (titleElement) {title = titleElement.innerText.trim();url = titleElement.href;}}// 将解析好的数据存入数组itemsData.push({ element: item, votes, votesText, title, url });});console.log(`${LOG_PREFIX} 成功解析 ${itemsData.length} 个有效项目。`);// 按赞同数(votes)进行降序排序itemsData.sort((a, b) => b.votes - a.votes);// 调用函数显示结果displayResultsInModal(itemsData, button);}/*** 创建并向页面添加左下角的悬浮排序按钮。* @param {object} config - 当前页面的配置对象。*/function createFixedButton(config) {if (document.getElementById('zhihu-sort-enhancer-btn')) return;const button = document.createElement('button');button.id = 'zhihu-sort-enhancer-btn';button.textContent = '排序';// 设置按钮的CSS样式,使其固定在左下角Object.assign(button.style, {position: 'fixed', bottom: '20px', left: '20px', zIndex: '9999',padding: '10px 15px', fontSize: '14px', color: '#fff',backgroundColor: '#0084ff', border: 'none', borderRadius: '5px',cursor: 'pointer', boxShadow: '0 2px 10px rgba(0,0,0,0.2)',transition: 'all 0.2s'});// 绑定点击事件,触发排序流程button.addEventListener('click', () => processAndShowSortedList(config, button));document.body.appendChild(button);console.log(`${LOG_PREFIX} 悬浮按钮创建成功。`);}/*** [最终优化] 脚本的启动入口函数,使用 MutationObserver 智能等待内容加载。*/function initialize() {const pageType = detectPageType();if (!pageType) return;const config = pageConfigs[pageType];console.log(`${LOG_PREFIX} 已识别页面为 [${pageType}]`);console.log(`${LOG_PREFIX} 正在等待第一个内容项出现: ${config.itemSelector}`);// 创建一个DOM变更观察器const observer = new MutationObserver((mutations, obs) => {// 每次页面DOM变化时,都检查第一个内容项是否已出现if (document.querySelector(config.itemSelector)) {console.log(`${LOG_PREFIX} 第一个内容项已出现, 正在创建按钮...`);// 一旦出现,立刻创建按钮createFixedButton(config);// 停止观察,避免不必要的性能消耗obs.disconnect();}});// 启动观察器,监视整个文档的变化observer.observe(document.body, {childList: true, // 观察子节点的添加或删除subtree: true    // 观察所有后代节点});}// 运行启动函数initialize();})();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/931803.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

印度乡村AI计划:用JAN AI打造人工智能优先村庄

JAN AI计划在印度农村地区建立人工智能优先村庄,通过本地语言AI培训帮助农民诊断作物疾病、助力手工艺人线上销售,目标覆盖1万个村庄、培训1000万公民并培养10万名乡村AI创业者。印度乡村AI计划:用JAN AI打造人工智…

兼论spatial和simulink,PTX及其他

兼论spatial和simulink,PTX及其他从工匠(工程师)的角度看,并行计算的构造最直观的一种方式共享内存的构架,从计算上讲就是数据级并行,比如单指令多数据流SIMD。 这种构造可以是从应用层来看,也可以是底层的硬件实…

做门户网站需要什么条件wordpress侧边文本轮播图片

Part.1 背景 近年来&#xff0c;汽车行业蓬勃发展&#xff0c;一度出现供不应求的现象。在汽车零配件、整车大规模制造的过程中&#xff0c;为了降本增效&#xff0c;提升产品质量&#xff0c;工厂急需完成自动化升级。随着人工智能的发展&#xff0c;越来越多的生产环节引入机…

怎么做简单网站首页北京网站怎么优化

本次分享将讲述如何在Python中对多个list的对应元素求和&#xff0c;前提是每个list的长度一样。比如&#xff1a;a[1,2,3], b[2,3,4], c[3,4,5], 对a,b,c的对应元素求和&#xff0c;输出应为[6,9,12].    方法一&#xff1a;   直接求解&#xff0c;按照对应元素相加的…

# Java方法学习:动手动脑与课后实验整理

这两周学了Java方法相关内容,攒了不少练习题和实验作业,整理成博客记录一下,也方便以后复习的时候回看~ 一、动手动脑小练习自定义随机数生成器 之前一直用Math.random()或者Random类生成随机数,这次老师要求用指定…

CF2155D Batteries

给定 \(n\) 个电池,其中 \(a\) 个是有效的,但是你不知道 \(a\) 的值,每次你可以选择两个电池进行询问,可以得知他们两个是否都有效。 要求在 \(\left\lfloor\dfrac{n^2}{a}\right\rfloor\) 次询问内找出至少一对有…

网站搜索引擎优化建议学校专业群建设专题网站

到这里&#xff0c;我们需要整理一下之前学习的epoll模型&#xff0c;并根据之前的epoll模型&#xff0c;提出弊端&#xff0c;进而整理epoll反应堆模型&#xff0c;进一步深刻理解&#xff0c;这是因为epoll实在是太重要了。 复习之前的epoll的整体流程以及思路。 参考之前写…

网站怎么伪静态网站上线是前端还是后端来做

目录 1.CString数据转化为int类型数据 2.int类型转化为CString类型数据 3.MFC中CString数据类型 转换为std::string数据类型 4.MFC中std::string数据类型 转换为CString数据类型 1.CString数据转化为int类型数据 在MFC中&#xff0c;将CString类型数据转换为int类型数据&a…

电商门户网站最美情侣免费观看

Jvm垃圾回收器cms和g1区别 G1垃圾回收器&#xff08;Garbage First&#xff09;和CMS &#xff08;Concurrent Mark-Sweep&#xff09;垃圾回收器是Java虚拟机&#xff08;JVM&#xff09;&#xff09;中的两种不同的垃圾回收策略&#xff0c;它们各有优缺点。以下是两者的比较…

北流做网站网页制作工具按其制作方式可分为

1、python介绍及与其它开发语言比较&#xff1a; 相比C、Java运行慢&#xff0c;但是代码简介&#xff0c;可以减小学习成本&#xff0c;加快项目进度。跨平台&#xff0c;支持Linux和Windows。 C语言是所有高级语言的基础&#xff0c;若要研究python语言的原理&#xff0c;需要…

JAVA语法基础》动手动脑与实验问题全整理

一、枚举类型(Enum)核心问题:枚举是基本数据类型还是引用类型?验证方法:使用和equals()比较枚举值。结论:枚举是引用类型,但相同值的枚举变量指向同一个对象,因此和equals()比较结果相同。 二、变量屏蔽…

崩铁壁纸

本人(KK_SpongeBob)蒟蒻,写不出好文章,但转载请注明原文链接:https://www.cnblogs.com/OIer-QAQ/p/19129921

PotPlayer 播放器

PotPlayer 播放器 快捷键 快进(左右) 音量(上下)

国内的平面设计网站wordpress配置网络

1. 题目 我们有一个项的集合&#xff0c;其中第 i 项的值为 values[i]&#xff0c;标签为 labels[i]。 我们从这些项中选出一个子集 S&#xff0c;这样一来&#xff1a; |S| < num_wanted对于任意的标签 L&#xff0c;子集 S 中标签为 L 的项的数目总满足 < use_limit…

10.8动手动孬

代码展示了方法重载的特殊之处,具体分析如下:特殊点:存在两个名为 square 的方法,一个接收 int 类型参数,返回 int 类型;另一个接收 double 类型参数,返回 double 类型。 原理:方法重载是指在同一个类中,允许…

[迷宫寻路 Round 3] 七连击

转化题意:求将一段序列划分为8段,求所有方案的前七段的每一段gcd的和的和. 首先朴素的dp很容易想到,设\(dp(i,j)\)为将前\(i\)位划分为前\(j\)段的答案,\(g(i,j)\)为将前\(i\)位划分为前\(j\)段的方案数. 于是有 \[\b…

Flink03-学习-套接字分词流自动写入工具 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

[笔记]树论笔记+做题记录

树的性质树上任意两点间恰有一条简单路径。树上所有节点度数和为 \(O(n)\) 的。树上 \(m\) 个点两两产生的 LCA 去重后不超过 \(m-1\) 个。Proof:考虑找 LCA 的过程,两个点向上跳,重合时合并成一个点。最后剩下 \(1…

云服务器部署大数据组件

大数据集群规划hw101 hw102 hw103HDFS NameNodeDataNode DataNode SecondaryNameNodeDataNodeYARN NodeManager ResourceManagerNodeManager NodeManagerZookeeper QuorumPeerMain QuorumPeerMain QuorumPeerMainHive …

做a视频网站有哪些要制作自己的网站需要什么

目录 LNMP部署--nginx 搭建mysql数据库 安装mysql的过程&#xff1a; 部署PHP&#xff1a; ​编辑​编辑php的配置文件在哪 wordpress程序安装 LNMP部署--nginx 纯净--联网状态 环境变量中没有nginx 安装形式的选择&#xff1a; yum安装&#xff1a;自动下载安装包及…