linux.do.level

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

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

  1. // ==UserScript==
  2. // @name linux.do.level
  3. // @namespace https://linux.do/u/io.oi/s/level
  4. // @version 1.3.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 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. const observer = new MutationObserver(() => {
  82. onChanged(dom);
  83. });
  84. observer.observe(dom, option ? option : { childList: true });
  85. return observer;
  86. } else {
  87. console.error(`query dom error: [${selector}]`);
  88. return null;
  89. }
  90. }
  91. function isOnTopicPage() {
  92. return window.location.href.includes("https://linux.do/t/topic");
  93. }
  94. const _DomEventBus = class _DomEventBus {
  95. constructor() {
  96. __publicField(this, "listenerMap");
  97. __publicField(this, "observeMap");
  98. this.listenerMap = {};
  99. this.observeMap = {};
  100. }
  101. static getInstance() {
  102. if (!this.instance) {
  103. this.instance = new _DomEventBus();
  104. }
  105. return this.instance;
  106. }
  107. add(name, listener) {
  108. if (!this.listenerMap[name]) {
  109. this.listenerMap[name] = [];
  110. }
  111. if (this.listenerMap[name].length === 0) {
  112. let observe = observeDom(name, () => {
  113. this.domEmit(name);
  114. });
  115. if (observe) {
  116. this.observeMap[name] = observe;
  117. }
  118. }
  119. this.listenerMap[name].push(listener);
  120. }
  121. domEmit(event) {
  122. const listeners = this.listenerMap[event];
  123. if (listeners) {
  124. for (const listener of listeners) {
  125. listener();
  126. }
  127. }
  128. }
  129. clear(name) {
  130. if (!this.listenerMap[name]) {
  131. return;
  132. }
  133. this.listenerMap[name] = [];
  134. }
  135. };
  136. __publicField(_DomEventBus, "instance");
  137. let DomEventBus = _DomEventBus;
  138. class Invite {
  139. fixInviteAnchors(container) {
  140. const selector = 'a[href^="https://linux.do/invites/"]';
  141. let anchors = Array.from(container ? container.querySelectorAll(selector) : document.querySelectorAll(selector));
  142. for (const anchor of anchors) {
  143. const inviteUrl = anchor.href;
  144. anchor.href = "javascript:void(0);";
  145. anchor.onclick = null;
  146. let buttons = [
  147. {
  148. text: "我就是想被下限",
  149. type: "btn-danger",
  150. onClicked: () => {
  151. window.location.href = inviteUrl;
  152. }
  153. },
  154. {
  155. text: "差点就不干净了",
  156. type: "",
  157. onClicked: () => {
  158. }
  159. }
  160. ];
  161. anchor.addEventListener("click", () => {
  162. showMessageBox("这是一个邀请连接,虽然你已经注册,但是跳转后,你仍会成为邀请人的下线。", "警告", buttons);
  163. });
  164. }
  165. }
  166. observeMainOutlet() {
  167. let observe = null;
  168. DomEventBus.getInstance().add("div#main-outlet", () => {
  169. if (isOnTopicPage()) {
  170. this.fixInviteAnchors();
  171. observe = observeDom("div#main-outlet div.container.posts div.row div.ember-view", (dom) => {
  172. this.fixInviteAnchors(dom);
  173. });
  174. } else {
  175. observe == null ? void 0 : observe.disconnect();
  176. }
  177. });
  178. }
  179. init() {
  180. this.fixInviteAnchors();
  181. this.observeMainOutlet();
  182. }
  183. }
  184. var _GM = /* @__PURE__ */ (() => typeof GM != "undefined" ? GM : void 0)();
  185. async function getLevelFromConnect() {
  186. return await new Promise((resolve, reject) => {
  187. _GM.xmlHttpRequest({
  188. method: "GET",
  189. url: "https://connect.linux.do",
  190. onload: (response) => {
  191. let regx = /<body[^>]*>([\s\S]+?)<\/body>/i;
  192. let contents = regx.exec(response.responseText);
  193. if (contents) {
  194. const content = contents[1].replace('<a href="/logout" target="_self" class="text-blue-500 hover:underline" title="LINUX DO登录也会退出">退出</a>', "");
  195. resolve({
  196. status: true,
  197. content,
  198. error: ""
  199. });
  200. }
  201. },
  202. onerror: (e) => {
  203. reject({ status: false, error: e.error, content: "" });
  204. }
  205. });
  206. });
  207. }
  208. function createWindow(content, onClose) {
  209. let root = document.createElement("div");
  210. root.setAttribute("id", "level-window");
  211. root.className = "level-window";
  212. root.style.right = document.querySelector("div.chat-drawer.is-expanded") ? "430px" : "15px";
  213. root.innerHTML = `
  214. <div class="title">
  215. <span class="close" id="close-button">
  216. <svg class="fa d-icon d-icon-times svg-icon svg-string" xmlns="http://www.w3.org/2000/svg">
  217. <use href="#times"></use>
  218. </svg>
  219. </span>
  220. <div id="content" class="content"></div>
  221. </div>`;
  222. let container = root.querySelector("div#content");
  223. if (container) {
  224. container.innerHTML = content;
  225. }
  226. let close = root.querySelector("span#close-button");
  227. close == null ? void 0 : close.addEventListener("click", onClose);
  228. DomEventBus.getInstance().add("div.chat-drawer-outlet-container", () => {
  229. let chat = document.querySelector("div.chat-drawer.is-expanded");
  230. root.style.right = chat ? "430px" : "15px";
  231. });
  232. let chatContainer = document.querySelector("div.chat-drawer-outlet-container");
  233. if (chatContainer) {
  234. let observer = new MutationObserver((_) => {
  235. let chat = document.querySelector("div.chat-drawer.is-expanded");
  236. root.style.right = chat ? "430px" : "15px";
  237. });
  238. observer.observe(chatContainer, { childList: true });
  239. }
  240. return root;
  241. }
  242. function getLoadingSvg(size = 60) {
  243. 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">
  244. <circle cx="50" cy="50" r="30" stroke="#B3B5B411" stroke-width="10" fill="none"/>
  245. <circle cx="50" cy="50" r="30" stroke="#808281" stroke-width="10" fill="none" transform="rotate(144 50 50)">
  246. <animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"/>
  247. <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"/>
  248. </circle>
  249. </svg>`;
  250. }
  251. class Level {
  252. constructor() {
  253. __publicField(this, "levelWindow");
  254. __publicField(this, "loading", false);
  255. }
  256. init() {
  257. this.replaceConnectAnchor();
  258. }
  259. replaceConnectAnchor() {
  260. let connectAnchor = document.querySelector('a[href="https://connect.linux.do"]');
  261. if (connectAnchor) {
  262. connectAnchor.href = "javascript:void(0);";
  263. connectAnchor.addEventListener("click", async () => {
  264. if (!this.loading && this.levelWindow === void 0) {
  265. this.loading = true;
  266. let icon = connectAnchor.querySelector("span.sidebar-section-link-prefix.icon");
  267. if (icon) {
  268. let defaultIcon = icon.innerHTML;
  269. icon.innerHTML = getLoadingSvg();
  270. let result = await getLevelFromConnect();
  271. this.loading = false;
  272. icon.innerHTML = defaultIcon;
  273. if (result.status) {
  274. this.levelWindow = createWindow(result.content, () => {
  275. var _a;
  276. (_a = this.levelWindow) == null ? void 0 : _a.remove();
  277. this.levelWindow = void 0;
  278. });
  279. document.body.appendChild(this.levelWindow);
  280. } else {
  281. console.error(result.error);
  282. }
  283. }
  284. } else {
  285. this.levelWindow.remove();
  286. this.levelWindow = void 0;
  287. }
  288. });
  289. return;
  290. }
  291. console.error("replace connect anchor error.");
  292. }
  293. }
  294. function createFloor(num) {
  295. let button = document.createElement("button");
  296. button.className = "widget-button btn-flat reply create fade-out btn-icon-text";
  297. button.setAttribute("title", `${num}楼`);
  298. button.setAttribute("id", "floor-button");
  299. button.innerHTML = `<span class='d-button-label'>${num}楼</span>`;
  300. return button;
  301. }
  302. class Floor {
  303. constructor() {
  304. __publicField(this, "eventBus");
  305. this.eventBus = DomEventBus.getInstance();
  306. }
  307. observeUrl() {
  308. const changed = () => {
  309. if (isOnTopicPage()) {
  310. this.eventBus.add("div.container.posts section.topic-area div.ember-view", () => {
  311. if (isOnTopicPage()) {
  312. this.fixFloorDom();
  313. }
  314. });
  315. this.fixFloorDom();
  316. } else {
  317. this.eventBus.clear("div.container.posts section.topic-area div.ember-view");
  318. }
  319. };
  320. this.eventBus.add("div#main-outlet", changed);
  321. }
  322. fixFloorDom() {
  323. var _a;
  324. let floors = Array.from(document.body.querySelectorAll("div.container.posts section.topic-area div.ember-view div.topic-post.clearfix.regular"));
  325. for (const floor of floors) {
  326. if (floor.querySelector("button#floor-button")) {
  327. continue;
  328. }
  329. let article = floor.querySelector("article");
  330. if (article) {
  331. let id = (_a = article.getAttribute("id")) == null ? void 0 : _a.replace("post_", "");
  332. let actions = floor.querySelector("article section nav div.actions");
  333. actions == null ? void 0 : actions.appendChild(createFloor(id ? id : "??"));
  334. }
  335. }
  336. }
  337. init() {
  338. this.observeUrl();
  339. }
  340. }
  341. function init() {
  342. window.addEventListener("load", () => {
  343. new Level().init();
  344. new Invite().init();
  345. new Floor().init();
  346. });
  347. }
  348. init();
  349.  
  350. })();