[油猴脚本] 回帖长度补全和替换泥潭助手图标
用户分享油猴脚本,实现发帖字数补全与替换泥潭助手图标。
1. 关键信息
- 脚本1:补全字数不足4的帖子(Sticker计1字符),使用零宽空格填充,忽略引文与单Sticker例外;替换泥潭助手图标为/u/258头像(#1)。
- 脚本2:基于图片Markdown在字数不足时插入Emoji图片作为补全(#7)。
2. 羊毛/优惠信息
无
3. 最新动态
无
4. 争议或不同意见
- #6质疑泥潭助手的实用性与时间成本。
5. 行动建议
无
看看我今天摸了什么: 回复一句简短绝杀的你还在zszs补充长度吗?试试这个脚本,可以在字符长度为4以下的时候补充不换行空格 // ==UserScript== // @name USCardForum Length Helper // @namespace http://tampermonkey.net/ // @version 1.0 // @description Ensures post length is at least 4 chars (stickers count as 1) on USCardForum // @author Antigravity // @match https://www.uscardforum.com/* // @match https://uscardforum.com/* // @grant none // ==/UserScript== (function () { 'use strict'; // Regex to identify stickers matching the :emoji_name: pattern const stickerRegex = /:[a-zA-Z0-9_\-+]+:/g; // Regex to identify quotes [quote=...]...[/quote] const quoteRegex = /\[quote(?:=[^\]]+)?\][\s\S]*?\[\/quote\]/gi; /** * Recursively removes all quote blocks from the text. */ function stripQuotes(text) { let lastText; do { lastText = text; text = text.replace(quoteRegex, ''); } while (text !== lastText); return text; } /** * Calculates the "effective" length where stickers count as 1 character and quotes are ignored. * Whitespace is trimmed to match the forum's validation logic. */ function calculateEffectiveLength(text) { const withoutQuotes = stripQuotes(text).trim(); // Replace all stickers with a placeholder 'S' and then count characters return withoutQuotes.replace(stickerRegex, 'S').length; } /** * Checks if the content consists of exactly one sticker and optional surrounding whitespace, * ignoring any quote blocks. */ function isSingleSticker(text) { const withoutQuotes = stripQuotes(text).trim(); const matches = withoutQuotes.match(stickerRegex); // It's a single sticker if there is exactly one match and it matches the entire non-quote string return matches && matches.length === 1 && matches[0] === withoutQuotes; } document.addEventListener('click', function (event) { // Find the clicked element or its closest ancestor that has a class containing "create" // This matches buttons like 'Reply' or 'Create Topic' const target = event.target.closest('[class*="create"]'); if (!target) return; // Find the composer textarea (standard Discourse class) const textarea = document.querySelector('textarea.d-editor-input'); if (!textarea) return; const originalValue = textarea.value; // Apply exception: exactly one sticker is fine if (isSingleSticker(originalValue)) { return; } const effectiveLen = calculateEffectiveLength(originalValue); if (effectiveLen < 4) { const paddingNeeded = 4 - effectiveLen; // Append Zero-Width Space (U+200B) const newValue = originalValue + '\u200B'.repeat(paddingNeeded); textarea.value = newValue; // Trigger an 'input' event so the site's framework (Discourse/Ember) // synchronizes the state and enables the submit button textarea.dispatchEvent(new Event('input', { bubbles: true })); } }, true); // Use capture phase to perform logic before potential submission events })(); 这个脚本可以把泥潭助手的图标替换成 /u/258 的头像。 // ==UserScript== // @name USCardForum Image Replacer // @namespace http://tampermonkey.net/ // @version 0.1 // @description Replace specific SVG icons/images within a given div on uscardforum.com with a custom image link // @match *://*.uscardforum.com/* // @match *://uscardforum.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; // 默认的替换图片链接 const DEFAULT_IMAGE_URL = 'https://www-cdn.uscardforum.com/user_avatar/www.uscardforum.com/258/288/690799_2.png'; // 获取设置的图片链接 function getReplacementUrl() { return GM_getValue('replacementImageUrl', DEFAULT_IMAGE_URL); } // 注册设置菜单 GM_registerMenuCommand('设置替换图片链接', () => { const currentUrl = getReplacementUrl(); const newUrl = prompt('请输入新的图片链接:', currentUrl); if (newUrl !== null && newUrl.trim() !== '') { GM_setValue('replacementImageUrl', newUrl.trim()); alert('图片链接已更新,刷新页面后生效。'); replaceImages(); // 尝试立即替换 } }); // 我们将把相关的属性加上 !important,并用循环定时器替代 observer // 很多插件或者 SPA 框架(如 React/Vue)会在不同时机渲染这个组件 // 定时检查是应对这种动态悬浮控件(z-index 很高)最稳妥的办法 function replaceImages() { const replacementUrl = getReplacementUrl(); // 查找包含指定的打开图标 const openIcons = document.querySelectorAll('svg#openIcon'); openIcons.forEach(openIcon => { const container = openIcon.parentElement; // 验证是否是我们要找的那个 div const closeIcon = container.querySelector('svg#closeIcon'); if (container && closeIcon) { let img = container.querySelector('img.custom-replacer-img'); // 如果还没有添加我们的替换图片,则进行添加 if (!img) { // 设置容器的一些属性以防止其变为方块 container.style.borderRadius = '50%'; container.style.overflow = 'hidden'; // 创建我们自己的替换图片 img = document.createElement('img'); img.className = 'custom-replacer-img'; img.src = replacementUrl; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'cover'; // 为了不遮挡 closeIcon,也不阻挡用户点击原来的悬浮球容器 img.style.position = 'absolute'; img.style.top = '0'; img.style.left = '0'; img.style.zIndex = '0'; img.style.pointerEvents = 'none'; // 【关键】让点击穿透图片 // 将图片插入到容器的最前面 container.prepend(img); // 创建一个观察器,专门用来实现无延迟的状态切换 const observer = new MutationObserver((mutations) => { let shouldSync = false; for (let m of mutations) { if (m.target === closeIcon || m.target === openIcon) { shouldSync = true; break; } } if (shouldSync) { syncImageState(img, openIcon, closeIcon); } }); // 监听原始图标属性发生变化(如 style 改变隐现状态) observer.observe(container, { attributes: true, subtree: true, attributeFilter: ['style', 'class'] }); } // 初始化与轮询调用 syncImageState(img, openIcon, closeIcon); } }); } // 单独抽象出的状态同步逻辑 function syncImageState(img, openIcon, closeIcon) { const isCloseIconVisible = closeIcon.style.display !== 'none'; if (isCloseIconVisible) { // 当展开时,只显示 closeIcon,隐藏我们的自定义图片 // 为了防止死循环,先判断当前值 if (img.style.getPropertyValue('display') !== 'none') { img.style.setProperty('display', 'none', 'important'); } } else { // 当收起时,显示我们的自定义图片,并强制隐藏原有的 openIcon if (img.style.getPropertyValue('display') !== 'block') { img.style.setProperty('display', 'block', 'important'); } if (openIcon.style.getPropertyValue('display') !== 'none') { openIcon.style.setProperty('display', 'none', 'important'); } } // 确保 closeIcon 层级正确不被图片遮挡 closeIcon.style.zIndex = '10'; } // 页面加载完成后执行替换 window.addEventListener('load', replaceImages); // 初始化时执行一次 replaceImages(); // 每秒执行一次轮询,仅用来容错和兜底保证即使 DOM 被重建也会重新插入 // 实时的无延迟体验已经由 MutationObserver 提供 setInterval(replaceImages, 1000); })();
折木奉太郎: 这个脚本可以把泥潭助手的图标替换成 /u/258 的头像。 ???
6
水
泥潭助手有用吗 ? 浪费时间还要找个助手, 时间不就浪费不完了啊。
自动补 // ==UserScript== // @name USCardForum Length Helper (Image Version) // @namespace http://tampermonkey.net/ // @version 1.1 // @description 当字数不足4时,自动补全指定的 Emoji 图片 // @author Antigravity & Gemini // @match https://www.uscardforum.com/* // @match https://uscardforum.com/* // @grant none // ==/UserScript== (function () { 'use strict'; // 目标补全的图片 Markdown const paddingImage = ''; // Regex to identify stickers matching the :emoji_name: pattern const stickerRegex = /:[a-zA-Z0-9_\-+]+:/g; // Regex to identify quotes [quote=...]...[/quote] const quoteRegex = /\[quote(?:=[^\]]+)?\][\s\S]*?\[\/quote\]/gi; /** * Recursively removes all quote blocks from the text. */ function stripQuotes(text) { let lastText; do { lastText = text; text = text.replace(quoteRegex, ''); } while (text !== lastText); return text; } /** * Calculates the "effective" length where stickers count as 1 character and quotes are ignored. */ function calculateEffectiveLength(text) { const withoutQuotes = stripQuotes(text).trim(); // Replace all stickers with a placeholder 'S' and then count characters return withoutQuotes.replace(stickerRegex, 'S').length; } /** * Checks if the content consists of exactly one sticker */ function isSingleSticker(text) { const withoutQuotes = stripQuotes(text).trim(); const matches = withoutQuotes.match(stickerRegex); return matches && matches.length === 1 && matches[0] === withoutQuotes; } document.addEventListener('click', function (event) { // 匹配包含 "create" 的按钮(如回复、创建主题) const target = event.target.closest('[class*="create"]'); if (!target) return; const textarea = document.querySelector('textarea.d-editor-input'); if (!textarea) return; const originalValue = textarea.value; // 例外情况:如果只有一个表情,不进行补全 if (isSingleSticker(originalValue)) { return; } const effectiveLen = calculateEffectiveLength(originalValue); if (effectiveLen > 0 && effectiveLen < 4) { const paddingNeeded = 4 - effectiveLen; // 将原来的零宽空格补全逻辑改为图片 Markdown 补全 // 每次补全前加一个换行或空格,防止图片和文字粘在一起(可选) const newValue = originalValue + "\n" + paddingImage.repeat(paddingNeeded); textarea.value = newValue; // 触发 input 事件以同步论坛前端状态 textarea.dispatchEvent(new Event('input', { bubbles: true })); } }, true); })();