Linux do Level Enhanced

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.

当前为 2024-03-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Linux do Level Enhanced
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.0.7
  5. // @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.
  6. // @author Hua, Reno
  7. // @match https://linux.do/*
  8. // @icon https://www.google.com/s2/favicons?domain=linux.do
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const StyleManager = {
  17. styles: `
  18. @keyframes breathAnimation {
  19. 0%, 100% { transform: scale(1); box-shadow: 0 0 5px rgba(0,0,0,0.5); }
  20. 50% { transform: scale(1.1); box-shadow: 0 0 10px rgba(0,0,0,0.7); }
  21. }
  22. .breath-animation { animation: breathAnimation 4s ease-in-out infinite; }
  23. .minimized { border-radius: 50%; cursor: pointer; }
  24. .linuxDoLevelPopup { position: fixed; width: 250px; height: 150px; box-shadow: 0 0 10px rgba(0,0,0,0.5); padding: 15px; z-index: 10000; font-size: 14px; border-radius: 5px; cursor: move; }
  25. .linuxDoLevelPopup input, .linuxDoLevelPopup button { width: 100%; margin-top: 10px; }
  26. .linuxDoLevelPopup button { cursor: pointer; }
  27. .minimizeButton { position: absolute; top: 5px; right: 5px; background: transparent; border: none; cursor: pointer; width: 30px; height: 30px; font-size: 16px; }
  28. .searchButton { width: 100%; marginTop: 10px }
  29. .searchBox { width: 100%; marginTop: 10px }
  30. `,
  31.  
  32. injectStyles: function() {
  33. const styleSheet = document.createElement('style');
  34. styleSheet.type = 'text/css';
  35. styleSheet.innerText = this.styles;
  36. document.head.appendChild(styleSheet);
  37. }
  38. };
  39.  
  40. const DataManager = {
  41. ABOUT_DATA_URL: 'https://linux.do/about.json',
  42. USER_DATA_URL_PREFIX: 'https://linux.do/u/',
  43. USER_DATA_URL_SUFFIX: '/summary.json',
  44. FETCH_REQUEST_OPTIONS: {
  45. headers: { "Accept": "application/json", "User-Agent": "Mozilla/5.0" },
  46. method: "GET",
  47. },
  48. levelRequirements: {
  49. 0: { 'topics_entered': 5, 'posts_read_count': 30, 'time_read': 600 },
  50. 1: { 'days_visited': 15, 'likes_given': 1, 'likes_received': 1, 'post_count': 3, 'topics_entered': 20, 'posts_read_count': 100, 'time_read': 3600 },
  51. 2: { 'days_visited': 50, 'likes_given': 30, 'likes_received': 20, 'post_count': 10 }
  52. },
  53. levelDescriptions: {
  54. 0: "游客", 1: "基本用户", 2: "成员", 3: "活跃用户", 4: "领导者"
  55. },
  56.  
  57. fetchAboutData: async function() {
  58. try {
  59. const response = await fetch(this.ABOUT_DATA_URL, this.FETCH_REQUEST_OPTIONS);
  60. if (!response.ok) throw new Error(`HTTP 错误!状态:${response.status}`);
  61. return await response.json();
  62. } catch (error) {
  63. UIManager.displayError("获取关于页面数据失败");
  64. throw error;
  65. }
  66. },
  67.  
  68. fetchUserData: async function(username) {
  69. try {
  70. const url = `${this.USER_DATA_URL_PREFIX}${username}${this.USER_DATA_URL_SUFFIX}`;
  71. const response = await fetch(url, this.FETCH_REQUEST_OPTIONS);
  72. if (!response.ok) throw new Error(`HTTP 错误!状态:${response.status}`);
  73. return await response.json();
  74. } catch (error) {
  75. UIManager.displayError("获取用户数据失败");
  76. throw error;
  77. }
  78. }
  79. };
  80.  
  81. const UIManager = {
  82. initPopup: function() {
  83. this.popup = this.createElement('div', { id: 'linuxDoLevelPopup', class: 'linuxDoLevelPopup' });
  84. this.content = this.createElement('div', { id: 'linuxDoLevelPopupContent' }, '欢迎使用 Linux do 等级增强插件');
  85. this.searchBox = this.createElement('input', { placeholder: '请输入用户名...', type: 'text', class: 'searchBox' });
  86. this.searchButton = this.createElement('button', { class: 'searchButton' }, '搜索');
  87. this.minimizeButton = this.createElement('button', { }, '隐藏');
  88. this.popup.style.bottom = '20px'; // 示例:距离顶部20px
  89. this.popup.style.right = '20px'; // 示例:距离左侧20px
  90. this.popup.style.width = '250px'; // 初始化宽度
  91. this.popup.style.height = 'auto'; // 高度自适应内容
  92. this.searchButton.classList.add('btn', 'btn-icon-text', 'btn-default')
  93. this.minimizeButton.classList.add('btn', 'btn-icon-text', 'btn-default')
  94.  
  95. this.popup.append(this.content, this.searchBox, this.searchButton, this.minimizeButton);
  96. document.body.appendChild(this.popup);
  97.  
  98. this.minimizeButton.addEventListener('click', () => this.togglePopupSize());
  99. this.searchButton.addEventListener('click', () => EventHandler.handleSearch());
  100. // 添加输入框的回车键事件监听器
  101. this.searchBox.addEventListener('keypress', (event) => {
  102. // 检查是否按下了回车键并且弹窗不处于最小化状态
  103. if (event.key === 'Enter' && !this.popup.classList.contains('minimized')) {
  104. EventHandler.handleSearch();
  105. }
  106. });
  107. },
  108.  
  109. createElement: function(tag, attributes, text) {
  110. const element = document.createElement(tag);
  111. for (const attr in attributes) {
  112. if (attr === 'class') {
  113. element.classList.add(attributes[attr]);
  114. } else {
  115. element.setAttribute(attr, attributes[attr]);
  116. }
  117. }
  118. if (text) element.textContent = text;
  119. return element;
  120. },
  121.  
  122. updatePopupContent: function(userSummary, user, status) {
  123. if (!userSummary || !user) return;
  124.  
  125. let content = `<strong>信任等级:</strong>${DataManager.levelDescriptions[user.trust_level]}<br><strong>升级进度:</strong><br>`;
  126. const requirements = DataManager.levelRequirements[user.trust_level] || {};
  127.  
  128. if (user.trust_level === 2) {
  129. requirements['posts_read_count'] = Math.min(parseInt(parseInt(status.posts_30_days) / 4), 20000);
  130. requirements['topics_entered'] = Math.min(parseInt(parseInt(status.topics_30_days) / 4), 500);
  131. }
  132.  
  133. if (user.trust_level === 3) {
  134. content += '联系管理员以升级到领导者<br>';
  135. } else if (user.trust_level === 4) {
  136. content += '您已是最高信任等级<br>';
  137. } else {
  138. console.log(requirements)
  139. for (const [stat, required] of Object.entries(requirements)) {
  140. console.log(stat)
  141. console.log(required)
  142. const current = userSummary[stat] || 0;
  143. console.log(current)
  144. console.log(userSummary[stat])
  145. console.log(this.translateStat(stat))
  146. const color = current >= required ? "green" : "red";
  147. content += `${this.translateStat(stat)}: <span style="color: ${color};">${current} / ${required}</span><br>`;
  148. }
  149. }
  150. this.content.innerHTML = content;
  151. },
  152.  
  153. togglePopupSize: function() {
  154. if (this.popup.classList.contains('minimized')) {
  155. this.popup.classList.remove('minimized');
  156. this.popup.style.width = '250px';
  157. this.popup.style.height = 'auto';
  158. this.content.style.display = 'block';
  159. this.searchBox.style.display = 'block';
  160. this.searchButton.style.display = 'block';
  161. this.minimizeButton.textContent = '隐藏';
  162. this.popup.classList.remove('breath-animation');
  163. } else {
  164. this.popup.classList.add('minimized');
  165. this.popup.style.width = '50px';
  166. this.popup.style.height = '50px';
  167. this.content.style.display = 'none';
  168. this.searchBox.style.display = 'none';
  169. this.searchButton.style.display = 'none';
  170. this.minimizeButton.textContent = '展开';
  171. this.popup.classList.add('breath-animation');
  172. }
  173. // 确保拖动时更新整个弹窗的位置而不仅是底部
  174. addDraggableFeature(this.popup);
  175. },
  176.  
  177. displayError: function(message) {
  178. this.content.innerHTML = `<strong>错误:</strong>${message}`;
  179. },
  180.  
  181. translateStat: function(stat) {
  182. const translations = {
  183. 'days_visited': '访问天数',
  184. 'likes_given': '给出的赞',
  185. 'likes_received': '收到的赞',
  186. 'post_count': '帖子数量',
  187. 'posts_read_count': '阅读的帖子数',
  188. 'topics_entered': '进入的主题数',
  189. 'time_read': '阅读时间(秒)'
  190. };
  191. return translations[stat] || stat;
  192. }
  193. };
  194.  
  195. const EventHandler = {
  196. handleSearch: async function() {
  197. const username = UIManager.searchBox.value.trim();
  198. if (!username) return;
  199.  
  200. try {
  201. const aboutData = await DataManager.fetchAboutData();
  202. const userData = await DataManager.fetchUserData(username);
  203. if (userData && aboutData) {
  204. UIManager.updatePopupContent(userData.user_summary, userData.users[0], aboutData.about.stats);
  205. }
  206. } catch (error) {
  207. console.error(error); // Already handled within data manager functions
  208. }
  209. }
  210. };
  211.  
  212. // 添加拖动功能
  213. function addDraggableFeature(element) {
  214. let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  215.  
  216. const dragMouseDown = function(e) {
  217. // 检查事件的目标是否是输入框,按钮或其他可以忽略拖动逻辑的元素
  218. if (e.target.tagName.toUpperCase() === 'INPUT' || e.target.tagName.toUpperCase() === 'TEXTAREA' || e.target.tagName.toUpperCase() === 'BUTTON') {
  219. return; // 如果是,则不执行拖动逻辑
  220. }
  221.  
  222. e = e || window.event;
  223. e.preventDefault();
  224. pos3 = e.clientX;
  225. pos4 = e.clientY;
  226. document.onmouseup = closeDragElement;
  227. document.onmousemove = elementDrag;
  228. };
  229.  
  230. const elementDrag = function(e) {
  231. e = e || window.event;
  232. e.preventDefault();
  233. pos1 = pos3 - e.clientX;
  234. pos2 = pos4 - e.clientY;
  235. pos3 = e.clientX;
  236. pos4 = e.clientY;
  237.  
  238. element.style.top = (element.offsetTop - pos2) + "px";
  239. element.style.left = (element.offsetLeft - pos1) + "px";
  240. // 为了避免与拖动冲突,在此移除bottom和right样式
  241. element.style.bottom = '';
  242. element.style.right = '';
  243. };
  244.  
  245. const closeDragElement = function() {
  246. document.onmouseup = null;
  247. document.onmousemove = null;
  248. };
  249.  
  250. element.onmousedown = dragMouseDown;
  251. }
  252.  
  253. const init = () => {
  254. StyleManager.injectStyles();
  255. UIManager.initPopup();
  256. addDraggableFeature(document.getElementById('linuxDoLevelPopup')); // 确保已设置该ID
  257. UIManager.togglePopupSize(); // 初始最小化
  258. };
  259.  
  260. init();
  261.  
  262. })();