// ==UserScript==
// @name 妖火复读机
// @namespace http://yaohuo.me/
// @supportURL http://zgcwkj.cn
// @version 20250714.01
// @description 妖火论坛每个回复后面加个复读按钮。
// @author zgcwkj
// @match *://yaohuo.me/bbs*
// @match *://www.yaohuo.me/bbs*
// @grant none
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/502259/%E5%A6%96%E7%81%AB%E5%A4%8D%E8%AF%BB%E6%9C%BA.user.js
// @updateURL https://update.greasyfork.icu/scripts/502259/%E5%A6%96%E7%81%AB%E5%A4%8D%E8%AF%BB%E6%9C%BA.meta.js
// ==/UserScript==
(function () {
//回复内容组件
let domTextarea = document.querySelector('textarea')
//发表回复
window.reply = function (txt, face) {
//填写内容
domTextarea.value += txt.trim()
//不处理空白内容
if (domTextarea.value == '') return
//选择表情
for (let i = 0; i < document.forms.length; i++) {
const form = document.forms[i]
if (form.name == 'f') {
form.face.value = face
break
}
}
//点击回复按钮
let domInput = document.querySelectorAll('input')
for (let i = domInput.length - 1; i > 0; i--) {
const btn = domInput[i]
if (btn.value == '快速回复' || btn.value == '发表回复') {
btn.click()
break
}
}
}
//注入彩色回复按钮
window.newBtn = function(){
var domBtn = document.createElement('input')
domBtn.type = 'submit'
domBtn.className = 'btn'
domBtn.value = '彩字回复'
domBtn.onclick = function(e) {
e.preventDefault()
let text = domTextarea.value
domTextarea.value = ''
window.reply(generateFontUBB(text))
}
let domKSHF = document.querySelector('.kuaisuhuifu')
domKSHF .childNodes.forEach(item => { item.style = '' })
domKSHF.insertBefore(domBtn, domKSHF.children[1])
}
//注入更多按钮
let moreButn = [
{ css: 'background:#937a3e;color:#fff', showText: '吃', text: '吃', face: '', },
{ css: 'background:#937a3e;color:#fff', showText: '过', text: '过', face: '', },
{ css: 'background:#a7588d;color:#fffa28', showText: '感谢分享', text: '感谢分享', face: '谢谢.gif' },
{ css: 'background:#3e933e;color:#fff', showText: '哈哈', text: '哈哈', face: '哈哈.gif' },
{ css: 'background:#3e933e;color:#fff', showText: '恭喜', text: '恭喜', face: '么么哒.gif' },
{ css: 'background:#3e933e;color:#fff', showText: '大佬带带', text: '大佬带带', face: '耶耶.gif' },
{ css: 'background:#3e933e;color:#fff', showText: '大水比', text: '大水比', face: '被揍.gif' },
]
let domForm = document.querySelector('.recontent')
window.moreReplyBtn = function () {
if (domForm != null) {
//注入按钮
let className = 'moreReplyButn'
let isAddBtn = domForm.querySelector(`.${className}`)
if (isAddBtn == null) {
let btnHtml = ''
let btnStyle = 'padding:5px;border-radius:5px;font-size:14px;'
moreButn.forEach(f => {
btnHtml += ` ${f.showText}`
})
domForm.innerHTML = `
${btnHtml}
` + domForm.innerHTML
}
}
}
//注入复读按钮
let isNewLayout = document.querySelectorAll('.forum-container').length > 0
window.repeatBotBtn = function () {
//获取当前主题版本
let domTxt = {}
if (isNewLayout) domTxt = document.querySelectorAll('.forum-post')//新主题
else domTxt = document.querySelectorAll('.list-reply')//旧主题
//注入按钮
let className = 'repeatBotButn'
domTxt.forEach(f => {
let domSpan = f.querySelector('.retext')
let isAddBtn = domSpan.querySelector(`.${className}`)
if (isAddBtn == null) {
let txt = generateHtmlUbb(domSpan.innerHTML)
let btnHtml = ` +1`
domSpan.innerHTML += btnHtml
}
})
}
//检查页面
if (domTextarea != null) {
//注入彩色回复按钮
window.newBtn()
//注入更多按钮
window.moreReplyBtn()
//注入复读按钮
window.repeatBotBtn()
//元素监视(减少性能损耗)
let domViewContent = document.querySelector('.viewContent')
const callback = function (mutationsList, observer) {
//隐藏提示
var showTipe = document.querySelector('#retip')
if (showTipe != null) showTipe.style.display = 'none'
//注入更多按钮
window.moreReplyBtn()
//注入复读按钮
window.repeatBotBtn()
}
const observer = new MutationObserver(callback)
observer.observe(domViewContent, { childList: true, subtree: true })
}
//HTML转UBB
function generateHtmlUbb(html) {
//创建一个临时 DOM 元素来解析 HTML
const tempDiv = document.createElement('div')
tempDiv.innerHTML = html
//获取所有的 img 元素并替换为 UBB 格式
const imgElements = tempDiv.getElementsByTagName('img')
for (let i = imgElements.length - 1; i >= 0; i--) {
const img = imgElements[i]
const ubbImage = `[img]${img.src}[/img]`
//用 UBB 格式替换 img 元素
img.replaceWith(ubbImage)
}
//获取所有的 br 元素并替换为换行符
const brElements = tempDiv.getElementsByTagName('br')
for (let i = brElements.length - 1; i >= 0; i--) {
const br = brElements[i]
const newLine = document.createTextNode('\n')
//用换行符替换 br 元素
br.replaceWith(newLine)
}
//返回处理后的文本
return tempDiv.innerText || tempDiv.textContent
}
//生成彩色文字UBB
function generateFontUBB(input) {
//生成随机颜色
let minDistance = 150
let startColor, endColor, startRGB, endRGB
//不断生成直到满足距离要求
do {
startColor = getRandomColor()
endColor = getRandomColor()
startRGB = hexToRGB(startColor)
endRGB = hexToRGB(endColor)
} while (colorDistance(startRGB, endRGB) < minDistance)
//初始化UBB字符串
let ubbString = ''
const charCount = input.length
//为每个字符生成渐变色
for (let i = 0; i < charCount; i++) {
const ratio = charCount > 1 ? i / (charCount - 1) : 0
const charColor = getGradientColor(startRGB, endRGB, ratio)
ubbString += `[forecolor=${charColor}]${input[i]}[/forecolor]`
}
//返回字符串
return ubbString
}
//获取随机颜色
function getRandomColor() {
const letters = '0123456789ABCDEF'
let color = '#'
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)]
}
return color
}
//将十六进制颜色转换为RGB对象
function hexToRGB(hex) {
hex = hex.replace('#', '')
const r = parseInt(hex.substring(0, 2), 16)
const g = parseInt(hex.substring(2, 4), 16)
const b = parseInt(hex.substring(4, 6), 16)
return { r, g, b }
}
//计算两个RGB颜色之间的欧氏距离
function colorDistance(rgb1, rgb2) {
return Math.sqrt(
Math.pow(rgb2[0] - rgb1[0], 2) +
Math.pow(rgb2[1] - rgb1[1], 2) +
Math.pow(rgb2[2] - rgb1[2], 2)
)
}
//根据比例生成渐变色
function getGradientColor(start, end, ratio) {
const r = Math.round(start.r + ratio * (end.r - start.r))
const g = Math.round(start.g + ratio * (end.g - start.g))
const b = Math.round(start.b + ratio * (end.b - start.b))
return rgbToHex(r, g, b)
}
//将RGB值转换为十六进制颜色
function rgbToHex(r, g, b) {
return '#' + [r, g, b]
.map(x => {
const hex = x.toString(16)
return hex.length === 1 ? '0' + hex : hex
})
.join('')
}
})()