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.4
  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. injectStyles: function() {
  18. const styleSheet = document.createElement('style');
  19. styleSheet.type = 'text/css';
  20. styleSheet.innerText = `
  21. @keyframes breathAnimation {
  22. 0%, 100% {
  23. transform: scale(1);
  24. box-shadow: 0 0 5px rgba(0,0,0,0.5);
  25. }
  26. 50% {
  27. transform: scale(1.1);
  28. box-shadow: 0 0 10px rgba(0,0,0,0.7);
  29. }
  30. }
  31. .breath-animation {
  32. animation: breathAnimation 4s ease-in-out infinite;
  33. }
  34. .minimized {
  35. width: 50px !important;
  36. height: 50px !important;
  37. border-radius: 50% !important;
  38. padding: 0 !important;
  39. overflow: hidden;
  40. cursor: pointer;
  41. }
  42. .button:hover {
  43. background-color: #f0f0f0;
  44. }
  45. `;
  46. document.head.appendChild(styleSheet);
  47. }
  48. };
  49.  
  50. const DataManager = {
  51. ABOUT_DATA_URL: `https://linux.do/about.json`,
  52. USER_DATA_URL_PREFIX: `https://linux.do/u/`,
  53. USER_DATA_URL_SUFFIX: `/summary.json`,
  54. FETCH_REQUEST_OPTIONS: {
  55. headers: {
  56. "Accept": "application/json",
  57. "User-Agent": "Mozilla/5.0"
  58. },
  59. method: "GET",
  60. },
  61. levelRequirements: {
  62. 0: { 'topics_entered': 5, 'posts_read_count': 30, 'time_read': 600 },
  63. 1: { 'days_visited': 15, 'likes_given': 1, 'likes_received': 1, 'post_count': 3, 'topics_entered': 20, 'posts_read_count': 100, 'time_read': 3600 },
  64. 2: { 'days_visited': 50, 'likes_given': 30, 'likes_received': 20, 'post_count': 10, 'topics_entered': 0, 'posts_read_count': 0 }
  65. },
  66. levelDescriptions: {
  67. 0: "游客", 1: "基本用户", 2: "成员", 3: "活跃用户", 4: "领导者"
  68. },
  69.  
  70. async fetchAboutData() {
  71. try {
  72. const response = await fetch(this.ABOUT_DATA_URL, this.FETCH_REQUEST_OPTIONS);
  73. if (!response.ok) throw new Error(`HTTP 错误!状态:${response.status}`);
  74. return await response.json();
  75. } catch (error) {
  76. console.error("获取关于页面数据失败:", error);
  77. UIManager.displayError("获取关于页面数据失败");
  78. return null;
  79. }
  80. },
  81.  
  82. async fetchUserData(username) {
  83. try {
  84. const response = await fetch(`${this.USER_DATA_URL_PREFIX}${username}${this.USER_DATA_URL_SUFFIX}`, this.FETCH_REQUEST_OPTIONS);
  85. if (!response.ok) throw new Error(`HTTP 错误!状态:${response.status}`);
  86. return await response.json();
  87. } catch (error) {
  88. console.error("获取用户数据失败:", error);
  89. UIManager.displayError("获取用户数据失败");
  90. return null;
  91. }
  92. }
  93. };
  94.  
  95. const UIManager = {
  96. popup: null,
  97. content: null,
  98. searchBox: null,
  99. searchButton: null,
  100. minimizeButton: null,
  101.  
  102. createPopup: function() {
  103. this.popup = document.createElement('div');
  104. this.popup.classList.add('linuxDoLevelPopup');
  105. this.popup.id = 'linuxDoLevelPopup';
  106. this.setStyle(this.popup, {
  107. position: 'fixed',
  108. bottom: '20px',
  109. right: '20px',
  110. width: '250px',
  111. height: 'auto',
  112. backgroundColor: 'white',
  113. boxShadow: '0 0 10px rgba(0,0,0,0.5)',
  114. padding: '15px',
  115. zIndex: '10000',
  116. fontSize: '14px',
  117. borderRadius: '5px',
  118. });
  119.  
  120. this.content = document.createElement('div');
  121. this.content.id = 'linuxDoLevelPopupContent';
  122. this.content.innerHTML = '欢迎使用 Linux do 等级增强插件';
  123. this.popup.appendChild(this.content);
  124.  
  125. this.searchBox = document.createElement('input');
  126. this.searchBox.type = 'text';
  127. this.searchBox.placeholder = '请输入用户名...';
  128. this.setStyle(this.searchBox, { width: '100%', marginTop: '10px' });
  129. this.searchBox.id = 'linuxDoUserSearch';
  130. this.popup.appendChild(this.searchBox);
  131.  
  132. this.searchButton = document.createElement('button');
  133. this.searchButton.textContent = '搜索';
  134. this.searchButton.classList.add('button');
  135. this.setStyle(this.searchButton, { width: '100%', marginTop: '10px' });
  136. this.popup.appendChild(this.searchButton);
  137.  
  138. this.minimizeButton = document.createElement('button');
  139. this.minimizeButton.textContent = '隐藏';
  140. this.minimizeButton.classList.add('button');
  141. this.setStyle(this.minimizeButton, {
  142. position: 'absolute',
  143. top: '5px',
  144. right: '5px',
  145. zIndex: '10001',
  146. background: 'transparent',
  147. border: 'none',
  148. cursor: 'pointer',
  149. borderRadius: '50%',
  150. textAlign: 'center',
  151. lineHeight: '40px',
  152. width: '40px',
  153. height: '40px',
  154. });
  155. this.popup.appendChild(this.minimizeButton);
  156.  
  157. document.body.appendChild(this.popup);
  158. },
  159.  
  160. updatePopupContent: function(userSummary, user, status) {
  161. if (this.content && userSummary && user) {
  162. let content = `<strong>信任等级:</strong>${DataManager.levelDescriptions[user.trust_level]}<br><strong>升级进度:</strong><br>`;
  163.  
  164. if (user.trust_level === 3) {
  165. content += `联系管理员以升级到领导者<br>`;
  166. } else if (user.trust_level === 4) {
  167. content += `您已是最高信任等级<br>`;
  168. } else {
  169. const requirements = DataManager.levelRequirements[user.trust_level];
  170. if (user.trust_level === 2) {
  171. requirements['posts_read_count'] = Math.min(Math.floor(status.posts_30_days / 4), 20000);
  172. requirements['topics_entered'] = Math.min(Math.floor(status.topics_30_days / 4), 500);
  173. }
  174.  
  175. Object.entries(requirements).forEach(([key, val]) => {
  176. const currentVal = userSummary[key] || 0;
  177. const color = currentVal >= val ? "green" : "red";
  178. content += `${this.translateStat(key)}: <span style="color: ${color};">${currentVal} / ${val}</span><br>`;
  179. });
  180. }
  181.  
  182. this.content.innerHTML = content;
  183. }
  184. },
  185.  
  186. togglePopupSize: function() {
  187. if (this.popup.classList.contains('minimized')) {
  188. // Maximizing
  189. this.popup.classList.remove('minimized');
  190. this.popup.classList.remove('breath-animation');
  191. this.popup.style.width = '250px';
  192. this.popup.style.height = 'auto';
  193. this.content.style.display = 'block';
  194. this.searchBox.style.display = 'block';
  195. this.searchButton.style.display = 'block';
  196. this.minimizeButton.textContent = '隐藏';
  197. } else {
  198. // Minimizing
  199. this.popup.classList.add('minimized');
  200. this.popup.classList.add('breath-animation');
  201. this.popup.style.width = '50px';
  202. this.popup.style.height = '50px';
  203. this.content.style.display = 'none';
  204. this.searchBox.style.display = 'none';
  205. this.searchButton.style.display = 'none';
  206. this.minimizeButton.textContent = '';
  207. }
  208. },
  209.  
  210. displayError: function(message) {
  211. if (this.content) {
  212. this.content.innerHTML = `<strong>错误:</strong>${message}`;
  213. }
  214. },
  215.  
  216. setStyle: function(element, styles) {
  217. Object.assign(element.style, styles);
  218. },
  219.  
  220. translateStat: function(stat) {
  221. const translations = {
  222. 'days_visited': '访问天数',
  223. 'likes_given': '给出的赞',
  224. 'likes_received': '收到的赞',
  225. 'post_count': '帖子数量',
  226. 'posts_read_count': '阅读的帖子数',
  227. 'topics_entered': '进入的主题数',
  228. 'time_read': '阅读时间'
  229. };
  230.  
  231. return translations[stat] || stat;
  232. }
  233. };
  234.  
  235. const EventHandler = {
  236. handleSearch: async function() {
  237. const username = UIManager.searchBox.value.trim();
  238. if (username) {
  239. const aboutData = await DataManager.fetchAboutData();
  240. const userData = await DataManager.fetchUserData(username);
  241. if (userData && aboutData) {
  242. const userSummary = userData.user_summary;
  243. const user = userData.users[0];
  244. const status = aboutData.about.stats;
  245. UIManager.updatePopupContent(userSummary, user, status);
  246. }
  247. }
  248. },
  249.  
  250. initEventListeners: function() {
  251. UIManager.searchButton.addEventListener('click', this.handleSearch.bind(this));
  252. UIManager.minimizeButton.addEventListener('click', UIManager.togglePopupSize.bind(UIManager));
  253. }
  254. };
  255.  
  256. const LevelEnhancement = {
  257. init: function() {
  258. StyleManager.injectStyles();
  259. UIManager.createPopup();
  260. EventHandler.initEventListeners();
  261. }
  262. };
  263.  
  264. LevelEnhancement.init();
  265. })();