linux.do.level

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

当前为 2024-04-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name linux.do.level
  3. // @namespace https://linux.do/u/io.oi/s/level
  4. // @version 1.2.0
  5. // @author LINUX.DO
  6. // @description Linux.Do 查看用户信任级别以及升级条件,数据来源于 https://connect.linux.do
  7. // @license MIT
  8. // @icon https://cdn.linux.do/uploads/default/original/1X/de7ee26820e897b6a07350126411ebc489f62202.png
  9. // @match https://linux.do/*
  10. // @connect https://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 o=document.createElement("style");o.textContent=e,document.head.append(o)})(" .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:30px;height:30px;color:#fff;background:red;display:inline-block;text-align:center;line-height:30px;float:right;cursor:pointer;border-radius:4px}.level-window .bg-white{background-color:var(--primary-50);border-radius:.5em;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} ");
  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) => {
  23. __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  24. return value;
  25. };
  26. function showMessageBox(message, title, buttons = [
  27. {
  28. text: "确认",
  29. type: "btn-primary",
  30. onClicked: function() {
  31. }
  32. }
  33. ]) {
  34. let root = document.querySelector("div.modal-container");
  35. if (root) {
  36. let box = document.createElement("div");
  37. box.id = "message-box";
  38. box.className = "ember-view modal d-modal discard-draft-modal";
  39. box.setAttribute("data-keyboard", "false");
  40. box.setAttribute("aria-modal", "true");
  41. box.setAttribute("role", "dialog");
  42. box.innerHTML = `
  43. <div class="d-modal__container">
  44. <div class="d-modal__header">${title}</div>
  45. <div class="d-modal__body" tabindex="-1">
  46. <div class="instructions">
  47. ${message}
  48. </div>
  49. </div>
  50. <div class="d-modal__footer">
  51. </div>
  52. </div>`;
  53. let backdrop = document.createElement("div");
  54. backdrop.className = "d-modal__backdrop";
  55. root.appendChild(backdrop);
  56. let footer = box.querySelector("div.d-modal__footer");
  57. if (footer) {
  58. for (const button of buttons) {
  59. let btnElement = document.createElement("button");
  60. btnElement.className = "btn btn-text " + button.type;
  61. btnElement.setAttribute("type", "button");
  62. btnElement.innerHTML = `
  63. <span class="d-button-label">
  64. ${button.text}
  65. </span>
  66. `;
  67. btnElement.addEventListener("click", () => {
  68. button.onClicked();
  69. box.remove();
  70. backdrop.remove();
  71. });
  72. footer.appendChild(btnElement);
  73. }
  74. root.appendChild(box);
  75. }
  76. }
  77. }
  78. function observeDom(selector, onChanged, option) {
  79. let dom = document.querySelector(selector);
  80. if (dom) {
  81. console.log(dom);
  82. const observer = new MutationObserver(() => {
  83. onChanged(dom);
  84. });
  85. observer.observe(dom, option ? option : { childList: true });
  86. return observer;
  87. } else {
  88. console.error(`query dom error: [${selector}]`);
  89. return null;
  90. }
  91. }
  92. class Invite {
  93. fixInviteAnchors(container) {
  94. const selector = 'a[href^="https://linux.do/invites/"]';
  95. let anchors = Array.from(container ? container.querySelectorAll(selector) : document.querySelectorAll(selector));
  96. for (const anchor of anchors) {
  97. const inviteUrl = anchor.href;
  98. anchor.href = "javascript:void(0);";
  99. anchor.onclick = null;
  100. let buttons = [
  101. {
  102. text: "我就是想被下限",
  103. type: "btn-danger",
  104. onClicked: () => {
  105. window.location.href = inviteUrl;
  106. }
  107. },
  108. {
  109. text: "差点就不干净了",
  110. type: "",
  111. onClicked: () => {
  112. }
  113. }
  114. ];
  115. anchor.addEventListener("click", () => {
  116. showMessageBox("这是一个邀请连接,虽然你已经注册,但是跳转后,你仍会成为邀请人的下线。", "警告", buttons);
  117. });
  118. }
  119. }
  120. observeMainOutlet() {
  121. let observe = null;
  122. observeDom("div#main-outlet", (_) => {
  123. if (window.location.href.includes("https://linux.do/t/topic")) {
  124. this.fixInviteAnchors();
  125. observe = observeDom("div#main-outlet div.container.posts div.row div.ember-view", (dom) => {
  126. this.fixInviteAnchors(dom);
  127. });
  128. } else {
  129. observe == null ? void 0 : observe.disconnect();
  130. }
  131. });
  132. }
  133. init() {
  134. this.fixInviteAnchors();
  135. this.observeMainOutlet();
  136. }
  137. }
  138. var _GM = /* @__PURE__ */ (() => typeof GM != "undefined" ? GM : void 0)();
  139. async function getLevelFromConnect() {
  140. return await new Promise((resolve, reject) => {
  141. _GM.xmlHttpRequest({
  142. method: "GET",
  143. url: "https://connect.linux.do",
  144. onload: (response) => {
  145. let regx = /<body[^>]*>([\s\S]+?)<\/body>/i;
  146. let contents = regx.exec(response.responseText);
  147. if (contents) {
  148. const content = contents[1].replace('<a href="/logout" target="_self" class="text-blue-500 hover:underline" title="LINUX DO登录也会退出">退出</a>', "");
  149. resolve({
  150. status: true,
  151. content,
  152. error: ""
  153. });
  154. }
  155. },
  156. onerror: (e) => {
  157. reject({ status: false, error: e.error, content: "" });
  158. }
  159. });
  160. });
  161. }
  162. function createWindow(content, onClose) {
  163. let root = document.createElement("div");
  164. root.setAttribute("id", "level-window");
  165. root.className = "level-window";
  166. root.style.right = document.querySelector("div.chat-drawer.is-expanded") ? "430px" : "15px";
  167. root.innerHTML = `
  168. <div class="title">
  169. <span class="close" id="close-button">
  170. <svg class="fa d-icon d-icon-times svg-icon svg-string" xmlns="http://www.w3.org/2000/svg">
  171. <use href="#times"></use>
  172. </svg>
  173. </span>
  174. <div id="content" class="content"></div>
  175. </div>`;
  176. let container = root.querySelector("div#content");
  177. if (container) {
  178. container.innerHTML = content;
  179. }
  180. let close = root.querySelector("span#close-button");
  181. if (close) {
  182. close.addEventListener("click", () => {
  183. onClose();
  184. });
  185. }
  186. let chatContainer = document.querySelector("div.chat-drawer-outlet-container");
  187. if (chatContainer) {
  188. let observer = new MutationObserver((_) => {
  189. let chat = document.querySelector("div.chat-drawer.is-expanded");
  190. root.style.right = chat ? "430px" : "15px";
  191. });
  192. observer.observe(chatContainer, { childList: true });
  193. }
  194. return root;
  195. }
  196. function getLoadingSvg(size = 60) {
  197. 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">
  198. <circle cx="50" cy="50" r="30" stroke="#B3B5B411" stroke-width="10" fill="none"/>
  199. <circle cx="50" cy="50" r="30" stroke="#808281" stroke-width="10" fill="none" transform="rotate(144 50 50)">
  200. <animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"/>
  201. <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"/>
  202. </circle>
  203. </svg>`;
  204. }
  205. class Level {
  206. constructor() {
  207. __publicField(this, "levelWindow");
  208. __publicField(this, "loading", false);
  209. }
  210. init() {
  211. this.replaceConnectAnchor();
  212. }
  213. replaceConnectAnchor() {
  214. let connectAnchor = document.querySelector('a[href="https://connect.linux.do"]');
  215. if (connectAnchor) {
  216. connectAnchor.href = "javascript:void(0);";
  217. connectAnchor.addEventListener("click", async () => {
  218. if (!this.loading && this.levelWindow === void 0) {
  219. this.loading = true;
  220. let icon = connectAnchor.querySelector("span.sidebar-section-link-prefix.icon");
  221. if (icon) {
  222. let defaultIcon = icon.innerHTML;
  223. icon.innerHTML = getLoadingSvg();
  224. let result = await getLevelFromConnect();
  225. this.loading = false;
  226. icon.innerHTML = defaultIcon;
  227. if (result.status) {
  228. this.levelWindow = createWindow(result.content, () => {
  229. var _a;
  230. (_a = this.levelWindow) == null ? void 0 : _a.remove();
  231. this.levelWindow = void 0;
  232. });
  233. document.body.appendChild(this.levelWindow);
  234. } else {
  235. console.error(result.error);
  236. }
  237. }
  238. } else {
  239. this.levelWindow.remove();
  240. this.levelWindow = void 0;
  241. }
  242. });
  243. return;
  244. }
  245. console.error("replace connect anchor error.");
  246. }
  247. }
  248. (() => {
  249. function init() {
  250. window.addEventListener("load", () => {
  251. new Level().init();
  252. new Invite().init();
  253. });
  254. }
  255. init();
  256. })();
  257.  
  258. })();