linux.do.level

Linux.Do 查看用户信任级别以及升级条件,数据来源于 https://connect.linux.do

  1. // ==UserScript==
  2. // @name linux.do.level
  3. // @namespace https://linux.do/u/io.oi/s/level
  4. // @version 1.4.10
  5. // @author LINUX.DO
  6. // @description Linux.Do 查看用户信任级别以及升级条件,数据来源于 https://connect.linux.do
  7. // @license MIT
  8. // @icon https://linux.do/uploads/default/original/3X/9/d/9dd49731091ce8656e94433a26a3ef36062b3994.png
  9. // @match https://linux.do/*
  10. // @connect connect.linux.do
  11. // @grant GM.xmlHttpRequest
  12. // @grant GM_addStyle
  13. // ==/UserScript==
  14.  
  15. (e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const r=document.createElement("style");r.textContent=e,document.head.append(r)})(" .level-window{position:fixed;bottom:0;background:var(--secondary);z-index:999;padding:.5em;color:var(--primary);box-shadow:0 0 4px #00000020;border:1px solid var(--primary-low)}.level-window .title .close{width:24px;height:24px;color:#fff;background:red;display:inline-block;text-align:center;line-height:24px;float:right;cursor:pointer;border-radius:var(--d-button-border-radius);font-size:var(--base-font-size-largest)}.level-window .bg-white{background-color:var(--primary-low);border-radius:var(--d-button-border-radius);padding:.5em;margin-top:.5em}.level-window h1{color:var(--primary);font-size:1.3rem}.level-window h2{font-size:1.25rem}.mb-4 table tr:nth-child(2n){background-color:var(--tertiary-400)}.level-window .text-red-500{color:#ef4444}.level-window .text-green-500{color:#10b981}.level-window .mb-4 table tr td{padding:4px 8px}.language-text{background:var(--primary-very-low);font-family:var(--d-font-family--monospace);font-size:var(--base-font-size-smallest);flex-grow:1;padding:6px}.code-box{display:flex;flex-direction:row;justify-content:space-between}.code-box .copy{padding:.5em 1em;cursor:pointer;-webkit-user-select:none;user-select:none;font-size:var(--base-font-size-smallest);background:var(--secondary)}.connect-button{width:100%;padding:.5em;border-radius:var(--d-button-border-radius)!important;margin-top:.5em!important}.emoji-picker-category-buttons,.emoji-picker-emoji-area{justify-content:center;padding-left:initial}.emoji-picker-category-buttons::-webkit-scrollbar,.emoji-picker-emoji-area::-webkit-scrollbar{width:5px;height:auto;background:var(--primary)}.emoji-picker-category-buttons::-webkit-scrollbar-thumb,.emoji-picker-emoji-area::-webkit-scrollbar-thumb{box-shadow:inset 0 0 5px #0003;background:var(--secondary)}.emoji-picker-category-buttons::-webkit-scrollbar-track,.emoji-picker-emoji-area::-webkit-scrollbar-track{box-shadow:inset 0 0 5px #0003;background:var(--primary-low)}.floor-text{color:var(--tertiary)} ");
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. var __defProp = Object.defineProperty;
  21. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  22. var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  23. var _GM = /* @__PURE__ */ (() => typeof GM != "undefined" ? GM : void 0)();
  24. async function getLevelFromConnect() {
  25. return await new Promise((resolve, reject) => {
  26. _GM.xmlHttpRequest({
  27. method: "GET",
  28. url: "https://connect.linux.do",
  29. onload: (response) => {
  30. let regx = /<body[^>]*>([\s\S]+?)<\/body>/i;
  31. let contents = regx.exec(response.responseText);
  32. if (contents) {
  33. resolve({
  34. status: true,
  35. content: contents[1],
  36. error: ""
  37. });
  38. }
  39. },
  40. onerror: (e) => {
  41. reject({ status: false, error: e.error, content: "" });
  42. }
  43. });
  44. });
  45. }
  46. function observeDom(selector, onChanged, option) {
  47. let dom = typeof selector === "string" ? document.querySelector(selector) : selector;
  48. if (dom) {
  49. const observer = new MutationObserver(() => {
  50. onChanged(dom);
  51. });
  52. observer.observe(dom, { childList: true });
  53. return observer;
  54. } else {
  55. console.error(`query dom error: [${selector}]`);
  56. return null;
  57. }
  58. }
  59. function isOnTopicPage() {
  60. return window.location.href.includes("https://linux.do/t/topic");
  61. }
  62. const _DomEventBus = class _DomEventBus {
  63. constructor() {
  64. __publicField(this, "listenerMap");
  65. __publicField(this, "observeMap");
  66. this.listenerMap = {};
  67. this.observeMap = {};
  68. }
  69. static getInstance() {
  70. if (!this.instance) {
  71. this.instance = new _DomEventBus();
  72. }
  73. return this.instance;
  74. }
  75. /**
  76. * 监听事件
  77. * @param name 事件名称
  78. * @param listener 事件监听器
  79. * @param dom 如果为 null,使用事件名称查找 dom, 不为空直接使用给定的 dom
  80. */
  81. add(name, listener, dom = null) {
  82. if (!this.listenerMap[name]) {
  83. this.listenerMap[name] = [];
  84. }
  85. if (this.listenerMap[name].length === 0) {
  86. let observe = dom === null ? observeDom(name, () => {
  87. this.domEmit(name);
  88. }) : observeDom(dom, () => {
  89. this.domEmit(name);
  90. });
  91. if (observe) {
  92. this.observeMap[name] = observe;
  93. }
  94. }
  95. this.listenerMap[name].push(listener);
  96. }
  97. domEmit(event) {
  98. const listeners = this.listenerMap[event];
  99. if (listeners) {
  100. for (const listener of listeners) {
  101. listener();
  102. }
  103. }
  104. }
  105. emit(name) {
  106. this.domEmit(name);
  107. }
  108. clear(name) {
  109. if (!this.listenerMap[name]) {
  110. return;
  111. }
  112. this.listenerMap[name] = [];
  113. }
  114. };
  115. __publicField(_DomEventBus, "instance");
  116. let DomEventBus = _DomEventBus;
  117. function createCodeElement(key) {
  118. var _a;
  119. let realKey = key;
  120. let copied = false;
  121. let root = document.createElement("div");
  122. root.className = "bg-white p-6 rounded-lg mb-4 shadow";
  123. root.innerHTML = `
  124. <h2>DeepLX Api Key</h2>
  125. <div class="code-box">
  126. <span class="hljs language-text">${key.replace(key.substring(12, 21), "**加密**")}</span>
  127. </div>
  128. `;
  129. let copyButton = document.createElement("span");
  130. copyButton.className = "copy";
  131. copyButton.innerHTML = "复制";
  132. copyButton.addEventListener("click", async () => {
  133. if (!copied) {
  134. await navigator.clipboard.writeText(realKey);
  135. copied = true;
  136. copyButton.innerHTML = "已复制";
  137. let timer = setTimeout(() => {
  138. copied = false;
  139. copyButton.innerHTML = "复制";
  140. clearInterval(timer);
  141. }, 2e3);
  142. }
  143. });
  144. (_a = root.querySelector("div.code-box")) == null ? void 0 : _a.appendChild(copyButton);
  145. let connectButton = document.createElement("a");
  146. connectButton.className = "btn btn-primary connect-button";
  147. connectButton.href = "https://connect.linux.do";
  148. connectButton.target = "_blank";
  149. connectButton.innerHTML = "前往 Connect 站";
  150. root.appendChild(connectButton);
  151. return root;
  152. }
  153. function createWindow(title, key, levelTable, onClose) {
  154. let root = document.createElement("div");
  155. root.setAttribute("id", "level-window");
  156. root.className = "level-window";
  157. root.style.right = document.querySelector("div.chat-drawer.is-expanded") ? "430px" : "15px";
  158. root.innerHTML = `
  159. <div class="title">
  160. <span class="close" id="close-button">
  161. <svg class="fa d-icon d-icon-times svg-icon svg-string" xmlns="http://www.w3.org/2000/svg">
  162. <use href="#xmark"></use>
  163. </svg>
  164. </span>
  165. <div id="content" class="content"></div>
  166. </div>`;
  167. let window2 = root.querySelector("div#content");
  168. if (window2) {
  169. window2.appendChild(title);
  170. window2.appendChild(createCodeElement(key));
  171. window2.appendChild(levelTable);
  172. }
  173. let close = root.querySelector("span#close-button");
  174. close == null ? void 0 : close.addEventListener("click", onClose);
  175. DomEventBus.getInstance().add("div.chat-drawer-outlet-container", () => {
  176. let chat = document.querySelector("div.chat-drawer.is-expanded");
  177. root.style.right = chat ? "430px" : "15px";
  178. });
  179. let chatContainer = document.querySelector("div.chat-drawer-outlet-container");
  180. if (chatContainer) {
  181. let observer = new MutationObserver((_) => {
  182. let chat = document.querySelector("div.chat-drawer.is-expanded");
  183. root.style.right = chat ? "430px" : "15px";
  184. });
  185. observer.observe(chatContainer, { childList: true });
  186. }
  187. return root;
  188. }
  189. function getLoadingSvg(size = 60) {
  190. return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}px" height="${size}px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="lds-ring">
  191. <circle cx="50" cy="50" r="30" stroke="#B3B5B411" stroke-width="10" fill="none"/>
  192. <circle cx="50" cy="50" r="30" stroke="#808281" stroke-width="10" fill="none" transform="rotate(144 50 50)">
  193. <animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"/>
  194. <animate attributeName="stroke-dasharray" calcMode="linear" values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882" keyTimes="0;0.5;1" dur="1" begin="0s" repeatCount="indefinite"/>
  195. </circle>
  196. </svg>`;
  197. }
  198. function showMessageBox(message, title, buttons = [
  199. {
  200. text: "确认",
  201. type: "btn-primary",
  202. onClicked: function() {
  203. }
  204. }
  205. ]) {
  206. let root = document.querySelector("div.modal-container");
  207. if (root) {
  208. let box = document.createElement("div");
  209. box.id = "message-box";
  210. box.className = "ember-view modal d-modal discard-draft-modal";
  211. box.setAttribute("data-keyboard", "false");
  212. box.setAttribute("aria-modal", "true");
  213. box.setAttribute("role", "dialog");
  214. box.innerHTML = `
  215. <div class="d-modal__container">
  216. <div class="d-modal__header">${title}</div>
  217. <div class="d-modal__body" tabindex="-1">
  218. <div class="instructions">
  219. ${message}
  220. </div>
  221. </div>
  222. <div class="d-modal__footer">
  223. </div>
  224. </div>`;
  225. let backdrop = document.createElement("div");
  226. backdrop.className = "d-modal__backdrop";
  227. root.appendChild(backdrop);
  228. let footer = box.querySelector("div.d-modal__footer");
  229. if (footer) {
  230. for (const button of buttons) {
  231. let btnElement = document.createElement("button");
  232. btnElement.className = "btn btn-text " + button.type;
  233. btnElement.setAttribute("type", "button");
  234. btnElement.innerHTML = `
  235. <span class="d-button-label">
  236. ${button.text}
  237. </span>
  238. `;
  239. btnElement.addEventListener("click", () => {
  240. button.onClicked();
  241. box.remove();
  242. backdrop.remove();
  243. });
  244. footer.appendChild(btnElement);
  245. }
  246. root.appendChild(box);
  247. }
  248. }
  249. }
  250. class Level {
  251. constructor() {
  252. __publicField(this, "levelWindow");
  253. __publicField(this, "loading", false);
  254. }
  255. init() {
  256. this.replaceConnectAnchor();
  257. }
  258. loadDomFromString(content) {
  259. let parser = new DOMParser();
  260. return parser.parseFromString(content, "text/html").body;
  261. }
  262. showErrorAndGotoConnect(error) {
  263. showMessageBox(error, "错误", [{
  264. text: "确认",
  265. type: "btn-primary",
  266. onClicked: () => {
  267. }
  268. }, {
  269. text: "前往 Connect 查看",
  270. type: "",
  271. onClicked: () => {
  272. window.open("https://connect.linux.do/", "_blank");
  273. }
  274. }]);
  275. }
  276. getContentsFromDom(dom) {
  277. var _a, _b, _c;
  278. let title = dom.querySelector("h1.text-2xl");
  279. (_a = title == null ? void 0 : title.querySelector('a[href^="/logout/"]')) == null ? void 0 : _a.remove();
  280. let levelTable = (_b = dom.querySelector("div.bg-white.p-6.rounded-lg.mb-4.shadow table")) == null ? void 0 : _b.parentElement;
  281. let key = (_c = dom.querySelector("div.bg-white.p-6.rounded-lg.mb-4.shadow p strong")) == null ? void 0 : _c.innerHTML;
  282. let status = key !== void 0 && levelTable !== null;
  283. return {
  284. status,
  285. key,
  286. title,
  287. content: levelTable,
  288. error: status ? null : "解析 Connect 数据错误。"
  289. };
  290. }
  291. replaceConnectAnchor() {
  292. let connectAnchor = document.querySelector('a[href="https://connect.linux.do"]');
  293. if (connectAnchor) {
  294. connectAnchor.href = "javascript:void(0);";
  295. connectAnchor.addEventListener("click", async () => {
  296. if (!this.loading && this.levelWindow === void 0) {
  297. this.loading = true;
  298. let icon = connectAnchor.querySelector("span.sidebar-section-link-prefix.icon");
  299. if (icon) {
  300. let defaultIcon = icon.innerHTML;
  301. icon.innerHTML = getLoadingSvg();
  302. let result = await getLevelFromConnect();
  303. this.loading = false;
  304. icon.innerHTML = defaultIcon;
  305. if (result.status) {
  306. let dom = this.loadDomFromString(result.content);
  307. let body = this.getContentsFromDom(dom);
  308. if (body.status) {
  309. this.levelWindow = createWindow(body.title, body.key, body.content, () => {
  310. this.close();
  311. });
  312. document.body.appendChild(this.levelWindow);
  313. } else {
  314. this.showErrorAndGotoConnect(body.error);
  315. }
  316. } else {
  317. this.showErrorAndGotoConnect(result.error);
  318. }
  319. }
  320. } else {
  321. this.close();
  322. }
  323. });
  324. return;
  325. }
  326. console.error("replace connect anchor error.");
  327. }
  328. close() {
  329. this.levelWindow.remove();
  330. this.levelWindow = void 0;
  331. }
  332. }
  333. function createFloor(num) {
  334. let button = document.createElement("button");
  335. button.className = "widget-button btn-flat reply create fade-out btn-icon-text";
  336. button.setAttribute("title", `${num}楼`);
  337. button.setAttribute("id", "floor-button");
  338. button.innerHTML = `<span class='d-button-label floor-text'>#${num}</span>`;
  339. return button;
  340. }
  341. class Floor {
  342. constructor() {
  343. __publicField(this, "eventBus");
  344. this.eventBus = DomEventBus.getInstance();
  345. }
  346. observeUrl() {
  347. const changed = () => {
  348. const timer = setInterval(() => {
  349. if (isOnTopicPage()) {
  350. this.eventBus.add("div.post-stream", () => {
  351. if (isOnTopicPage()) {
  352. this.fixFloorDom();
  353. }
  354. });
  355. this.fixFloorDom();
  356. } else {
  357. this.eventBus.clear("div.post-stream");
  358. }
  359. clearInterval(timer);
  360. });
  361. };
  362. this.eventBus.add("div#main-outlet", changed);
  363. if (isOnTopicPage()) {
  364. this.eventBus.emit("div#main-outlet");
  365. }
  366. }
  367. fixFloorDom() {
  368. let timer = setInterval(() => {
  369. var _a;
  370. let floors = Array.from(document.querySelectorAll("div.container.posts section.topic-area div.ember-view div.topic-post"));
  371. for (const floor of floors) {
  372. if (floor.querySelector("button#floor-button")) {
  373. continue;
  374. }
  375. let article = floor.querySelector("article");
  376. if (article) {
  377. let id = (_a = article.getAttribute("id")) == null ? void 0 : _a.replace("post_", "");
  378. let actions = floor.querySelectorAll("article section nav div.actions");
  379. const button = createFloor(id ? id : "??");
  380. if (actions.length > 0) {
  381. const i = actions.length === 2 ? 1 : 0;
  382. actions[i].appendChild(button);
  383. } else {
  384. console.error("query actions error.");
  385. }
  386. }
  387. }
  388. clearInterval(timer);
  389. });
  390. }
  391. init() {
  392. this.observeUrl();
  393. }
  394. }
  395. class Emoji {
  396. constructor() {
  397. __publicField(this, "customs", ["飞书", "小红书", "b站", "贴吧"]);
  398. __publicField(this, "observe", new MutationObserver(() => {
  399. let loadTimes = 0;
  400. let emojiPicker = document.querySelector("div.emoji-picker");
  401. if (emojiPicker) {
  402. let timer = setInterval(() => {
  403. let emojiButtons = emojiPicker.querySelector("div.emoji-picker__sections-nav");
  404. let emojiContainer = emojiPicker.querySelector("div.emoji-picker__sections");
  405. if (emojiButtons && emojiContainer) {
  406. for (const custom of this.customs) {
  407. this.moveElementToFirstBySelector(`div[data-section="${custom}"]`, emojiContainer);
  408. this.moveElementToFirstBySelector(`button[data-section="${custom}"]`, emojiButtons, custom === "贴吧");
  409. }
  410. clearInterval(timer);
  411. }
  412. loadTimes++;
  413. if (loadTimes >= 300) {
  414. console.warn("emoji 加载缓慢,跳过修正,下次打开表情面板即可正常显示。");
  415. clearInterval(timer);
  416. }
  417. });
  418. }
  419. }));
  420. }
  421. moveElementToFirstBySelector(selector, root, click = false) {
  422. let node = root.querySelector(selector);
  423. if (node) {
  424. root.insertBefore(node, root.children[0].nextSibling);
  425. if (click && node instanceof HTMLButtonElement) {
  426. node.click();
  427. }
  428. }
  429. }
  430. init() {
  431. observeDom("div#reply-control", (replay) => {
  432. this.onReplayOpen(replay);
  433. });
  434. }
  435. onReplayOpen(replay) {
  436. if (replay.className.includes("open")) {
  437. let menu = document.querySelector("div#d-menu-portals");
  438. if (menu) {
  439. this.observe.observe(menu, { childList: true });
  440. } else {
  441. console.error("querySelector:div.d-editor");
  442. }
  443. } else {
  444. this.observe.disconnect();
  445. }
  446. }
  447. }
  448. function init() {
  449. window.addEventListener("load", () => {
  450. new Level().init();
  451. new Floor().init();
  452. new Emoji().init();
  453. });
  454. }
  455. init();
  456.  
  457. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址