// ==UserScript==
// @name Linux.do 浏览助手
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 都是一些简简单单的功能
// @author LiNFERS
// @match https://linux.do/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// 工具函数:设置样式
function setElementStyle(element, styles) {
for (const [key, value] of Object.entries(styles)) {
element.style[key] = value;
}
}
// 创建浏览记录按钮
const btn = document.createElement('button');
btn.innerHTML = '最近浏览';
setElementStyle(btn, {
position: 'fixed',
bottom: '140px',
right: '20px',
zIndex: '9999',
padding: '8px 15px',
background: 'rgba(255, 255, 255, 0.6)',
color: '#333',
border: 'none',
borderRadius: '20px',
cursor: 'pointer',
backdropFilter: 'blur(5px)',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
transition: 'all 0.3s ease',
width: '90px',
});
document.body.appendChild(btn);
// 创建随机看帖按钮
const randomBtn = document.createElement('button');
randomBtn.innerHTML = '随机看帖';
setElementStyle(randomBtn, {
position: 'fixed',
bottom: '80px',
right: '20px',
zIndex: '9999',
padding: '8px 15px',
background: 'rgba(255, 255, 255, 0.6)',
color: '#333',
border: 'none',
borderRadius: '20px',
cursor: 'pointer',
backdropFilter: 'blur(5px)',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
transition: 'all 0.3s ease',
width: '90px',
});
document.body.appendChild(randomBtn);
// 创建插眼按钮
const markBtn = document.createElement('button');
markBtn.innerHTML = '插眼';
setElementStyle(markBtn, {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: '9999',
padding: '8px 15px',
background: 'rgba(255, 255, 255, 0.6)',
color: '#333',
border: 'none',
borderRadius: '20px',
cursor: 'pointer',
backdropFilter: 'blur(5px)',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
transition: 'all 0.3s ease',
width: '90px',
});
document.body.appendChild(markBtn);
// 创建返回按钮
const backBtn = document.createElement('button');
backBtn.innerHTML = '←';
setElementStyle(backBtn, {
position: 'fixed',
bottom: '140px',
right: '130px',
zIndex: '9999',
width: '35px',
height: '35px',
background: 'rgba(255, 255, 255, 0.6)',
color: '#333',
border: 'none',
borderRadius: '50%',
cursor: 'pointer',
backdropFilter: 'blur(5px)',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
transition: 'all 0.3s ease',
fontSize: '16px',
});
document.body.appendChild(backBtn);
// 创建回到顶部按钮
const topBtn = document.createElement('button');
topBtn.innerHTML = '↑';
setElementStyle(topBtn, {
position: 'fixed',
bottom: '80px',
right: '130px',
zIndex: '9999',
width: '35px',
height: '35px',
background: 'rgba(255, 255, 255, 0.6)',
color: '#333',
border: 'none',
borderRadius: '50%',
cursor: 'pointer',
backdropFilter: 'blur(5px)',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
transition: 'all 0.3s ease',
fontSize: '16px',
display: 'block', // 始终显示
});
document.body.appendChild(topBtn);
// 创建到达底部按钮
const bottomBtn = document.createElement('button');
bottomBtn.innerHTML = '↓';
setElementStyle(bottomBtn, {
position: 'fixed',
bottom: '20px',
right: '130px',
zIndex: '9999',
width: '35px',
height: '35px',
background: 'rgba(255, 255, 255, 0.6)',
color: '#333',
border: 'none',
borderRadius: '50%',
cursor: 'pointer',
backdropFilter: 'blur(5px)',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
transition: 'all 0.3s ease',
fontSize: '16px',
});
document.body.appendChild(bottomBtn);
// 按钮透明度控制
const buttons = [btn, randomBtn, markBtn, backBtn, topBtn, bottomBtn];
let fadeTimeout;
function fadeButtons() {
buttons.forEach(button => {
button.style.opacity = '0.3';
});
}
function resetButtons() {
buttons.forEach(button => {
button.style.opacity = '1';
});
if (fadeTimeout) {
clearTimeout(fadeTimeout);
}
fadeTimeout = setTimeout(fadeButtons, 3000);
}
// 监听鼠标移动
document.addEventListener('mousemove', (e) => {
// 检查鼠标是否在任何按钮附近
const isNearButtons = buttons.some(button => {
const rect = button.getBoundingClientRect();
const buffer = 50; // 扩大检测范围
return e.clientX >= rect.left - buffer &&
e.clientX <= rect.right + buffer &&
e.clientY >= rect.top - buffer &&
e.clientY <= rect.bottom + buffer;
});
if (isNearButtons) {
resetButtons();
}
});
// 初始设置淡出定时器
resetButtons();
// 插眼功能
markBtn.onclick = async () => {
const path = window.location.pathname;
const postId = path.match(/\/t\/topic\/(\d+)/)?.[1] || path.match(/\/topic\/(\d+)/)?.[1];
if (!postId) {
alert('请在帖子页面使用此功能');
return;
}
try {
// 获取 CSRF token
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
if (!csrfToken) {
throw new Error('无法获取 CSRF token');
}
// 发送回复请求
const response = await fetch('https://linux.do/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify({
raw: 'LiNFERSmark~~~',
topic_id: postId,
}),
credentials: 'include'
});
if (!response.ok) {
throw new Error('回复失败');
}
alert('插眼成功!');
// 刷新页面以显示新回复
window.location.reload();
} catch (error) {
alert('插眼失败:' + error.message);
}
};
// 随机看帖功能
randomBtn.onclick = async () => {
// 获取最新帖子列表
const response = await fetch('https://linux.do/latest.json');
const data = await response.json();
if (data.topic_list && data.topic_list.topics.length > 0) {
// 从帖子列表中随机选择一个
const topics = data.topic_list.topics;
const randomIndex = Math.floor(Math.random() * topics.length);
const randomTopic = topics[randomIndex];
// 导航到随机选择的帖子
window.location.href = `https://linux.do/t/topic/${randomTopic.id}`;
}
};
// 回到顶部功能
topBtn.onclick = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
// 到达底部功能
bottomBtn.onclick = () => {
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior: 'smooth'
});
};
// 返回功能
backBtn.onclick = () => {
history.back();
};
// 创建弹窗
const popup = document.createElement('div');
setElementStyle(popup, {
display: 'none',
position: 'fixed',
bottom: '80px',
right: '220px',
width: '300px',
maxHeight: '400px',
background: 'rgba(255, 255, 255, 0.95)',
borderRadius: '15px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
zIndex: '9999',
overflowY: 'auto',
padding: '10px',
backdropFilter: 'blur(10px)',
opacity: '0',
transform: 'translateX(20px)',
transition: 'opacity 0.3s ease, transform 0.3s ease',
});
document.body.appendChild(popup);
let lastRecordedId = ''; // 记录最后一次记录的帖子 ID
function recordVisit() {
setTimeout(() => {
const path = window.location.pathname;
const postId = path.match(/\/t\/topic\/(\d+)/)?.[1] || path.match(/\/topic\/(\d+)/)?.[1];
if (!postId) return;
const title = document.querySelector('h1')?.textContent || document.title;
const url = window.location.href;
if (postId === lastRecordedId) return; // 避免重复记录
lastRecordedId = postId;
let history = GM_getValue('postHistory', []);
// 检查是否存在相同帖子 ID
const existingIndex = history.findIndex(item => item.id === postId);
if (existingIndex !== -1) {
history.splice(existingIndex, 1); // 删除重复记录
}
history.unshift({
id: postId,
title: title,
url: url,
time: new Date().toISOString(),
});
const maxRecords = Math.min(GM_getValue('maxRecords', 10), 20);
if (history.length > maxRecords) {
history = history.slice(0, maxRecords);
}
GM_setValue('postHistory', history);
}, 1000);
}
function showHistory() {
const history = GM_getValue('postHistory', []);
popup.innerHTML = '';
// 设置区域
const settingsDiv = document.createElement('div');
setElementStyle(settingsDiv, {
marginBottom: '10px',
padding: '5px',
borderBottom: '1px solid #eee',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
});
const settingsLabel = document.createElement('label');
settingsLabel.textContent = '记录条数: ';
const settingsInput = document.createElement('input');
settingsInput.type = 'number';
settingsInput.min = 1;
settingsInput.max = 20;
settingsInput.value = GM_getValue('maxRecords', 10);
setElementStyle(settingsInput, {
width: '60px',
margin: '0 5px',
padding: '3px',
borderRadius: '4px',
border: '1px solid #ddd',
});
settingsInput.onchange = (e) => {
let value = parseInt(e.target.value, 10);
value = Math.min(20, Math.max(1, value));
e.target.value = value;
GM_setValue('maxRecords', value);
};
const clearBtn = document.createElement('button');
clearBtn.textContent = '清空记录';
setElementStyle(clearBtn, {
padding: '5px 10px',
background: 'rgba(255, 68, 68, 0.9)',
color: 'white',
border: 'none',
borderRadius: '15px',
cursor: 'pointer',
transition: 'all 0.3s ease',
});
clearBtn.onclick = () => {
if (confirm('确定要清空所有浏览记录吗?')) {
GM_setValue('postHistory', []);
showHistory();
}
};
settingsDiv.appendChild(settingsLabel);
settingsDiv.appendChild(settingsInput);
settingsDiv.appendChild(clearBtn);
popup.appendChild(settingsDiv);
if (history.length === 0) {
popup.innerHTML += '
暂无浏览记录
';
return;
}
// 展示记录
const ul = document.createElement('ul');
setElementStyle(ul, {
listStyle: 'none',
margin: '0',
padding: '0',
});
history.forEach((item, index) => {
const li = document.createElement('li');
setElementStyle(li, {
padding: '10px',
borderBottom: '1px solid #eee',
cursor: 'pointer',
transition: 'all 0.3s ease',
borderRadius: '8px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
});
const time = new Date(item.time).toLocaleString();
const titleDiv = document.createElement('div');
titleDiv.innerHTML = `
${item.title}
${time}
`;
titleDiv.style.flex = '1';
titleDiv.onclick = () => {
window.location.href = item.url;
};
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
setElementStyle(deleteBtn, {
padding: '5px 10px',
background: 'rgba(255, 68, 68, 0.9)',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
marginLeft: '10px',
});
deleteBtn.onclick = (e) => {
e.stopPropagation(); // 防止触发跳转
if (confirm(`确定删除记录 "${item.title}" 吗?`)) {
history.splice(index, 1);
GM_setValue('postHistory', history);
showHistory();
}
};
li.appendChild(titleDiv);
li.appendChild(deleteBtn);
ul.appendChild(li);
});
popup.appendChild(ul);
}
// 弹窗显示控制
let isPopupVisible = false;
btn.onclick = () => {
isPopupVisible = !isPopupVisible;
popup.style.display = isPopupVisible ? 'block' : 'none';
if (isPopupVisible) {
showHistory();
// 添加一个小延迟以确保过渡动画生效
setTimeout(() => {
popup.style.opacity = '1';
popup.style.transform = 'translateX(0)';
}, 10);
} else {
popup.style.opacity = '0';
popup.style.transform = 'translateX(20px)';
// 等待过渡动画完成后隐藏弹窗
setTimeout(() => {
if (!isPopupVisible) {
popup.style.display = 'none';
}
}, 300);
}
};
// 点击外部关闭弹窗
document.addEventListener('click', (e) => {
if (!popup.contains(e.target) && e.target !== btn) {
if (isPopupVisible) {
isPopupVisible = false;
popup.style.opacity = '0';
popup.style.transform = 'translateX(20px)';
setTimeout(() => {
if (!isPopupVisible) {
popup.style.display = 'none';
}
}, 300);
}
}
});
// 初始记录
recordVisit();
// 监听 URL 变化
let lastUrl = location.href;
new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
recordVisit();
}
}).observe(document.body, { childList: true, subtree: true });
})();