您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
论坛列表显示图片,同时支持discuz搭建的论坛(如吾爱破解)以及phpwind搭建的论坛(如south plus)灯
当前为
// ==UserScript== // @name 论坛列表显示图片 // @namespace form_show_images_in_list // @version 1.4 // @description 论坛列表显示图片,同时支持discuz搭建的论坛(如吾爱破解)以及phpwind搭建的论坛(如south plus)灯 // @license MIT // @author Gloduck // @note discuz路径匹配 // @match *://*/forum-*.html // @match *://*/forum-*.html?* // @match *://*/forum.php // @match *://*/forum.php?* // @match *://*/*/forum-*.html // @match *://*/*/forum-*.html?* // @match *://*/*/forum.php // @match *://*/*/forum.php?* // @note phpwind路径匹配 // @match *://*/*/thread.php // @match *://*/*/thread.php?* // @match *://*/thread.php // @match *://*/thread.php?* // @note 1024路径匹配 // @match *://*/*/thread0806.php* // @match *://*/thread0806.php* // @grant GM_xmlhttpRequest // @grant GM_addStyle // ==/UserScript== (function () { 'use strict'; GM_addStyle(` .zoomable-image { cursor: pointer; } .zoomable-image.zoomed { position: fixed; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; background: rgba(0, 0, 0, 0.9); z-index: 9999; } `); // todo 添加extend继承的功能 let settings = [ { // 类型名称 name: "discuz", // 文章列表选择器 articleListSelector: 'tbody[id^="normalthread_"]', // 文章链接a标签选择器 articleLinkSelector: '.icn a', // 文章详情页中文章主体选择器 postContentSelector: 'div[id^="post_"] .plc', // 需要忽略的图片的正则表达式字符串 ignoreImageRegs: [ "/uc_server/images/*", "static/image/*", "/uc_server/data/avatar/*" ], // 列表最大展示图片数量(todo 支持的有点问题,反正别写太大了) maxShowLimit: 3, // 是否启用懒加载 lazyLoad: true, // 找到img标签后,解析img标签中链接的callback postImageLinkCallback: function (element) { let fileLink = element.getAttribute('file'); if (fileLink) { return fileLink; } return element.getAttribute('src'); }, // 初始化好放图片的div后,对该div的element包装 initElementDecorator: function (element) { let tbody = document.createElement("tbody"); let tr = document.createElement("tr"); tr.appendChild(element); tbody.appendChild(tr); return tbody; } }, { name: "phpwind", articleListSelector: '#ajaxtable tbody:last-of-type tr[align=center]', articleLinkSelector: 'td a', postContentSelector: '.tpc_content', ignoreImageRegs: [ "images/post/smile/*", ], maxShowLimit: 3, lazyLoad: true, postImageLinkCallback: function (element) { return element.getAttribute('src'); }, initElementDecorator: function (element) { let tr = document.createElement("tr"); tr.align = "center"; let td = document.createElement("td"); td.colSpan = 5; tr.appendChild(td); td.appendChild(element); return tr; } }, { name: "1024", articleListSelector: 'tbody[id="tbody"] tr', articleLinkSelector: '.tal h3 a', postContentSelector: '#conttpc', ignoreImageRegs: [], maxShowLimit: 3, lazyLoad: true, postImageLinkCallback: function (element) { let fileLink = element.getAttribute('ess-data'); if (fileLink) { return fileLink; } return element.getAttribute('src'); }, initElementDecorator: function (element) { let tr = document.createElement("tr"); tr.align = "center"; let td = document.createElement("td"); td.colSpan = 5; tr.appendChild(td); td.appendChild(element); return tr; } } ]; let urlPatterns = [ { name: "discuz", pattern: [ "*://*/forum-*.html", "*://*/forum-*.html?*", "*://*/forum.php", "*://*/forum.php?*", "*://*/*/forum-*.html", "*://*/*/forum-*.html?*", "*://*/*/forum.php", "*://*/*/forum.php?*" ] }, { name: "phpwind", pattern: [ "*://*/*/thread.php", "*://*/*/thread.php?*", "*://*/thread.php", "*://*/thread.php?*" ] }, { name: "1024", pattern: [ "*://*/*/thread0806.php*", "*://*/thread0806.php*" ] } ] activeByUrlPattern(); function activeByUrlPattern() { let activeSettingNames = []; urlPatterns.forEach(value => { let urlPatternReg = value.pattern.map(urlPattern => toURlPattern(urlPattern)); if (checkRegMatchStr(urlPatternReg, window.location.href)) { activeSettingNames.push(value.name); } }) if (activeSettingNames.length == 0) { console.log("无法找到要激活的配置"); return; } if (activeSettingNames.length != 1) { console.log("找到多个匹配的配置,默认激活第一个") } let activeSettingName = activeSettingNames[0]; console.log("激活的配置为:" + activeSettingName); showImageInList(activeSettingName); } /** * 根据类型来处理列表链接图片的展示 * @param type {string} 类型 */ function showImageInList(type) { let setting = getSettingByType(type); let articleListElement = document.querySelectorAll(setting.articleListSelector); articleListElement.forEach(element => { if (setting.lazyLoad) { lazyLoadImageInList(element, setting); } else { loadImageInList(element, setting); } }) } /** * 懒加载列表链接里面的图片 * @param element {Element} * @param setting {Object} */ function lazyLoadImageInList(element, setting) { // 注册(不可用)滚动事件,实现懒加载。同时通过节流来避免重复加载 window.addEventListener('scroll', throttle(function () { const targetElementRect = element.getBoundingClientRect(); if (targetElementRect.top < window.innerHeight && !element.getAttribute("imageLoad")) { handleSingleArticle(element, setting).then(toAppendElement => { if (!element.getAttribute("imageLoad")) { insertElementBelow(element, toAppendElement); element.setAttribute("imageLoad", "true"); } }) } }, 200, 500)); } /** * 实时加载列表链接里面的图片 * @param element {Element} * @param setting {Object} */ function loadImageInList(element, setting) { handleSingleArticle(element, setting).then(toAppendElement => { insertElementBelow(element, toAppendElement); }) } /** * 插入元素到对应元素之后(需要有夫元素) * @param targetElement {Element} * @param newElement {Element} */ function insertElementBelow(targetElement, newElement) { var parentElement = targetElement.parentNode; parentElement.insertBefore(newElement, targetElement.nextSibling); } /** * 根据类型获取设置信息 * @param type * @returns {{ignoreImageRegs: string[], initElementDecorator: (function(*=): HTMLTableSectionElement), name: string, postImageLinkCallback: ((function(*): (string))|*), postContentSelector: string, articleListSelector: string, maxShowLimit: number, lazyLoad: boolean} | {ignoreImageRegs: *[], initElementDecorator: (function(*=): HTMLTableRowElement), name: string, postImageLinkCallback: (function(*): string), postContentSelector: string, articleListSelector: string, maxShowLimit: number, lazyLoad: boolean}} */ function getSettingByType(type) { let setting = settings.find(value => { return value.name === type; }); if (setting == null) { throw new Error("不支持的类型"); } return setting; } /** * 处理单个文章,返回最后需要拼接的element * @param element {Element} * @param setting {Object} * @returns {Promise<void>} */ async function handleSingleArticle(element, setting) { if (!element) { throw new Error("参数不能为空"); } let link = findActualArticleLinkBySelector(setting.articleLinkSelector, element); let postResult = await httpRequest("GET", link); if (!postResult) { throw new Error("请求文章错误"); } var htmlDivElement = document.createElement("div"); // 初始化图片区域 htmlDivElement.appendChild(getImagesDiv(setting, link, postResult)); // todo 添加自定义元素 return setting.initElementDecorator(htmlDivElement); } /** * 根据设置,解析文章中的图片,并且生成html div * @param setting {Object} * @param content {string} * @param postLink {string} * @returns {HTMLDivElement} */ function getImagesDiv(setting, postLink, content) { let images = parsePostImages(setting, postLink, content); if (setting.maxShowLimit && setting.maxShowLimit > 0) { images = images.slice(0, setting.maxShowLimit); } let imageDiv = document.createElement("div"); imageDiv.style = "display: flex;"; imageDiv.className = "image_list"; images.forEach(value => { let imgElement = document.createElement("img"); imgElement.src = value; imgElement.style = "max-width: 300px;max-height: 300px;margin-right: 10px" imageDiv.appendChild(imgElement); imgElement.addEventListener('click', function () { // 创建一个新的图片元素 var zoomedImg = document.createElement('img'); zoomedImg.src = imgElement.src; // 添加类名以应用放大样式 zoomedImg.classList.add('zoomable-image', 'zoomed'); // 点击放大的功能 zoomedImg.addEventListener('click', function () { // 移除放大的图片元素 document.body.removeChild(zoomedImg); }); // 将放大的图片元素添加到文档中 document.body.appendChild(zoomedImg); }); }) return imageDiv; } /** * 根据类型设置匹配图片中的链接 * @param setting {Object} 类型设置 * @param postLink {string} 文章链接 * @param postDetails {string} 文章的内容字符串(解析前的html) * @returns {*[]} */ function parsePostImages(setting, postLink, postDetails) { let images = []; let content = new DOMParser().parseFromString(postDetails, "text/html"); if (!content) { return images; } let postContentSelector = setting.postContentSelector; let postContent = content.querySelector(postContentSelector); if (!postContent) { console.log("无法匹配到文章主体,请确认选择器是否正确,并确认点击链接进去是否能正常访问内容,匹配失败的链接为:" + postLink); return images; } let ignoreImageRegs = regStrToReg(setting.ignoreImageRegs); let imageElements = postContent.querySelectorAll('img'); imageElements.forEach(imageElement => { let imageLink = setting.postImageLinkCallback(imageElement); if (checkRegMatchStr(ignoreImageRegs, imageLink)) { return; } images.push(convertPathToAccessible(imageLink, postLink)); }) return images; } /** * 通过文章链接选择器获取文章的绝对链接 * @param selector {string} * @param element {Element} */ function findActualArticleLinkBySelector(selector, element) { let linkElement = element.querySelector(selector); if (!linkElement) { throw new Error("通过选择器,无法找到文章的链接元素"); } let href = linkElement.getAttribute("href"); if (!href) { throw new Error("无法获取href元素,请确认选择器是否最终选择了一个a标签,以及a标签上是否有href"); } return convertPathToAccessible(href, window.location.href); } /** * 找到第一个a标签中的链接 * @param element {Element} * @returns {*|string|null} */ function findFirstAnchorLink(element) { const linkElement = element.querySelector("a"); if (linkElement) { return linkElement.getAttribute("href"); } else { const childElements = element.children; for (let i = 0; i < childElements.length; i++) { const link = findFirstAnchorLink(childElements[i]); if (link) { return link; } } } return null; } /** * 正则表达式字符串列表转正则表达式列表 * @param regs {string[]} * @returns {*} */ function regStrToReg(regs) { return regs.map(value => { return new RegExp(value); }); } /** * 校验正则表达式是否匹配内容 * @param regs {RegExp[]} * @param content {string} * @returns {boolean} */ function checkRegMatchStr(regs, content) { if (!content || !regs) { throw new Error("参数不能为空"); } for (var i = 0; i < regs.length; i++) { if (regs[i].test(content)) { return true; } } return false; } function convertPathToAccessible(path, currentPath) { var url = new URL(path, currentPath); return url.href; } /** * 防抖 * @param func {function} 回调函数 * @param wait 等待时间(ms) * @returns {(function(): void)|*} */ function debounce(func, wait) { // 定时器变量 var timeout; return function () { // 每次触发 scroll handler 时先清除定时器 clearTimeout(timeout); // 指定 xx ms 后触发真正想进行的操作 handler timeout = setTimeout(func, wait); }; }; /** * 节流 * @param func {function} 回调函数 * @param wait 延迟执行时间(ms) * @param mustRun 必须执行时间(ms) * @returns {(function(): void)|*} */ function throttle(func, wait, mustRun) { var timeout, startTime = new Date(); return function () { var context = this, args = arguments, curTime = new Date(); clearTimeout(timeout); // 如果达到了规定的触发时间间隔,触发 handler if (curTime - startTime >= mustRun) { func.apply(context, args); startTime = curTime; // 没达到触发间隔,重新设定定时器 } else { timeout = setTimeout(func, wait); } }; }; /** * @param patternStr {string} * @returns {RegExp} */ function toURlPattern(patternStr) { return new RegExp('^' + patternStr .replace(/\*/g, '.*') .replace(/\//g, '\\/')); } /** * 调用油猴脚本发送请求 * @param method {string} 请求方式 * @param url {string} 请求地址 * @returns {Promise<unknown>} */ function httpRequest(method, url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: method, url: url, onload: function (response) { resolve(response.responseText); }, onerror: function (error) { reject(error); } }); }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址