UserStatusSub

萌娘百科UserStatus订阅

安装此脚本
作者推荐脚本

您可能也喜欢JustMoeComments

安装此脚本
  1. // ==UserScript==
  2. // @name UserStatusSub
  3. // @namespace https://github.com/gui-ying233/UserStatusSub
  4. // @version 1.3.0
  5. // @description 萌娘百科UserStatus订阅
  6. // @author 鬼影233
  7. // @contributor BearBin@BearBin1215
  8. // @license MIT
  9. // @match *.moegirl.org.cn/*
  10. // @icon https://img.moegirl.org.cn/common/b/b7/%E5%A4%A7%E8%90%8C%E5%AD%97.svg
  11. // @supportURL https://github.com/gui-ying233/UserStatusSub/issues
  12. // ==/UserScript==
  13.  
  14. (() => {
  15. "use strict";
  16. if (new URLSearchParams(window.location.search).get("safemode")) return;
  17. $(() =>
  18. (async () => {
  19. window.addEventListener("storage", e => {
  20. if (e.key === "userStatusSub") {
  21. if (e.newValue) {
  22. localStorage.setItem("userStatusSub", e.newValue);
  23. } else {
  24. localStorage.removeItem("userStatusSub");
  25. }
  26. }
  27. });
  28.  
  29. await mw.loader.using([
  30. "mediawiki.api",
  31. "mediawiki.notification",
  32. "mediawiki.util",
  33. "oojs-ui",
  34. ]);
  35.  
  36. const api = new mw.Api();
  37.  
  38. mw.util.addCSS(`.userStatus {
  39. width: 100%;
  40. display: grid;
  41. grid-template-columns: repeat(auto-fit, 11em);
  42. gap: 1em;
  43. padding: 1em;
  44. justify-content: center;
  45. }
  46.  
  47. .userStatusImg {
  48. width: 25px;
  49. vertical-align: middle;
  50. }
  51.  
  52. .userStatusRaw {
  53. display: grid;
  54. gap: 0.5em;
  55. border: var(--theme-just-kidding-text-color) dashed 1px;
  56. padding: 0.5em;
  57. }
  58.  
  59. .userStatusContent {
  60. max-height: 11em;
  61. overflow: auto;
  62. transition: max-height 350ms;
  63. }
  64.  
  65. .userStatusContent:hover {
  66. max-height: 100%;
  67. }
  68.  
  69. #userStatuUpdate {
  70. margin: 1em 0;
  71. }`);
  72.  
  73. /**
  74. * @return {JSON}
  75. */
  76. function userStatusGetLocalStorage() {
  77. return Object.values(
  78. JSON.parse(localStorage.getItem("userStatusSub")) || {}
  79. );
  80. }
  81. async function userStatusSetLocalStorage() {
  82. if (
  83. document
  84. .querySelector("#userStatuSummary > textarea")
  85. .value.trim()
  86. ) {
  87. let subList = [];
  88. for (const t of [
  89. ...new Set(
  90. document
  91. .querySelector("#userStatuSummary > textarea")
  92. .value.trim()
  93. .split("\n")
  94. .map(str => str.trim())
  95. .filter(str => str)
  96. ),
  97. ]) {
  98. await api
  99. .get({
  100. action: "query",
  101. format: "json",
  102. prop: "revisions",
  103. titles: `User:${t}/Status`,
  104. utf8: 1,
  105. formatversion: 2,
  106. rvprop: "timestamp",
  107. })
  108. .then(d => {
  109. if (d.query.pages[0].missing) {
  110. mw.notify(`用户${t}不存在`, {
  111. type: "warn",
  112. });
  113. } else {
  114. subList.push({
  115. title: t,
  116. timestamp:
  117. d.query.pages[0].revisions[0]
  118. .timestamp,
  119. });
  120. }
  121. });
  122. }
  123. localStorage.setItem(
  124. "userStatusSub",
  125. JSON.stringify(subList)
  126. );
  127. document.querySelector("#userStatuSummary > textarea");
  128. } else {
  129. localStorage.removeItem("userStatusSub");
  130. }
  131. }
  132.  
  133. /**
  134. * @param {JSON} subList
  135. */
  136. async function updateUserStatus(subList) {
  137. const userStatusUpdateButton =
  138. document.getElementById("userStatuUpdate");
  139. userStatusUpdateButton.style.pointerEvents = "none";
  140. userStatusUpdateButton.classList.add("oo-ui-widget-disabled");
  141. userStatusUpdateButton.classList.remove("oo-ui-widget-enabled");
  142. userStatusUpdateButton.setAttribute("aria-disabled", "true");
  143. document.body.querySelector(".userStatus").innerHTML = "";
  144. userStatusSetLocalStorage();
  145. for (const u of subList) {
  146. try {
  147. await api
  148. .get({
  149. action: "parse",
  150. format: "json",
  151. page: `User:${u.title}/Status`,
  152. prop: "text",
  153. disabletoc: 1,
  154. utf8: 1,
  155. formatversion: 2,
  156. })
  157. .then(d => {
  158. const userStatusRaw =
  159. document.createElement("div");
  160. userStatusRaw.classList.add("userStatusRaw");
  161. userStatusRaw.innerHTML += d.parse.text;
  162. switch (
  163. userStatusRaw.innerText.trim().toLowerCase()
  164. ) {
  165. case "online":
  166. case "on":
  167. userStatusRaw.innerHTML =
  168. '<img class="userStatusImg" src="https://img.moegirl.org.cn/common/9/94/Symbol_support_vote.svg"> <b style="color:green;">在线</b>';
  169. break;
  170. case "busy":
  171. userStatusRaw.innerHTML =
  172. '<img class="userStatusImg" src="https://img.moegirl.org.cn/common/c/c5/Symbol_support2_vote.svg"> <b style="color:blue;">忙碌</b>';
  173. break;
  174. case "offline":
  175. case "off":
  176. userStatusRaw.innerHTML =
  177. '<img class="userStatusImg" src="https://img.moegirl.org.cn/common/7/7f/Symbol_oppose_vote.svg"> <b style="color:red;">离线</b>';
  178. break;
  179. case "away":
  180. userStatusRaw.innerHTML =
  181. '<img class="userStatusImg" src="https://img.moegirl.org.cn/common/6/6c/Time2wait.svg"> <b style="color:grey;">已离开</b>';
  182. break;
  183. case "sleeping":
  184. case "sleep":
  185. userStatusRaw.innerHTML =
  186. '<img class="userStatusImg" src="https://img.moegirl.org.cn/common/5/54/Symbol_wait.svg"> <b style="color:purple;">在睡觉</b>';
  187. break;
  188. case "wikibreak":
  189. case "break":
  190. userStatusRaw.innerHTML =
  191. '<img class="userStatusImg" src="https://img.moegirl.org.cn/common/6/61/Symbol_abstain_vote.svg"> <b style="color:brown;">正在放萌百假期</b>';
  192. break;
  193. case "holiday":
  194. userStatusRaw.innerHTML =
  195. '<img class="userStatusImg" src="https://img.moegirl.org.cn/common/3/30/Symbol_deferred.svg"> <b style="color:#7B68EE;">处于假期中</b>';
  196. break;
  197. }
  198. userStatusRaw.innerHTML = `<b class="userStatusUserName">${u.title}</b><div class="userStatusContent">${userStatusRaw.innerHTML}</div>`;
  199. document.body
  200. .querySelector(".userStatus")
  201. .append(userStatusRaw);
  202. userStatusSubDialog.updateSize();
  203. });
  204. } catch (e) {
  205. if (e !== "missingtitle") {
  206. console.error(e);
  207. }
  208. }
  209. }
  210. userStatusUpdateButton.style.pointerEvents = "auto";
  211. userStatusUpdateButton.classList.remove(
  212. "oo-ui-widget-disabled"
  213. );
  214. userStatusUpdateButton.classList.add("oo-ui-widget-enabled");
  215. userStatusUpdateButton.setAttribute("aria-disabled", "false");
  216. }
  217.  
  218. /* 感谢BearBin@BearBin1215提供的的OOUI部分 */
  219. const $body = $("body");
  220.  
  221. class userStatusSubWindow extends OO.ui.ProcessDialog {
  222. static static = {
  223. ...super.static,
  224. tagName: "div",
  225. name: "userStatus",
  226. title: "用户状态监控",
  227. actions: [
  228. {
  229. action: "cancel",
  230. label: "取消",
  231. flags: ["safe", "close", "destructive"],
  232. },
  233. {
  234. action: "submit",
  235. label: "保存",
  236. flags: ["primary", "progressive"],
  237. },
  238. ],
  239. };
  240. constructor(config) {
  241. super(config);
  242. }
  243. initialize() {
  244. super.initialize();
  245. this.panelLayout = new OO.ui.PanelLayout({
  246. scrollable: false,
  247. expanded: false,
  248. padded: true,
  249. });
  250.  
  251. const $userStatus = document.createElement("div");
  252. $userStatus.classList.add("userStatus");
  253.  
  254. const $label = document.createElement("p");
  255. $label.innerText = "订阅列表:";
  256. const userStatusInputBox =
  257. new OO.ui.MultilineTextInputWidget({
  258. placeholder: "仅填写用户名,每个用户名一行",
  259. id: "userStatuSummary",
  260. autosize: true,
  261. });
  262. const userStatusUpdateButton = new OO.ui.ButtonWidget({
  263. label: "更新列表",
  264. flags: ["primary"],
  265. id: "userStatuUpdate",
  266. });
  267. userStatusUpdateButton.on("click", async () => {
  268. await updateUserStatus(
  269. userStatusInputBox
  270. .getValue()
  271. .trim()
  272. .split("\n")
  273. .map(str => str.trim())
  274. .filter(str => str)
  275. .map(str => {
  276. return { title: str };
  277. })
  278. );
  279. this.updateSize();
  280. });
  281.  
  282. this.panelLayout.$element.append(
  283. $userStatus,
  284. $label,
  285. userStatusInputBox.$element,
  286. userStatusUpdateButton.$element
  287. );
  288. this.$body.append(this.panelLayout.$element);
  289. }
  290.  
  291. getActionProcess(action) {
  292. if (action === "cancel") {
  293. return new OO.ui.Process(() => {
  294. this.close({ action });
  295. }, this);
  296. } else if (action === "submit") {
  297. userStatusSetLocalStorage();
  298. return new OO.ui.Process(() => {
  299. this.close({ action });
  300. }, this);
  301. }
  302. return super.getActionProcess(action);
  303. }
  304. }
  305.  
  306. const windowManager = new OO.ui.WindowManager({});
  307. $body.append(windowManager.$element);
  308. const userStatusSubDialog = new userStatusSubWindow({
  309. size: "larger",
  310. });
  311. windowManager.addWindows([userStatusSubDialog]);
  312.  
  313. mw.util
  314. .addPortletLink(
  315. "p-cactions",
  316. "javascript:void(0);",
  317. "用户状态监控",
  318. "ca-userstatussub"
  319. )
  320. .addEventListener("click", async () => {
  321. $("#mw-notification-area").appendTo("body");
  322. windowManager.openWindow(userStatusSubDialog);
  323. document.body.querySelector(
  324. "#userStatuSummary > textarea"
  325. ).value = userStatusGetLocalStorage()
  326. .map(u => u.title)
  327. .join("\n");
  328. await updateUserStatus(userStatusGetLocalStorage());
  329. });
  330.  
  331. const waitTime = 300000;
  332.  
  333. var timestamps = {};
  334. (async () => {
  335. if (Notification.permission !== "denied") {
  336. Notification.requestPermission();
  337. }
  338. for (const u of userStatusGetLocalStorage()) {
  339. timestamps[u.title] = u.timestamp;
  340. setInterval(() => {
  341. api.get({
  342. action: "query",
  343. format: "json",
  344. prop: "revisions",
  345. titles: `User:${u.title}/Status`,
  346. utf8: 1,
  347. formatversion: 2,
  348. rvprop: "timestamp|content",
  349. }).then(d => {
  350. const timestamp =
  351. d.query.pages[0].revisions[0].timestamp;
  352. if (timestamp !== timestamps[u.title]) {
  353. localStorage.setItem(
  354. "userStatusSub",
  355. JSON.stringify(
  356. userStatusGetLocalStorage()
  357. ).replace(
  358. `"title":"${u.title}","timestamp":"${
  359. timestamps[u.title]
  360. }"`,
  361. `"title":"${u.title}","timestamp":"${timestamp}"`
  362. )
  363. );
  364. timestamps[u.title] = timestamp;
  365. let status =
  366. d.query.pages[0].revisions[0].content
  367. .trim()
  368. .toLowerCase();
  369. switch (status) {
  370. case "online":
  371. case "on":
  372. status = "在线";
  373. break;
  374. case "busy":
  375. status = "忙碌";
  376. break;
  377. case "offline":
  378. case "off":
  379. status = "离线";
  380. break;
  381. case "away":
  382. status = "已离开";
  383. break;
  384. case "sleeping":
  385. case "sleep":
  386. status = "在睡觉";
  387. break;
  388. case "wikibreak":
  389. case "break":
  390. status = "正在放萌百假期";
  391. break;
  392. case "holiday":
  393. status = "处于假期中";
  394. break;
  395. default:
  396. const s = document.createElement("div");
  397. s.innerHTML = status;
  398. status = s.innerText;
  399. }
  400. new Notification(`${u.title}已更新:`, {
  401. body: status,
  402. icon: `//commons.moegirl.org.cn/extensions/Avatar/avatar.php?user=${u.title}`,
  403. });
  404. }
  405. });
  406. }, waitTime);
  407. await (() => {
  408. return new Promise(resolve =>
  409. setTimeout(
  410. resolve,
  411. waitTime / userStatusGetLocalStorage().length
  412. )
  413. );
  414. })();
  415. }
  416. })();
  417. })()
  418. );
  419. })();