// ==UserScript==
// @name Linux do Level Enhanced
// @namespace http://tampermonkey.net/
// @version 1.0.4
// @description Enhanced script to track progress towards next trust level on linux.do with added search functionality, adjusted posts read limit, and a breathing icon animation.
// @author Hua, Reno, NullUser
// @match https://linux.do/*
// @icon https://www.google.com/s2/favicons?domain=linux.do
// @grant none
// @license MIT
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
const StyleManager = {
styles: `
@keyframes breathAnimation {
0%, 100% { transform: scale(1); box-shadow: 0 0 5px rgba(0,0,0,0.5); }
50% { transform: scale(1.1); box-shadow: 0 0 10px rgba(0,0,0,0.7); }
}
.breath-animation { animation: breathAnimation 4s ease-in-out infinite; }
.minimized { border-radius: 50%; cursor: pointer; }
.linuxDoLevelPopup { position: fixed; width: 250px; height: 150px; background: var(--d-sidebar-background); box-shadow: 0 0 10px rgba(0,0,0,0.5); padding: 15px; z-index: 10000; font-size: 14px; border-radius: 5px; cursor: move; }
.linuxDoLevelPopup input, .linuxDoLevelPopup button { width: 100%; margin-top: 10px; }
.linuxDoLevelPopup button { cursor: pointer; }
.minimizeButton { position: absolute; top: 5px; right: 5px; background: transparent; border: none; cursor: pointer; width: 30px; height: 30px; font-size: 16px; }
.searchButton { width: 100%; marginTop: 10px }
.searchBox { width: 100%; marginTop: 10px }
`,
injectStyles: function() {
const styleSheet = document.createElement('style');
styleSheet.type = 'text/css';
styleSheet.innerText = this.styles;
document.head.appendChild(styleSheet);
}
};
const DataManager = {
Config: {
BASE_URL: 'https://linux.do',
PATHS: {
ABOUT: '/about.json',
USER_SUMMARY: '/u/{username}/summary.json',
USER_DETAIL: '/u/{username}.json',
},
},
levelRequirements: {
0: { 'topics_entered': 5, 'posts_read_count': 30, 'time_read': 600 },
1: { 'days_visited': 15, 'likes_given': 1, 'likes_received': 1, 'post_count': 3, 'topics_entered': 20, 'posts_read_count': 100, 'time_read': 3600 },
2: { 'days_visited': 50, 'likes_given': 30, 'likes_received': 20, 'post_count': 10 },
},
levelDescriptions: {
0: "游客",
1: "基本用户",
2: "成员",
3: "活跃用户",
4: "领导者"
},
fetch: async function(url, options = {}) {
try {
const response = await fetch(url, {
...options,
headers: { "Accept": "application/json", "User-Agent": "Mozilla/5.0" },
method: options.method || "GET",
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} catch (error) {
console.error(`Error fetching data from ${url}:`, error);
throw error;
}
},
fetchAboutData: function() {
const url = this.buildUrl(this.Config.PATHS.ABOUT);
return this.fetch(url);
},
fetchSummaryData: function(username) {
const url = this.buildUrl(this.Config.PATHS.USER_SUMMARY, { username });
return this.fetch(url);
},
fetchUserData: function(username) {
const url = this.buildUrl(this.Config.PATHS.USER_DETAIL, { username });
return this.fetch(url);
},
buildUrl: function(path, params = {}) {
let url = this.Config.BASE_URL + path;
Object.keys(params).forEach(key => {
url = url.replace(`{${key}}`, encodeURIComponent(params[key]));
});
return url;
},
};
const UIManager = {
initPopup: function() {
this.popup = this.createElement('div', { id: 'linuxDoLevelPopup', class: 'linuxDoLevelPopup' });
this.content = this.createElement('div', { id: 'linuxDoLevelPopupContent' }, '欢迎使用 Linux do 等级增强插件');
this.searchBox = this.createElement('input', { placeholder: '请输入用户名...', type: 'text', class: 'searchBox' });
this.searchButton = this.createElement('button', { class: 'searchButton' }, '搜索');
this.minimizeButton = this.createElement('button', { }, '隐藏');
this.popup.style.bottom = '20px'; // 示例:距离顶部20px
this.popup.style.right = '20px'; // 示例:距离左侧20px
this.popup.style.width = '250px'; // 初始化宽度
this.popup.style.height = 'auto'; // 高度自适应内容
this.searchButton.classList.add('btn', 'btn-icon-text', 'btn-default')
this.minimizeButton.classList.add('btn', 'btn-icon-text', 'btn-default')
this.popup.append(this.content, this.searchBox, this.searchButton, this.minimizeButton);
document.body.appendChild(this.popup);
this.minimizeButton.addEventListener('click', () => this.togglePopupSize());
this.searchButton.addEventListener('click', () => EventHandler.handleSearch());
// 添加输入框的回车键事件监听器
this.searchBox.addEventListener('keypress', (event) => {
// 检查是否按下了回车键并且弹窗不处于最小化状态
if (event.key === 'Enter' && !this.popup.classList.contains('minimized')) {
EventHandler.handleSearch();
}
});
var checkInterval = setInterval(function() {
// 查找id为current-user的li元素
var currentUserLi = document.querySelector('#current-user');
// 如果找到了元素
if(currentUserLi) {
// 查找该元素下的button
var button = currentUserLi.querySelector('button');
// 如果找到了button元素
if(button) {
// 获取button的href属性值
var href = button.getAttribute('href');
UIManager.searchBox.value = href.replace('/u/', '');
clearInterval(checkInterval); // 停止检查
// 这里你可以根据需要对href进行进一步操作
}
}
}, 1000); // 每隔1秒检查一次
},
createElement: function(tag, attributes, text) {
const element = document.createElement(tag);
for (const attr in attributes) {
if (attr === 'class') {
element.classList.add(attributes[attr]);
} else {
element.setAttribute(attr, attributes[attr]);
}
}
if (text) element.textContent = text;
return element;
},
updatePopupContent: function(userSummary, user, userDetail, status) {
if (!userSummary || !user || !userDetail) return;
// 初始化内容字符串,并添加用户信任等级
let content = `信任等级:${DataManager.levelDescriptions[user.trust_level]}
`;
// 获取用户的信任等级要求
const requirements = DataManager.levelRequirements[user.trust_level] || {};
// 添加用户的 gamification_score
if (userDetail.gamification_score) {
content += `你的点数:${userDetail.gamification_score}
`;
}
// 添加用户的最近活跃时间
content += `最近活跃:${formatTimestamp(userDetail.last_seen_at)}
`;
// 处理2级以下用户,调用 summaryRequired 功能
if (user.trust_level <= 2) {
if (user.trust_level === 2) {
requirements['posts_read_count'] = Math.min(parseInt(parseInt(status.posts_30_days) / 4), 20000);
requirements['topics_entered'] = Math.min(parseInt(parseInt(status.topics_30_days) / 4), 500);
}
let summary = summaryRequired(requirements, userSummary, this.translateStat.bind(this));
content += summary;
} else {
// 处理2级以上用户,调用 analyzeAbility 功能
if (userSummary.top_categories) {
content += analyzeAbility(userSummary.top_categories);
}
}
// 更新弹窗内容
this.content.innerHTML = content;
},
togglePopupSize: function() {
if (this.popup.classList.contains('minimized')) {
this.popup.classList.remove('minimized');
this.popup.style.width = '250px';
this.popup.style.height = 'auto';
this.content.style.display = 'block';
this.searchBox.style.display = 'block';
this.searchButton.style.display = 'block';
this.minimizeButton.textContent = '隐藏';
this.popup.classList.remove('breath-animation');
} else {
this.popup.classList.add('minimized');
this.popup.style.width = '50px';
this.popup.style.height = '50px';
this.content.style.display = 'none';
this.searchBox.style.display = 'none';
this.searchButton.style.display = 'none';
this.popup.classList.add('breath-animation');
// 调用 updatePercentage 函数并更新按钮文本
updatePercentage().then(percentage => {
this.minimizeButton.textContent = `${percentage.toFixed(2)}%`;
}).catch(error => {
console.error('Error calculating percentage:', error);
// 出错时保持原有文本
this.minimizeButton.textContent = '展开';
});
}
// 自动校正窗口位置
addDraggableFeature(this.popup);
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const popupWidth = this.popup.offsetWidth;
const popupHeight = this.popup.offsetHeight;
const popupTop = parseInt(this.popup.style.top);
const popupLeft = parseInt(this.popup.style.left);
// 初始化新的位置
let newTop = popupTop;
let newLeft = popupLeft;
// 上下边界同时检查
newTop = Math.min(Math.max(70, popupTop), windowHeight - popupHeight);
// 左右边界同时检查
newLeft = Math.min(Math.max(5, popupLeft), windowWidth - popupWidth - 20);
this.popup.style.top = newTop + 'px';
this.popup.style.left = newLeft + 'px';
},
displayError: function(message) {
this.content.innerHTML = `错误:${message}`;
},
translateStat: function(stat) {
const translations = {
'days_visited': '访问天数',
'likes_given': '给出的赞',
'likes_received': '收到的赞',
'post_count': '帖子数量',
'posts_read_count': '已读帖子',
'topics_entered': '已读主题',
'time_read': '阅读时间(秒)'
};
return translations[stat] || stat;
}
};
const EventHandler = {
handleSearch: async function() {
const username = UIManager.searchBox.value.trim();
if (!username) return;
try {
const aboutData = await DataManager.fetchAboutData();
const summaryData = await DataManager.fetchSummaryData(username);
const userData = await DataManager.fetchUserData(username);
if (summaryData && userData && aboutData) {
UIManager.updatePopupContent(summaryData.user_summary, summaryData.users ? summaryData.users[0] : { 'trust_level': 0 }, userData.user, aboutData.about.stats);
}
} catch (error) {
console.error(error);
}
},
// 更新拖动状态
handleDragEnd: function() {
UIManager.updateDragStatus(true);
}
};
// 添加技能分析
function analyzeAbility(topCategories) {
let resultStr = "技能分析:
";
const scores = topCategories.map(category => category.topic_count + category.post_count);
const minScore = Math.min(...scores);
const maxScore = Math.max(...scores);
const scoreRange = Math.max(1, maxScore - minScore);
topCategories.sort((a, b) => a.name.length - b.name.length);
topCategories.forEach(category => {
const score = category.topic_count + category.post_count;
const normalizedScore = 1 + (score - minScore) / scoreRange * 9;
const numPoints = Math.round(normalizedScore);
let block = numPoints > 3 ? "▊" : "▊";
resultStr += `