// ==UserScript==
// @name Linux DO 助手 (Clash导入+快捷评论)
// @namespace http://tampermonkey.net/
// @version 1.03
// @description 为 Linux DO 论坛添加Clash订阅导入和快捷评论功能
// @author ltw
// @match https://linux.do/*
// @license MIT
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant window.focus
// ==/UserScript==
;(function () {
"use strict"
// =============== 样式定义 ===============
const style = document.createElement("style")
style.textContent = `
.clash-import-btn {
margin-left: 8px;
padding: 4px 12px;
background: linear-gradient(145deg, #4CAF50, #45a049);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
transition: all 0.3s ease;
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.clash-import-btn:hover {
background: linear-gradient(145deg, #45a049, #388e3c);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.clash-import-btn:active {
transform: translateY(1px);
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
`
document.head.appendChild(style)
// =============== Clash导入功能 ===============
// 订阅链接正则表达式
const subscriptionPattern = /https?:\/\/[^\s<>"']+?(?:token=)[^\s<>"']+/gi
// 记录已处理的URL,避免重复添加按钮
const processedUrls = new Set()
// 检查URL是否已有对应的按钮
function hasClashButton(url) {
const encodedUrl = btoa(url).replace(/=/g, "")
return !!document.querySelector(`[data-clash-url="${encodedUrl}"]`)
}
// 导入到Clash
async function importToClash(url) {
try {
const clashProtocol = `clash://install-config?url=${encodeURIComponent(
url
)}&name=${encodeURIComponent("导入的配置")}`
const link = document.createElement("a")
link.style.display = "none"
link.href = clashProtocol
link.rel = "noopener noreferrer"
document.body.appendChild(link)
link.click()
setTimeout(() => {
document.body.removeChild(link)
}, 100)
} catch (e) {
alert("导入失败,请确保已安装Clash并正确配置")
}
}
// 创建导入按钮
function createImportButton(url) {
const encodedUrl = btoa(url).replace(/=/g, "")
const button = document.createElement("button")
button.className = "clash-import-btn"
button.textContent = "导入到Clash"
button.dataset.clashUrl = encodedUrl
button.onclick = e => {
e.preventDefault()
e.stopPropagation()
importToClash(url)
}
return button
}
// 为文本节点添加导入按钮
function processTextNode(node) {
// 如果节点是空的或者不是文本节点,直接返回
if (!node || !node.textContent) return
// 获取节点的文本内容
const text = node.textContent
// 使用正则表达式查找所有匹配的URL
const matches = text.matchAll(subscriptionPattern)
for (const match of matches) {
const fullUrl = match[0]
// 如果URL已经处理过,跳过
if (processedUrls.has(fullUrl) || hasClashButton(fullUrl)) continue
// 创建并插入按钮
const button = createImportButton(fullUrl)
if (node.parentElement) {
node.parentElement.insertBefore(button, node.nextSibling)
processedUrls.add(fullUrl)
}
}
}
// 为链接添加导入按钮
function addImportButton(element) {
const url = element.href || element.textContent.trim()
if (!subscriptionPattern.test(url)) return
// 如果已经处理过,直接返回
if (processedUrls.has(url) || hasClashButton(url)) return
const button = createImportButton(url)
element.parentNode.insertBefore(button, element.nextSibling)
processedUrls.add(url)
}
// 清理重复按钮
function cleanupDuplicateButtons() {
const processedButtonUrls = new Set()
document.querySelectorAll(".clash-import-btn").forEach(button => {
const url = button.dataset.clashUrl
if (processedButtonUrls.has(url)) {
button.remove()
} else {
processedButtonUrls.add(url)
}
})
}
// =============== 快捷评论功能 ===============
// 默认快捷评论列表
const quickComments = [
"感谢分享 🙏",
"直接起飞 🚀",
"感谢大佬分享 ✨",
"学习了,收藏! 📚",
"涨知识了 🧠",
"学到了学到了 ✌️",
"mark一下,以后用得着 🔖",
"太强了吧 🚀",
"学习ing... 📝",
"收藏了,慢慢消化 🤓",
"大佬tql 🎉",
"学习了,学习了 💪",
"参与参与✌️"
]
// 创建快捷评论面板
function createQuickCommentPanel() {
const panel = document.createElement("div")
panel.style.cssText = `
position: fixed;
right: 80px;
top: 50%;
transform: translateY(-50%);
background: linear-gradient(145deg, #e8f4f8, #d5e6f3);
padding: 15px;
border-radius: 16px;
box-shadow: 5px 5px 15px rgba(0,0,0,0.1),
-5px -5px 15px rgba(255,255,255,0.5);
width: 160px;
z-index: 999;
transition: all 0.3s ease;
`
// 添加标题
const title = document.createElement("div")
title.textContent = "✨ 快捷评论"
title.style.cssText = `
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
text-align: center;
color: #2c3e50;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
padding-bottom: 10px;
border-bottom: 2px solid #b8d4e8;
`
panel.appendChild(title)
// 添加快捷评论按钮
quickComments.forEach(comment => {
const button = document.createElement("button")
button.textContent = comment
button.style.cssText = `
display: block;
width: 100%;
margin: 8px 0;
padding: 8px 12px;
border: none;
border-radius: 8px;
background: linear-gradient(145deg, #f0f9ff, #d8e9f3);
box-shadow: 3px 3px 6px rgba(0,0,0,0.1),
-3px -3px 6px rgba(255,255,255,0.8);
cursor: pointer;
font-size: 13px;
color: #444;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
`
button.addEventListener("mouseover", () => {
button.style.cssText += `
transform: translateY(-2px);
background: linear-gradient(145deg, #e6e6e6, #ffffff);
color: #2980b9;
box-shadow: 4px 4px 8px rgba(0,0,0,0.15),
-4px -4px 8px rgba(255,255,255,0.9);
`
})
button.addEventListener("mouseout", () => {
button.style.cssText = button.getAttribute("style").replace(/transform:[^;]+;/, "")
button.style.background = "linear-gradient(145deg, #f0f9ff, #d8e9f3)"
button.style.color = "#444"
})
button.addEventListener("mousedown", () => {
button.style.cssText += `
transform: scale(0.95);
box-shadow: 2px 2px 4px rgba(0,0,0,0.1),
-2px -2px 4px rgba(255,255,255,0.8);
`
})
button.addEventListener("mouseup", () => {
button.style.transform = "scale(1)"
})
button.addEventListener("click", () => {
const replyButton = document.querySelector(".reply.create")
if (replyButton) {
insertComment(comment)
}
})
panel.appendChild(button)
})
// 添加面板hover效果
panel.addEventListener("mouseover", () => {
panel.style.transform = "translateY(-50%) scale(1.02)"
panel.style.boxShadow = "6px 6px 20px rgba(0,0,0,0.15), -6px -6px 20px rgba(255,255,255,0.6)"
})
panel.addEventListener("mouseout", () => {
panel.style.transform = "translateY(-50%) scale(1)"
panel.style.boxShadow = "5px 5px 15px rgba(0,0,0,0.1), -5px -5px 15px rgba(255,255,255,0.8)"
})
return panel
}
// 插入评论到输入框
function insertComment(comment) {
const replyButton = document.querySelector('.reply.create[aria-label*="#1"]')
if (replyButton) {
replyButton.click()
setTimeout(() => {
const editor = document.querySelector(".d-editor-textarea-wrapper textarea")
if (editor) {
editor.value = comment
editor.dispatchEvent(new Event("input", { bubbles: true }))
editor.dispatchEvent(new Event("change", { bubbles: true }))
editor.focus()
}
}, 500)
}
}
// =============== 通用功能 ===============
// 检查是否为帖子页面
function isPostPage() {
const hasCommentFeature = document.querySelector(".timeline-controls")
return !!hasCommentFeature
}
// 处理页面内容
function processContent() {
if (!isPostPage()) {
return
}
// 处理Clash导入按钮
const links = document.querySelectorAll("a")
links.forEach(addImportButton)
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false)
while (walker.nextNode()) {
processTextNode(walker.currentNode)
}
// 清理可能存在的重复按钮
cleanupDuplicateButtons()
// 处理快捷评论面板
const hasPanel = document.querySelector(".quick-comment-panel")
if (!hasPanel) {
const panel = createQuickCommentPanel()
panel.classList.add("quick-comment-panel")
document.body.appendChild(panel)
}
}
// 初始化函数
function init() {
// 初始化时清空已处理URL集合
processedUrls.clear()
// 初始处理
processContent()
// 监听URL变化
let lastUrl = location.href
new MutationObserver(() => {
const url = location.href
if (url !== lastUrl) {
lastUrl = url
// URL变化时清空已处理URL集合
processedUrls.clear()
// 移除旧的评论面板
const oldPanel = document.querySelector(".quick-comment-panel")
if (oldPanel) {
oldPanel.remove()
}
// 延迟一段时间等待页面加载完成
setTimeout(() => {
// 检查是否为帖子页面
if (!isPostPage()) {
// 不是帖子页面时移除评论面板
const panel = document.querySelector(".quick-comment-panel")
if (panel) {
panel.remove()
}
return
}
processContent()
}, 500)
}
}).observe(document.body, { subtree: true, childList: true })
// 监听DOM变化
const observer = new MutationObserver(() => {
// 检查是否为帖子页面
if (!isPostPage()) {
// 不是帖子页面时移除评论面板
const panel = document.querySelector(".quick-comment-panel")
if (panel) {
panel.remove()
}
return
}
processContent()
})
observer.observe(document.body, {
childList: true,
subtree: true
})
}
// 页面加载完成后初始化
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init)
} else {
init()
}
})()