} - A promise that resolves when the HTML content is set.
+ */
+let GetUsernameHTML = async (Element, Username, Simple = false, Href = "https://www.xmoj.tech/userinfo.php?user=") => {
+ try {
+ //Username = Username.replaceAll(/[^a-zA-Z0-9]/g, "");
+ let ID = "Username-" + Username + "-" + Math.random();
+ Element.id = ID;
+ Element.innerHTML = ``;
+ Element.appendChild(document.createTextNode(Username));
+ let UserInfo = await GetUserInfo(Username);
+ if (UserInfo === null) {
+ document.getElementById(ID).innerHTML = "";
+ document.getElementById(ID).appendChild(document.createTextNode(Username));
+ return;
+ }
+ let HTMLData = "";
+ if (!Simple) {
+ HTMLData += `
`;
+ }
+ HTMLData += ` 500) {
+ HTMLData += "link-danger";
+ } else if (Rating >= 400) {
+ HTMLData += "link-warning";
+ } else if (Rating >= 300) {
+ HTMLData += "link-success";
+ } else {
+ HTMLData += "link-info";
+ }
+ } else {
+ HTMLData += "link-info";
+ }
+ HTMLData += `\";">`;
+ if (!Simple) {
+ if (AdminUserList.includes(Username)) {
+ HTMLData += `脚本管理员`;
+ }
+ let BadgeInfo = await GetUserBadge(Username);
+ if (BadgeInfo.Content != "") {
+ HTMLData += `${BadgeInfo.Content}`;
+ }
+ }
+ if (document.getElementById(ID) !== null) {
+ document.getElementById(ID).innerHTML = HTMLData;
+ document.getElementById(ID).getElementsByTagName("a")[0].appendChild(document.createTextNode(Username));
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+};
+/**
+ * Converts the given number of seconds to a formatted string representation of hours, minutes, and seconds.
+ * @param {number} InputSeconds - The number of seconds to convert.
+ * @returns {string} The formatted string representation of the input seconds.
+ */
+let SecondsToString = (InputSeconds) => {
+ try {
+ let Hours = Math.floor(InputSeconds / 3600);
+ let Minutes = Math.floor((InputSeconds % 3600) / 60);
+ let Seconds = InputSeconds % 60;
+ return (Hours < 10 ? "0" : "") + Hours + ":" + (Minutes < 10 ? "0" : "") + Minutes + ":" + (Seconds < 10 ? "0" : "") + Seconds;
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
}
-
-function PollNotifications() {
- // Clear toast container once before fetching to prevent race condition
- if (UtilityEnabled("BBSPopup") || UtilityEnabled("MessagePopup")) {
- let ToastContainer = document.querySelector(".toast-container");
- if (ToastContainer) {
- ToastContainer.innerHTML = "";
+/**
+ * Converts a string in the format "hh:mm:ss" to the equivalent number of seconds.
+ * @param {string} InputString - The input string to convert.
+ * @returns {number} The number of seconds equivalent to the input string.
+ */
+let StringToSeconds = (InputString) => {
+ try {
+ let SplittedString = InputString.split(":");
+ return parseInt(SplittedString[0]) * 60 * 60 + parseInt(SplittedString[1]) * 60 + parseInt(SplittedString[2]);
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
- if (UtilityEnabled("BBSPopup")) {
- RequestAPI("GetBBSMentionList", {}, (Response) => {
- if (Response.Success) {
- let MentionList = Response.Data.MentionList;
- for (let i = 0; i < MentionList.length; i++) {
- CreateAndShowBBSMentionToast(MentionList[i]);
- }
+}
+/**
+ * Converts a memory size in bytes to a human-readable string representation.
+ * @param {number} Memory - The memory size in bytes.
+ * @returns {string} The human-readable string representation of the memory size.
+ */
+let SizeToStringSize = (Memory) => {
+ try {
+ if (UtilityEnabled("AddUnits")) {
+ if (Memory < 1024) {
+ return Memory + "KB";
+ } else if (Memory < 1024 * 1024) {
+ return (Memory / 1024).toFixed(2) + "MB";
+ } else if (Memory < 1024 * 1024 * 1024) {
+ return (Memory / 1024 / 1024).toFixed(2) + "GB";
+ } else {
+ return (Memory / 1024 / 1024 / 1024).toFixed(2) + "TB";
}
- });
+ } else {
+ return Memory;
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
}
- if (UtilityEnabled("MessagePopup")) {
- RequestAPI("GetMailMentionList", {}, (Response) => {
- if (Response.Success) {
- let MentionList = Response.Data.MentionList;
- for (let i = 0; i < MentionList.length; i++) {
- CreateAndShowMailMentionToast(MentionList[i]);
- }
+};
+let CodeSizeToStringSize = (Memory) => {
+ try {
+ if (UtilityEnabled("AddUnits")) {
+ if (Memory < 1024) {
+ return Memory + "B";
+ } else if (Memory < 1024 * 1024) {
+ return (Memory / 1024).toFixed(2) + "KB";
+ } else if (Memory < 1024 * 1024 * 1024) {
+ return (Memory / 1024 / 1024).toFixed(2) + "MB";
+ } else {
+ return (Memory / 1024 / 1024 / 1024).toFixed(2) + "GB";
}
- });
- }
-}
-
-GM_registerMenuCommand("清除缓存", () => {
- let Temp = [];
- for (let i = 0; i < localStorage.length; i++) {
- if (localStorage.key(i).startsWith("UserScript-User-")) {
- Temp.push(localStorage.key(i));
+ } else {
+ return Memory;
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
- for (let i = 0; i < Temp.length; i++) {
- localStorage.removeItem(Temp[i]);
+};
+/**
+ * Converts a time value to a string representation.
+ * @param {number} Time - The time value to convert.
+ * @returns {string|number} - The converted time value as a string, or the original value if UtilityEnabled("AddUnits") is false.
+ */
+let TimeToStringTime = (Time) => {
+ try {
+ if (UtilityEnabled("AddUnits")) {
+ if (Time < 1000) {
+ return Time + "ms";
+ } else if (Time < 1000 * 60) {
+ return (Time / 1000).toFixed(2) + "s";
+ }
+ } else {
+ return Time;
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
}
- location.reload();
-});
-GM_registerMenuCommand("重置数据", () => {
- if (confirm("确定要重置数据吗?")) {
- localStorage.clear();
- location.reload();
+};
+/**
+ * Tidies up the given table by applying Bootstrap styling and removing unnecessary attributes.
+ *
+ * @param {HTMLElement} Table - The table element to be tidied up.
+ */
+let TidyTable = (Table) => {
+ try {
+ if (UtilityEnabled("NewBootstrap") && Table != null) {
+ Table.className = "table table-hover";
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
}
-});
-
-//otherwise CurrentUsername might be undefined
-let loginStatus;
-await fetch("https://www.xmoj.tech/loginpage.php")
- .then((response) => response.text())
- .then((data) => (loginStatus = data));
-const logined = loginStatus == "Please logout First!";
-if (UtilityEnabled("AutoLogin") && document.querySelector("body > a:nth-child(1)") != null && document.querySelector("body > a:nth-child(1)").innerText == "请登录后继续操作") {
- localStorage.setItem("UserScript-LastPage", location.pathname + location.search);
- location.href = "https://www.xmoj.tech/loginpage.php";
-}
-
-let SearchParams = new URLSearchParams(location.search);
-let ServerURL = (UtilityEnabled("DebugMode") ? "https://ghpages.xmoj-script.uk/" : "https://www.xmoj-script.uk")
-if (document.querySelector("#profile") === null && !logined) {
- location.href = "https://www.xmoj.tech/loginpage.php";
-}
-let CurrentUsername = document.querySelector("#profile").innerText;
-CurrentUsername = CurrentUsername.replaceAll(/[^a-zA-Z0-9]/g, "");
-let IsAdmin = AdminUserList.indexOf(CurrentUsername) !== -1;
-
-const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
-const applyTheme = (theme) => {
- document.querySelector("html").setAttribute("data-bs-theme", theme);
- localStorage.setItem("UserScript-Setting-DarkMode", theme === "dark" ? "true" : "false");
};
-const applySystemTheme = (e) => applyTheme(e.matches ? "dark" : "light");
-let initTheme = () => {
- const saved = localStorage.getItem("UserScript-Setting-Theme") || "auto";
- if (saved === "auto") {
- applyTheme(prefersDark.matches ? "dark" : "light");
- prefersDark.addEventListener("change", applySystemTheme);
- } else {
- applyTheme(saved);
- prefersDark.removeEventListener("change", applySystemTheme);
+let UtilityEnabled = (Name) => {
+ try {
+ if (localStorage.getItem("UserScript-Setting-" + Name) == null) {
+ const defaultOffItems = ["DebugMode", "SuperDebug", "ReplaceXM"];
+ localStorage.setItem("UserScript-Setting-" + Name, defaultOffItems.includes(Name) ? "false" : "true");
+ }
+ return localStorage.getItem("UserScript-Setting-" + Name) == "true";
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
}
};
-initTheme();
-PeriodicCloudSync();
-setInterval(PeriodicCloudSync, 60 * 60 * 1000);
-
-
-class NavbarStyler {
- constructor() {
+let storeCredential = async (username, password) => {
+ if ('credentials' in navigator && window.PasswordCredential) {
try {
- this.navbar = document.querySelector('.navbar.navbar-expand-lg.bg-body-tertiary');
- if (this.navbar && UtilityEnabled("NewTopBar")) {
- this.init();
- }
+ const credential = new PasswordCredential({id: username, password: password});
+ await navigator.credentials.store(credential);
} catch (e) {
console.error(e);
- if (UtilityEnabled("DebugMode")) {
- SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
- }
}
}
-
- init() {
+};
+let getCredential = async () => {
+ if ('credentials' in navigator && window.PasswordCredential) {
try {
- this.applyStyles();
- this.createOverlay();
- this.createSpacer();
- window.addEventListener('resize', () => this.updateBlurOverlay());
- this.updateBlurOverlay();
+ return await navigator.credentials.get({password: true, mediation: 'optional'});
} catch (e) {
console.error(e);
- if (UtilityEnabled("DebugMode")) {
- SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
- }
}
}
-
- applyStyles() {
+ return null;
+};
+let clearCredential = async () => {
+ if ('credentials' in navigator && window.PasswordCredential) {
try {
- let n = this.navbar;
- n.classList.add('fixed-top', 'container', 'ml-auto');
- if (UtilityEnabled("MonochromeUI")) {
- Object.assign(n.style, {
- position: 'fixed',
- borderRadius: '0',
- boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)',
- margin: '0',
- maxWidth: '100%',
- backgroundColor: 'rgba(255, 255, 255, 0)',
- opacity: '0.75',
- zIndex: '1000'
- });
- } else {
- Object.assign(n.style, {
- position: 'fixed',
- borderRadius: '28px',
- boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)',
- margin: '16px auto',
- backgroundColor: 'rgba(255, 255, 255, 0)',
- opacity: '0.75',
- zIndex: '1000'
- });
- }
+ await navigator.credentials.preventSilentAccess();
} catch (e) {
console.error(e);
- if (UtilityEnabled("DebugMode")) {
- SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
- }
}
}
-
- createOverlay() {
- try {
- if (!document.getElementById('blur-overlay')) {
- let overlay = document.createElement('div');
- overlay.id = 'blur-overlay';
- document.body.appendChild(overlay);
-
- let style = document.createElement('style');
- style.textContent = UtilityEnabled("MonochromeUI") ? `
- #blur-overlay {
- display: none !important;
+};
+let RequestAPI = (Action, Data, CallBack) => {
+ try {
+ let Session = "";
+ let Temp = document.cookie.split(";");
+ for (let i = 0; i < Temp.length; i++) {
+ if (Temp[i].includes("PHPSESSID")) {
+ Session = Temp[i].split("=")[1];
+ }
+ }
+ if (Session === "") { //The cookie is httpOnly
+ GM.cookie.set({
+ name: 'PHPSESSID',
+ value: (Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)).substring(0, 28),
+ path: "/"
+ })
+ .then(() => {
+ console.log('Reset PHPSESSID successfully.');
+ location.reload(); //Refresh the page to auth with the new PHPSESSID
+ })
+ .catch((error) => {
+ console.error(error);
+ });
+ }
+ let PostData = {
+ "Authentication": {
+ "SessionID": Session, "Username": CurrentUsername,
+ }, "Data": Data, "Version": GM_info.script.version, "DebugMode": UtilityEnabled("DebugMode")
+ };
+ let DataString = JSON.stringify(PostData);
+ if (UtilityEnabled("DebugMode")) {
+ console.log("Sent for", Action + ":", DataString);
+ }
+ GM_xmlhttpRequest({
+ method: "POST",
+ url: (UtilityEnabled("SuperDebug") ? "http://127.0.0.1:8787/" : "https://api.xmoj-script.uk/") + Action,
+ headers: {
+ "Content-Type": "application/json",
+ "Cache-Control": "no-cache",
+ "XMOJ-UserID": CurrentUsername,
+ "XMOJ-Script-Version": GM_info.script.version,
+ "DebugMode": UtilityEnabled("DebugMode")
+ },
+ data: DataString,
+ onload: (Response) => {
+ if (UtilityEnabled("DebugMode")) {
+ console.log("Received for", Action + ":", Response.responseText);
}
- ` : `
- #blur-overlay {
- position: fixed;
- backdrop-filter: blur(4px);
- z-index: 999;
- pointer-events: none;
- border-radius: 28px;
+ try {
+ CallBack(JSON.parse(Response.responseText));
+ } catch (Error) {
+ console.log(Response.responseText);
}
- `;
- document.head.appendChild(style);
- }
- } catch (e) {
- console.error(e);
- if (UtilityEnabled("DebugMode")) {
- SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
+ });
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
}
}
-
- updateBlurOverlay() {
- try {
- let overlay = document.getElementById('blur-overlay');
- let n = this.navbar;
- Object.assign(overlay.style, {
- top: `${n.offsetTop}px`,
- left: `${n.offsetLeft}px`,
- width: `${n.offsetWidth}px`,
- height: `${n.offsetHeight}px`
- });
- } catch (e) {
- console.error(e);
- if (UtilityEnabled("DebugMode")) {
- SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
- }
+};
+let SyncSettingsToCloud = (CallBack) => {
+ if (!CurrentUsername) {
+ if (CallBack) CallBack({ Success: false, Message: "用户未登录" });
+ return;
+ }
+ if (!UtilityEnabled("CloudSync")) {
+ if (CallBack) CallBack({ Success: false, Message: "云同步已禁用" });
+ return;
+ }
+ let Settings = {};
+ for (let i = 0; i < localStorage.length; i++) {
+ let key = localStorage.key(i);
+ if (key && key.startsWith("UserScript-Setting-")) {
+ Settings[key.replace("UserScript-Setting-", "")] = localStorage.getItem(key);
}
}
-
- createSpacer() {
- try {
- let spacer = document.getElementById('navbar-spacer');
- let newHeight = this.navbar.offsetHeight + 24;
- if (!spacer) {
- spacer = document.createElement('div');
- spacer.id = 'navbar-spacer';
- spacer.style.height = `${newHeight}px`;
- spacer.style.width = '100%';
- document.body.insertBefore(spacer, document.body.firstChild);
+ RequestAPI("SetUserSettings", {"Settings": JSON.stringify(Settings)}, (Response) => {
+ if (UtilityEnabled("DebugMode")) {
+ if (Response.Success) {
+ console.log("设置已同步到云端");
} else {
- let currentHeight = parseInt(spacer.style.height, 10);
- if (currentHeight !== newHeight) {
- document.body.removeChild(spacer);
- spacer = document.createElement('div');
- spacer.id = 'navbar-spacer';
- spacer.style.height = `${newHeight}px`;
- spacer.style.width = '100%';
- document.body.insertBefore(spacer, document.body.firstChild);
+ console.error("设置云端同步失败:", Response.Message);
+ }
+ }
+ if (CallBack) CallBack(Response);
+ });
+};
+
+let PeriodicCloudSync = () => {
+ if (!CurrentUsername || !UtilityEnabled("CloudSync")) return;
+ const lastSync = parseInt(localStorage.getItem("UserScript-CloudSync-LastSync") || "0");
+ if (Date.now() - lastSync < 60 * 60 * 1000) return;
+ RequestAPI("GetUserSettings", {}, (Response) => {
+ if (Response.Success) {
+ localStorage.setItem("UserScript-CloudSync-LastSync", String(Date.now()));
+ const cloudSettings = (Response.Data && Response.Data.Settings) || {};
+ if (Object.keys(cloudSettings).length > 0) {
+ let themeChanged = false;
+ for (let key in cloudSettings) {
+ const rawValue = String(cloudSettings[key]);
+ const localKey = "UserScript-Setting-" + key;
+ if (localStorage.getItem(localKey) !== rawValue) {
+ localStorage.setItem(localKey, rawValue);
+ if (key === "Theme") themeChanged = true;
+ }
}
+ if (themeChanged) initTheme();
}
- } catch (e) {
- console.error(e);
- if (UtilityEnabled("DebugMode")) {
- SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ SyncSettingsToCloud();
+ }
+ });
+};
+
+unsafeWindow.GetContestProblemList = async function(RefreshList) {
+ try {
+ const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid"));
+ const res = await contestReq.text();
+ if (contestReq.status === 200 && res.indexOf("比赛尚未开始或私有,不能查看题目。") === -1) {
+ const parser = new DOMParser();
+ const dom = parser.parseFromString(res, "text/html");
+ const rows = (dom.querySelector("#problemset > tbody")).rows;
+ let problemList = [];
+ for (let i = 0; i < rows.length; i++) {
+ problemList.push({
+ "title": rows[i].children[2].innerText,
+ "url": rows[i].children[2].children[0].href
+ });
}
+ localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList));
+ if (RefreshList) location.reload();
}
+ } catch (e) {
+ console.error(e);
}
}
-function replaceMarkdownImages(text, string) {
- return text.replace(/!\[.*?\]\(.*?\)/g, string);
+// WebSocket Notification System
+let NotificationSocket = null;
+let NotificationSocketReconnectAttempts = 0;
+let NotificationSocketReconnectDelay = 1000;
+let NotificationSocketPingInterval = null;
+let NotificationSocketReconnectTimer = null;
+
+function GetPHPSESSID() {
+ let Session = "";
+ let Temp = document.cookie.split(";");
+ for (let i = 0; i < Temp.length; i++) {
+ if (Temp[i].includes("PHPSESSID")) {
+ Session = Temp[i].split("=")[1];
+ break;
+ }
+ }
+ return Session;
}
-function GetMDText(element) {
- let result = '';
- const blockTags = new Set([
- 'P', 'DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'NAV',
- 'UL', 'OL', 'LI', 'PRE', 'BLOCKQUOTE',
- 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
- 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'
- ]);
- const cellTags = new Set(['TD', 'TH']);
-
- function traverse(node) {
- if (node.nodeType === Node.TEXT_NODE) {
- result += node.textContent;
- return;
+function ConnectNotificationSocket() {
+ try {
+ // Clear any pending reconnection timer to prevent duplicate connections
+ if (NotificationSocketReconnectTimer) {
+ clearTimeout(NotificationSocketReconnectTimer);
+ NotificationSocketReconnectTimer = null;
}
- if (node.nodeType !== Node.ELEMENT_NODE) {
+ let Session = GetPHPSESSID();
+ if (Session === "") {
+ if (UtilityEnabled("DebugMode")) {
+ console.log("WebSocket: PHPSESSID not available, skipping connection");
+ }
return;
}
- const tag = node.nodeName.toUpperCase();
+ let wsUrl = (UtilityEnabled("SuperDebug") ? "ws://127.0.0.1:8787" : "wss://api.xmoj-script.uk") + "/ws/notifications?SessionID=" + Session;
- // Preserve line breaks for
- if (tag === 'BR') {
- result += '\n';
- return;
+ if (UtilityEnabled("DebugMode")) {
+ console.log("WebSocket: Connecting to", wsUrl);
}
- // Convert images to Markdown
- if (tag === 'IMG') {
- const src = node.getAttribute('src');
- if (src) {
- let resolvedSrc = src;
- try {
- resolvedSrc = new URL(src, location.href).href;
- } catch (e) {
- // Fallback to the raw src if URL construction fails
+ NotificationSocket = new WebSocket(wsUrl);
+
+ NotificationSocket.onopen = () => {
+ if (UtilityEnabled("DebugMode")) {
+ console.log("WebSocket: Connected successfully");
+ }
+ NotificationSocketReconnectAttempts = 0;
+ NotificationSocketReconnectDelay = 1000;
+
+ // Start ping keepalive
+ if (NotificationSocketPingInterval) {
+ clearInterval(NotificationSocketPingInterval);
+ }
+ NotificationSocketPingInterval = setInterval(() => {
+ if (NotificationSocket && NotificationSocket.readyState === WebSocket.OPEN) {
+ NotificationSocket.send(JSON.stringify({ type: 'ping' }));
+ if (UtilityEnabled("DebugMode")) {
+ console.log("WebSocket: Sent ping");
+ }
+ } else {
+ clearInterval(NotificationSocketPingInterval);
}
- result += ``;
+ }, 30000);
+ };
+
+ NotificationSocket.onmessage = (event) => {
+ HandleNotificationMessage(event);
+ };
+
+ NotificationSocket.onerror = (error) => {
+ if (UtilityEnabled("DebugMode")) {
+ console.error("WebSocket: Error", error);
}
- return;
- }
+ };
- const isBlock = blockTags.has(tag);
- const isCell = cellTags.has(tag);
+ NotificationSocket.onclose = (event) => {
+ if (UtilityEnabled("DebugMode")) {
+ console.log("WebSocket: Connection closed", event.code, event.reason);
+ }
+ if (NotificationSocketPingInterval) {
+ clearInterval(NotificationSocketPingInterval);
+ }
+ ReconnectNotificationSocket();
+ };
+ } catch (e) {
+ console.error("WebSocket: Failed to connect", e);
+ ReconnectNotificationSocket();
+ }
+}
- if (isBlock && !result.endsWith('\n')) {
- result += '\n';
- }
+function ReconnectNotificationSocket() {
+ const delay = Math.min(NotificationSocketReconnectDelay * Math.pow(2, NotificationSocketReconnectAttempts), 30000);
+ NotificationSocketReconnectAttempts++;
- // Keep table cells visually separated when copied as plain text.
- if (isCell && result.length > 0 && !result.endsWith('\n') && !result.endsWith('\t') && !result.endsWith(' ')) {
- result += '\t';
- }
+ if (UtilityEnabled("DebugMode")) {
+ console.log(`WebSocket: Reconnecting in ${delay}ms (attempt ${NotificationSocketReconnectAttempts})`);
+ }
- for (let child of node.childNodes) {
- traverse(child);
- }
+ NotificationSocketReconnectTimer = setTimeout(() => {
+ ConnectNotificationSocket();
+ }, delay);
+}
- if (isCell && !result.endsWith('\n') && !result.endsWith('\t')) {
- result += '\t';
+function HandleNotificationMessage(event) {
+ try {
+ const notification = JSON.parse(event.data);
+
+ if (UtilityEnabled("DebugMode")) {
+ console.log("WebSocket: Received message", notification);
}
- if (isBlock && !result.endsWith('\n')) {
- result += '\n';
+ if (notification.type === 'connected') {
+ if (UtilityEnabled("DebugMode")) {
+ console.log("WebSocket: Server confirmed connection at timestamp", notification.timestamp);
+ }
+ } else if (notification.type === 'bbs_mention') {
+ if (UtilityEnabled("BBSPopup")) {
+ CreateAndShowBBSMentionToast(notification.data);
+ }
+ } else if (notification.type === 'mail_mention') {
+ if (UtilityEnabled("MessagePopup")) {
+ CreateAndShowMailMentionToast(notification.data);
+ }
+ } else if (notification.type === 'pong') {
+ if (UtilityEnabled("DebugMode")) {
+ console.log("WebSocket: Received pong");
+ }
}
+ } catch (e) {
+ console.error("WebSocket: Failed to handle message", e);
}
+}
- traverse(element);
- return result;
+function CreateAndShowBBSMentionToast(mention) {
+ let ToastContainer = document.querySelector(".toast-container");
+ if (!ToastContainer) return;
+
+ let Toast = document.createElement("div");
+ Toast.classList.add("toast");
+ Toast.setAttribute("role", "alert");
+ let ToastHeader = document.createElement("div");
+ ToastHeader.classList.add("toast-header");
+ let ToastTitle = document.createElement("strong");
+ ToastTitle.classList.add("me-auto");
+ ToastTitle.innerHTML = "提醒:有人@你";
+ ToastHeader.appendChild(ToastTitle);
+ let ToastTime = document.createElement("small");
+ ToastTime.classList.add("text-body-secondary");
+ ToastTime.innerHTML = GetRelativeTime(mention.MentionTime);
+ ToastHeader.appendChild(ToastTime);
+ let ToastCloseButton = document.createElement("button");
+ ToastCloseButton.type = "button";
+ ToastCloseButton.classList.add("btn-close");
+ ToastCloseButton.setAttribute("data-bs-dismiss", "toast");
+ ToastHeader.appendChild(ToastCloseButton);
+ Toast.appendChild(ToastHeader);
+ let ToastBody = document.createElement("div");
+ ToastBody.classList.add("toast-body");
+ ToastBody.innerHTML = "讨论" + escapeHTML(mention.PostTitle) + "有新回复";
+ let ToastFooter = document.createElement("div");
+ ToastFooter.classList.add("mt-2", "pt-2", "border-top");
+ let ToastDismissButton = document.createElement("button");
+ ToastDismissButton.type = "button";
+ ToastDismissButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2");
+ ToastDismissButton.innerText = "忽略";
+ ToastDismissButton.addEventListener("click", () => {
+ RequestAPI("ReadBBSMention", {
+ "MentionID": Number(mention.MentionID)
+ }, () => {
+ });
+ Toast.remove();
+ });
+ ToastFooter.appendChild(ToastDismissButton);
+ let ToastViewButton = document.createElement("button");
+ ToastViewButton.type = "button";
+ ToastViewButton.classList.add("btn", "btn-primary", "btn-sm");
+ ToastViewButton.innerText = "查看";
+ ToastViewButton.addEventListener("click", () => {
+ open("https://www.xmoj.tech/discuss3/thread.php?tid=" + mention.PostID + '&page=' + mention.PageNumber, "_blank");
+ RequestAPI("ReadBBSMention", {
+ "MentionID": Number(mention.MentionID)
+ }, () => {
+ });
+ });
+ ToastFooter.appendChild(ToastViewButton);
+ ToastBody.appendChild(ToastFooter);
+ Toast.appendChild(ToastBody);
+ ToastContainer.appendChild(Toast);
+ new bootstrap.Toast(Toast).show();
+}
+
+function CreateAndShowMailMentionToast(mention) {
+ let ToastContainer = document.querySelector(".toast-container");
+ if (!ToastContainer) return;
+
+ let Toast = document.createElement("div");
+ Toast.classList.add("toast");
+ Toast.setAttribute("role", "alert");
+ let ToastHeader = document.createElement("div");
+ ToastHeader.classList.add("toast-header");
+ let ToastTitle = document.createElement("strong");
+ ToastTitle.classList.add("me-auto");
+ ToastTitle.innerHTML = "提醒:有新消息";
+ ToastHeader.appendChild(ToastTitle);
+ let ToastTime = document.createElement("small");
+ ToastTime.classList.add("text-body-secondary");
+ ToastTime.innerHTML = GetRelativeTime(mention.MentionTime);
+ ToastHeader.appendChild(ToastTime);
+ let ToastCloseButton = document.createElement("button");
+ ToastCloseButton.type = "button";
+ ToastCloseButton.classList.add("btn-close");
+ ToastCloseButton.setAttribute("data-bs-dismiss", "toast");
+ ToastHeader.appendChild(ToastCloseButton);
+ Toast.appendChild(ToastHeader);
+ let ToastBody = document.createElement("div");
+ ToastBody.classList.add("toast-body");
+ let ToastUser = document.createElement("span");
+ GetUsernameHTML(ToastUser, mention.FromUserID);
+ ToastBody.appendChild(ToastUser);
+ ToastBody.appendChild(document.createTextNode(" 给你发了一封短消息"));
+ let ToastFooter = document.createElement("div");
+ ToastFooter.classList.add("mt-2", "pt-2", "border-top");
+ let ToastDismissButton = document.createElement("button");
+ ToastDismissButton.type = "button";
+ ToastDismissButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2");
+ ToastDismissButton.setAttribute("data-bs-dismiss", "toast");
+ ToastDismissButton.innerText = "忽略";
+ ToastDismissButton.addEventListener("click", () => {
+ RequestAPI("ReadMailMention", {
+ "MentionID": Number(mention.MentionID)
+ }, () => {
+ });
+ });
+ ToastFooter.appendChild(ToastDismissButton);
+ let ToastViewButton = document.createElement("button");
+ ToastViewButton.type = "button";
+ ToastViewButton.classList.add("btn", "btn-primary", "btn-sm");
+ ToastViewButton.innerText = "查看";
+ ToastViewButton.addEventListener("click", () => {
+ open("https://www.xmoj.tech/mail.php?to_user=" + mention.FromUserID, "_blank");
+ RequestAPI("ReadMailMention", {
+ "MentionID": Number(mention.MentionID)
+ }, () => {
+ });
+ });
+ ToastFooter.appendChild(ToastViewButton);
+ ToastBody.appendChild(ToastFooter);
+ Toast.appendChild(ToastBody);
+ ToastContainer.appendChild(Toast);
+ new bootstrap.Toast(Toast).show();
}
-async function main() {
- try {
- if (location.href.startsWith('http://')) {
- //use https
- location.href = location.href.replace('http://', 'https://');
+function PollNotifications() {
+ // Clear toast container once before fetching to prevent race condition
+ if (UtilityEnabled("BBSPopup") || UtilityEnabled("MessagePopup")) {
+ let ToastContainer = document.querySelector(".toast-container");
+ if (ToastContainer) {
+ ToastContainer.innerHTML = "";
}
- if (location.host != "www.xmoj.tech") {
- location.host = "www.xmoj.tech";
- } else {
- if (location.href === 'https://www.xmoj.tech/open_contest_sign_up.php') {
- return;
- }
- document.body.classList.add("placeholder-glow");
- if (document.querySelector("#navbar") != null) {
- if (document.querySelector("body > div > div.jumbotron") != null) {
- document.querySelector("body > div > div.jumbotron").className = "mt-3";
- }
-
- if (UtilityEnabled("AutoLogin") && document.querySelector("#profile") != null && document.querySelector("#profile").innerHTML == "登录" && location.pathname != "/login.php" && location.pathname != "/loginpage.php" && location.pathname != "/lostpassword.php" && !logined) {
- localStorage.setItem("UserScript-LastPage", location.pathname + location.search);
- location.href = "https://www.xmoj.tech/loginpage.php";
- }
-
- let Discussion = null;
- if (UtilityEnabled("Discussion")) {
- Discussion = document.createElement("li");
- document.querySelector("#navbar > ul:nth-child(1)").appendChild(Discussion);
- Discussion.innerHTML = "讨论";
- }
- if (UtilityEnabled("Translate")) {
- document.querySelector("#navbar > ul:nth-child(1) > li:nth-child(2) > a").innerText = "题库";
- }
- //send analytics
- RequestAPI("SendData", {});
- if (UtilityEnabled("ReplaceLinks")) {
- document.body.innerHTML = String(document.body.innerHTML).replaceAll(/\[([^<]*)<\/a>\]/g, "");
- }
- if (UtilityEnabled("ReplaceXM")) {
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("我", "高老师");
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("小明", "高老师");
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("下海", "上海");
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("海上", "上海");
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("小红", "徐师娘");
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("小粉", "彩虹");
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("提交上节课的代码", "自动提交当年代码");
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("高老师们", "我们");
- document.body.innerHTML = String(document.body.innerHTML).replaceAll("自高老师", "自我");
- document.title = String(document.title).replaceAll("小明", "高老师");
- }
-
- if (UtilityEnabled("NewBootstrap")) {
- let Temp = document.querySelectorAll("link");
- for (var i = 0; i < Temp.length; i++) {
- if (Temp[i].href.indexOf("bootstrap.min.css") != -1) {
- Temp[i].remove();
- } else if (Temp[i].href.indexOf("white.css") != -1) {
- Temp[i].remove();
- } else if (Temp[i].href.indexOf("semantic.min.css") != -1) {
- Temp[i].remove();
- } else if (Temp[i].href.indexOf("bootstrap-theme.min.css") != -1) {
- Temp[i].remove();
- } else if (Temp[i].href.indexOf("problem.css") != -1) {
- Temp[i].remove();
- }
- }
- if (UtilityEnabled("DarkMode")) {
- document.querySelector("html").setAttribute("data-bs-theme", "dark");
- } else {
- document.querySelector("html").setAttribute("data-bs-theme", "light");
- }
- if (UtilityEnabled("MonochromeUI")) {
- let fontLink = document.createElement("link");
- fontLink.rel = "stylesheet";
- fontLink.href = "https://fonts.loli.net/css2?family=Playfair+Display:wght@400;700&family=Source+Serif+4:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap";
- document.head.appendChild(fontLink);
- let earlyStyle = document.createElement("style");
- earlyStyle.textContent = `
- :root {
- --mono-black: #000; --mono-white: #fff;
- --mono-gray-100: #f5f5f5; --mono-gray-300: #d4d4d4;
- --mono-font-heading: 'Playfair Display', Georgia, serif;
- --mono-font-body: 'Source Serif 4', 'Source Serif Pro', Georgia, serif;
- }
- [data-bs-theme='dark'] {
- --mono-black: #e5e5e5; --mono-white: #1a1a1a;
- --mono-gray-100: #222; --mono-gray-300: #404040;
- }
- * { border-radius: 0 !important; box-shadow: none !important; }
- body { font-family: var(--mono-font-body) !important; background-color: var(--mono-white) !important; color: var(--mono-black) !important; }
- h1,h2,h3,h4,h5,h6 { font-family: var(--mono-font-heading) !important; }
- .table thead th { background-color: var(--mono-black) !important; color: var(--mono-white) !important; }
- .card { border: 2px solid var(--mono-black) !important; }
- .card-header { background-color: var(--mono-black) !important; color: var(--mono-white) !important; }
- `;
- document.head.appendChild(earlyStyle);
- }
- var resources = [{
- type: 'link',
- href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css',
- rel: 'stylesheet'
- }, {
- type: 'link',
- href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/darcula.min.css',
- rel: 'stylesheet'
- }, {
- type: 'link',
- href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/merge/merge.min.css',
- rel: 'stylesheet'
- }, {
- type: 'link',
- href: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css',
- rel: 'stylesheet'
- }, {
- type: 'script',
- src: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.js',
- isModule: true
- }];
- let loadResources = async () => {
- let promises = resources.map(resource => {
- return new Promise((resolve, reject) => {
- let element;
- if (resource.type === 'script') {
- element = document.createElement('script');
- element.src = resource.src;
- if (resource.isModule) {
- element.type = 'module';
- }
- element.onload = resolve;
- element.onerror = reject;
- } else if (resource.type === 'link') {
- element = document.createElement('link');
- element.href = resource.href;
- element.rel = resource.rel;
- resolve(); // Stylesheets don't have an onload event
- }
- document.head.appendChild(element);
- });
- });
-
- await Promise.all(promises);
- };
- if (location.pathname == "/submitpage.php") {
- await loadResources();
- } else {
- loadResources();
- }
- document.querySelector("nav").className = "navbar navbar-expand-lg bg-body-tertiary";
- document.querySelector("#navbar > ul:nth-child(1)").classList = "navbar-nav me-auto mb-2 mb-lg-0";
- document.querySelector("body > div > nav > div > div.navbar-header").outerHTML = `${UtilityEnabled("ReplaceXM") ? "高老师" : "小明"}的OJ`;
- document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li").classList = "nav-item dropdown";
- document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").className = "nav-link dropdown-toggle";
- document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a > span.caret").remove();
- Temp = document.querySelector("#navbar > ul:nth-child(1)").children;
- for (var i = 0; i < Temp.length; i++) {
- if (Temp[i].classList.contains("active")) {
- Temp[i].classList.remove("active");
- Temp[i].children[0].classList.add("active");
- }
- Temp[i].classList.add("nav-item");
- Temp[i].children[0].classList.add("nav-link");
- }
- document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").setAttribute("data-bs-toggle", "dropdown");
- document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").removeAttribute("data-toggle");
+ }
+ if (UtilityEnabled("BBSPopup")) {
+ RequestAPI("GetBBSMentionList", {}, (Response) => {
+ if (Response.Success) {
+ let MentionList = Response.Data.MentionList;
+ for (let i = 0; i < MentionList.length; i++) {
+ CreateAndShowBBSMentionToast(MentionList[i]);
}
- if (UtilityEnabled("RemoveUseless") && document.getElementsByTagName("marquee")[0] != undefined) {
- document.getElementsByTagName("marquee")[0].remove();
+ }
+ });
+ }
+ if (UtilityEnabled("MessagePopup")) {
+ RequestAPI("GetMailMentionList", {}, (Response) => {
+ if (Response.Success) {
+ let MentionList = Response.Data.MentionList;
+ for (let i = 0; i < MentionList.length; i++) {
+ CreateAndShowMailMentionToast(MentionList[i]);
}
- let Style = document.createElement("style");
- document.body.appendChild(Style);
- if (UtilityEnabled("MonochromeUI")) {
- Style.innerHTML = `
- /* Fonts loaded via to avoid layout shift */
+ }
+ });
+ }
+}
- :root {
- --mono-black: #000;
- --mono-white: #fff;
- --mono-gray-100: #f5f5f5;
- --mono-gray-200: #e5e5e5;
- --mono-gray-300: #d4d4d4;
- --mono-gray-400: #a3a3a3;
- --mono-gray-500: #737373;
- --mono-border: 2px solid var(--mono-black);
- --mono-border-thin: 1px solid var(--mono-gray-300);
- --mono-font-heading: 'Playfair Display', Georgia, serif;
- --mono-font-body: 'Source Serif 4', 'Source Serif Pro', Georgia, serif;
- --mono-font-mono: 'JetBrains Mono', 'Consolas', monospace;
- --mono-transition: 100ms ease;
- }
+GM_registerMenuCommand("清除缓存", () => {
+ let Temp = [];
+ for (let i = 0; i < localStorage.length; i++) {
+ if (localStorage.key(i).startsWith("UserScript-User-")) {
+ Temp.push(localStorage.key(i));
+ }
+ }
+ for (let i = 0; i < Temp.length; i++) {
+ localStorage.removeItem(Temp[i]);
+ }
+ location.reload();
+});
+GM_registerMenuCommand("重置数据", () => {
+ if (confirm("确定要重置数据吗?")) {
+ localStorage.clear();
+ location.reload();
+ }
+});
- [data-bs-theme='dark'] {
- --mono-black: #e5e5e5;
- --mono-white: #1a1a1a;
- --mono-gray-100: #222;
- --mono-gray-200: #2a2a2a;
- --mono-gray-300: #404040;
- --mono-gray-400: #737373;
- --mono-gray-500: #a3a3a3;
- }
+// Wrapped in an async IIFE so that `await` is valid in Violentmonkey,
+// which executes userscripts as classic scripts (not ES modules).
+(async () => {
+if (document.readyState === "loading") {
+ await new Promise(r => document.addEventListener("DOMContentLoaded", r, { once: true }));
+}
+// Reveal the page now that DOMContentLoaded has fired. Remove any old Bootstrap
+// stylesheets the preload scanner fetched (un-applies them from the CSSOM), then
+// remove the FOUC hide so the user sees the correct final state immediately.
+if (_earlyObs) { _earlyObs.disconnect(); _earlyObs = null; }
+if (_foucStyle) {
+ let _blocked = ["bootstrap.min.css", "white.css", "semantic.min.css", "bootstrap-theme.min.css", "problem.css"];
+ for (let _link of document.querySelectorAll("link")) {
+ if (_blocked.some(h => _link.href && _link.href.indexOf(h) !== -1)) _link.remove();
+ }
+ _foucStyle.remove(); _foucStyle = null;
+}
+//otherwise CurrentUsername might be undefined
+let loginStatus;
+await fetch("https://www.xmoj.tech/loginpage.php")
+ .then((response) => response.text())
+ .then((data) => (loginStatus = data));
+const logined = loginStatus == "Please logout First!";
+if (UtilityEnabled("AutoLogin") && document.querySelector("body > a:nth-child(1)") != null && document.querySelector("body > a:nth-child(1)").innerText == "请登录后继续操作") {
+ localStorage.setItem("UserScript-LastPage", location.pathname + location.search);
+ location.href = "https://www.xmoj.tech/loginpage.php";
+ return;
+}
- * {
- border-radius: 0 !important;
- box-shadow: none !important;
- }
+SearchParams = new URLSearchParams(location.search);
+let ServerURL = (UtilityEnabled("DebugMode") ? "https://ghpages.xmoj-script.uk/" : "https://www.xmoj-script.uk")
+const profileElement = document.querySelector("#profile");
+if (profileElement === null) {
+ if (!logined) {
+ location.href = "https://www.xmoj.tech/loginpage.php";
+ }
+ return;
+}
+CurrentUsername = profileElement.innerText;
+CurrentUsername = CurrentUsername.replaceAll(/[^a-zA-Z0-9]/g, "");
+let IsAdmin = AdminUserList.indexOf(CurrentUsername) !== -1;
- body {
- font-family: var(--mono-font-body) !important;
- color: var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- }
+const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
+const applyTheme = (theme) => {
+ document.querySelector("html").setAttribute("data-bs-theme", theme);
+ localStorage.setItem("UserScript-Setting-DarkMode", theme === "dark" ? "true" : "false");
+};
+const applySystemTheme = (e) => applyTheme(e.matches ? "dark" : "light");
+initTheme = () => {
+ const saved = localStorage.getItem("UserScript-Setting-Theme") || "auto";
+ if (saved === "auto") {
+ applyTheme(prefersDark.matches ? "dark" : "light");
+ prefersDark.addEventListener("change", applySystemTheme);
+ } else {
+ applyTheme(saved);
+ prefersDark.removeEventListener("change", applySystemTheme);
+ }
+};
+initTheme();
+PeriodicCloudSync();
+setInterval(PeriodicCloudSync, 60 * 60 * 1000);
- h1, h2, h3, h4, h5, h6 {
- font-family: var(--mono-font-heading) !important;
- font-weight: 700 !important;
- }
- code, pre, .CodeMirror, kbd, samp {
- font-family: var(--mono-font-mono) !important;
- }
+class NavbarStyler {
+ constructor() {
+ try {
+ this.navbar = document.querySelector('.navbar.navbar-expand-lg.bg-body-tertiary');
+ if (this.navbar && UtilityEnabled("NewTopBar")) {
+ this.init();
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+ }
- a {
- color: var(--mono-black) !important;
- text-decoration: none !important;
- transition: var(--mono-transition) !important;
- }
- .container a:not(.nav-link):not(.btn):not(.dropdown-item):not(.list-group-item):not(.page-link) {
- border-bottom: 1px solid var(--mono-gray-400) !important;
- padding-bottom: 1px !important;
- }
- .container a:not(.nav-link):not(.btn):not(.dropdown-item):not(.list-group-item):not(.page-link):hover {
- border-bottom-color: var(--mono-black) !important;
- }
+ init() {
+ try {
+ this.applyStyles();
+ this.createOverlay();
+ this.createSpacer();
+ window.addEventListener('resize', () => this.updateBlurOverlay());
+ this.updateBlurOverlay();
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+ }
- blockquote {
- border-left: 4px solid var(--mono-black) !important;
- padding: 0.5em 1em;
- }
+ applyStyles() {
+ try {
+ let n = this.navbar;
+ n.classList.add('fixed-top', 'container', 'ml-auto');
+ if (UtilityEnabled("MonochromeUI")) {
+ Object.assign(n.style, {
+ position: 'fixed',
+ borderRadius: '0',
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)',
+ margin: '0',
+ maxWidth: '100%',
+ backgroundColor: 'rgba(255, 255, 255, 0)',
+ opacity: '0.75',
+ zIndex: '1000'
+ });
+ } else {
+ Object.assign(n.style, {
+ position: 'fixed',
+ borderRadius: '28px',
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)',
+ margin: '16px auto',
+ backgroundColor: 'rgba(255, 255, 255, 0)',
+ opacity: '0.75',
+ zIndex: '1000'
+ });
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+ }
- /* Navbar */
- .navbar, nav.navbar {
- border-bottom: 4px solid var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- opacity: 1 !important;
- }
- .navbar .nav-link {
- color: var(--mono-black) !important;
- text-decoration: none !important;
- font-family: var(--mono-font-body) !important;
- text-transform: uppercase !important;
- letter-spacing: 0.05em !important;
- font-size: 0.85rem !important;
- }
- .navbar .nav-link:hover, .navbar .nav-link.active {
- background-color: var(--mono-black) !important;
- color: var(--mono-white) !important;
- }
+ createOverlay() {
+ try {
+ if (!document.getElementById('blur-overlay')) {
+ let overlay = document.createElement('div');
+ overlay.id = 'blur-overlay';
+ document.body.appendChild(overlay);
- /* Buttons */
- .btn {
- border: 2px solid var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- color: var(--mono-black) !important;
- text-transform: uppercase !important;
- letter-spacing: 0.1em !important;
- font-family: var(--mono-font-body) !important;
- font-weight: 600 !important;
- transition: var(--mono-transition) !important;
- }
- .btn:hover, .btn:focus, .btn:active, .btn.active {
- background-color: var(--mono-black) !important;
- color: var(--mono-white) !important;
- border-color: var(--mono-black) !important;
- }
- .btn-primary {
- border: 2px solid var(--mono-black) !important;
- background-color: var(--mono-black) !important;
- color: var(--mono-white) !important;
- }
- .btn-primary:hover {
- background-color: var(--mono-white) !important;
- color: var(--mono-black) !important;
- }
- .btn-secondary {
- border: 2px solid var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- color: var(--mono-black) !important;
- }
- .btn-secondary:hover {
- background-color: var(--mono-black) !important;
- color: var(--mono-white) !important;
- }
- .btn-success {
- background-color: var(--mono-white) !important;
- border: 2px solid #52c41a !important;
- color: #52c41a !important;
- }
- .btn-danger {
- background-color: var(--mono-white) !important;
- border: 2px solid #fe4c61 !important;
- color: #fe4c61 !important;
- }
- .btn-warning {
- background-color: var(--mono-white) !important;
- border: 2px solid #ffa900 !important;
- color: #ffa900 !important;
+ let style = document.createElement('style');
+ style.textContent = UtilityEnabled("MonochromeUI") ? `
+ #blur-overlay {
+ display: none !important;
}
- .btn-info {
- background-color: var(--mono-white) !important;
- border: 2px solid #0dcaf0 !important;
- color: #0dcaf0 !important;
+ ` : `
+ #blur-overlay {
+ position: fixed;
+ backdrop-filter: blur(4px);
+ z-index: 999;
+ pointer-events: none;
+ border-radius: 28px;
}
+ `;
+ document.head.appendChild(style);
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+ }
- /* Cards */
- .card {
- border: 2px solid var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- }
- .card-header {
- background-color: var(--mono-black) !important;
- color: var(--mono-white) !important;
- border-bottom: none !important;
- font-family: var(--mono-font-heading) !important;
- }
- .card-header * {
- color: var(--mono-white) !important;
- }
- .card-body {
- background-color: var(--mono-white) !important;
- color: var(--mono-black) !important;
- }
- .card-footer {
- border-top: 1px solid var(--mono-gray-300) !important;
- background-color: var(--mono-white) !important;
- }
+ updateBlurOverlay() {
+ try {
+ let overlay = document.getElementById('blur-overlay');
+ let n = this.navbar;
+ Object.assign(overlay.style, {
+ top: `${n.offsetTop}px`,
+ left: `${n.offsetLeft}px`,
+ width: `${n.offsetWidth}px`,
+ height: `${n.offsetHeight}px`
+ });
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+ }
- /* Modals */
- .modal-content {
- border: 4px solid var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- }
- .modal-header {
- border-bottom: 1px solid var(--mono-gray-300) !important;
- background-color: var(--mono-white) !important;
- }
- .modal-footer {
- border-top: 1px solid var(--mono-gray-300) !important;
- background-color: var(--mono-white) !important;
- }
- .modal-title {
- font-family: var(--mono-font-heading) !important;
+ createSpacer() {
+ try {
+ let spacer = document.getElementById('navbar-spacer');
+ let newHeight = this.navbar.offsetHeight + 24;
+ if (!spacer) {
+ spacer = document.createElement('div');
+ spacer.id = 'navbar-spacer';
+ spacer.style.height = `${newHeight}px`;
+ spacer.style.width = '100%';
+ document.body.insertBefore(spacer, document.body.firstChild);
+ } else {
+ let currentHeight = parseInt(spacer.style.height, 10);
+ if (currentHeight !== newHeight) {
+ document.body.removeChild(spacer);
+ spacer = document.createElement('div');
+ spacer.id = 'navbar-spacer';
+ spacer.style.height = `${newHeight}px`;
+ spacer.style.width = '100%';
+ document.body.insertBefore(spacer, document.body.firstChild);
}
+ }
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+ }
+}
- /* Toasts */
- .toast {
- border: 2px solid var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- }
- .toast-header {
- background-color: var(--mono-gray-100) !important;
- color: var(--mono-black) !important;
- border-bottom: 1px solid var(--mono-gray-300) !important;
- }
+function replaceMarkdownImages(text, string) {
+ return text.replace(/!\[.*?\]\(.*?\)/g, string);
+}
- /* Tables */
- .table {
- border-color: var(--mono-gray-300) !important;
- }
- thead th, th.header, th.headerSortUp, th.headerSortDown {
- background-color: var(--mono-black) !important;
- background-image: none !important;
- color: var(--mono-white) !important;
- border-bottom: none !important;
- font-family: var(--mono-font-heading) !important;
- text-transform: uppercase !important;
- letter-spacing: 0.05em !important;
- font-size: 0.85rem !important;
- }
- td, th {
- border-color: var(--mono-gray-300) !important;
- text-align: center !important;
- }
- .table-striped > tbody > tr:nth-of-type(odd) > * {
- background-color: var(--mono-gray-100) !important;
- }
- table {
- margin-top: 16px !important;
- }
+function GetMDText(element) {
+ let result = '';
+ const blockTags = new Set([
+ 'P', 'DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'NAV',
+ 'UL', 'OL', 'LI', 'PRE', 'BLOCKQUOTE',
+ 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
+ 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'
+ ]);
+ const cellTags = new Set(['TD', 'TH']);
- /* List groups */
- .list-group-item {
- border: none !important;
- border-bottom: 1px solid var(--mono-gray-300) !important;
- background-color: var(--mono-white) !important;
- color: var(--mono-black) !important;
- }
- .list-group-item-success {
- border-left: 4px solid #52c41a !important;
- }
- .list-group-item-warning {
- border-left: 4px solid #ffa900 !important;
- }
- .list-group-item-danger {
- border-left: 4px solid #fe4c61 !important;
- }
+ function traverse(node) {
+ if (node.nodeType === Node.TEXT_NODE) {
+ result += node.textContent;
+ return;
+ }
- /* Dropdowns */
- .dropdown-menu {
- border: 2px solid var(--mono-black) !important;
- padding: 0 !important;
- background-color: var(--mono-white) !important;
- }
- .dropdown-item {
- border-bottom: 1px solid var(--mono-gray-200) !important;
- color: var(--mono-black) !important;
- transition: var(--mono-transition) !important;
- text-decoration: none !important;
- }
- .dropdown-item:last-child {
- border-bottom: none !important;
- }
- .dropdown-item:hover, .dropdown-item:focus {
- background-color: var(--mono-black) !important;
- color: var(--mono-white) !important;
- }
+ if (node.nodeType !== Node.ELEMENT_NODE) {
+ return;
+ }
- /* Forms */
- .form-control, .form-select {
- border: 2px solid var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- color: var(--mono-black) !important;
- font-family: var(--mono-font-body) !important;
- }
- .form-control:focus, .form-select:focus {
- outline: 2px solid var(--mono-black) !important;
- outline-offset: 2px !important;
- border-color: var(--mono-black) !important;
- }
+ const tag = node.nodeName.toUpperCase();
- /* Alerts */
- .alert {
- border: 2px solid var(--mono-black) !important;
- background-color: var(--mono-white) !important;
- color: var(--mono-black) !important;
- }
- .alert-primary {
- border-left: 8px solid var(--mono-black) !important;
- }
+ // Preserve line breaks for
+ if (tag === 'BR') {
+ result += '\n';
+ return;
+ }
- /* Status indicators */
- .status_y {
- background-color: #52c41a !important;
- color: #fff !important;
- border-color: #52c41a !important;
- }
- .status_n {
- background-color: #fe4c61 !important;
- color: #fff !important;
- border-color: #fe4c61 !important;
- }
- .status_w {
- background-color: #ffa900 !important;
- color: #fff !important;
- border-color: #ffa900 !important;
+ // Convert images to Markdown
+ if (tag === 'IMG') {
+ const src = node.getAttribute('src');
+ if (src) {
+ let resolvedSrc = src;
+ try {
+ resolvedSrc = new URL(src, location.href).href;
+ } catch (e) {
+ // Fallback to the raw src if URL construction fails
}
+ result += ``;
+ }
+ return;
+ }
- .test-case:hover {
- border: 2px solid var(--mono-black) !important;
- }
+ const isBlock = blockTags.has(tag);
+ const isCell = cellTags.has(tag);
- .software_list {
- width: unset !important;
- }
- .software_item {
- margin: 5px 10px !important;
- background-color: var(--mono-gray-100) !important;
- border: 1px solid var(--mono-gray-300) !important;
- }
- .software_item img {
- width: 50px !important;
- height: 50px !important;
- object-fit: contain !important;
- }
- .item-txt {
- color: var(--mono-black) !important;
- }
- .cnt-row {
- justify-content: inherit;
- align-items: stretch;
- width: 100% !important;
- padding: 1rem 0;
- }
- .cnt-row-head {
- padding: 0.8em 1em;
- background-color: var(--mono-black);
- color: var(--mono-white);
- width: 100%;
- font-family: var(--mono-font-heading);
- }
- .cnt-row-head * {
- color: var(--mono-white) !important;
- }
- .cnt-row-body {
- padding: 1em;
- border: 2px solid var(--mono-black);
- border-top: none;
- }
+ if (isBlock && !result.endsWith('\n')) {
+ result += '\n';
+ }
- /* Scrollbar */
- ::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- }
- ::-webkit-scrollbar-track {
- background: var(--mono-white);
- }
- ::-webkit-scrollbar-thumb {
- background: var(--mono-black);
- }
+ // Keep table cells visually separated when copied as plain text.
+ if (isCell && result.length > 0 && !result.endsWith('\n') && !result.endsWith('\t') && !result.endsWith(' ')) {
+ result += '\t';
+ }
- /* Copy button in inverted headers */
- .cnt-row-head .copy-btn, .card-header .copy-btn {
- border-color: var(--mono-white) !important;
- color: var(--mono-white) !important;
- background-color: transparent !important;
- }
- .cnt-row-head .copy-btn:hover, .card-header .copy-btn:hover {
- background-color: var(--mono-white) !important;
- color: var(--mono-black) !important;
- }
+ for (let child of node.childNodes) {
+ traverse(child);
+ }
- /* Problem switcher responsive */
- @media (max-width: 768px) {
- .problem-switcher-container {
- display: none !important;
- }
- }
- .refreshList {
- cursor: pointer;
+ if (isCell && !result.endsWith('\n') && !result.endsWith('\t')) {
+ result += '\t';
+ }
+
+ if (isBlock && !result.endsWith('\n')) {
+ result += '\n';
+ }
+ }
+
+ traverse(element);
+ return result;
+}
+
+async function main() {
+ try {
+ if (location.href.startsWith('http://')) {
+ //use https
+ location.href = location.href.replace('http://', 'https://');
+ }
+ if (location.host != "www.xmoj.tech") {
+ location.host = "www.xmoj.tech";
+ } else {
+ if (location.href === 'https://www.xmoj.tech/open_contest_sign_up.php') {
+ return;
+ }
+ document.body.classList.add("placeholder-glow");
+ if (document.querySelector("#navbar") != null) {
+ if (document.querySelector("body > div > div.jumbotron") != null) {
+ document.querySelector("body > div > div.jumbotron").className = "mt-3";
}
- /* Contain images */
- img {
- max-width: 100% !important;
- height: auto !important;
+ if (UtilityEnabled("AutoLogin") && document.querySelector("#profile") != null && document.querySelector("#profile").innerHTML == "登录" && location.pathname != "/login.php" && location.pathname != "/loginpage.php" && location.pathname != "/lostpassword.php" && !logined) {
+ localStorage.setItem("UserScript-LastPage", location.pathname + location.search);
+ location.href = "https://www.xmoj.tech/loginpage.php";
}
- /* Hide blur overlay */
- #blur-overlay { display: none !important; }`;
- } else {
- Style.innerHTML = `
- nav {
- border-bottom-left-radius: 5px;
- border-bottom-right-radius: 5px;
- }
- blockquote {
- border-left: 5px solid var(--bs-secondary-bg);
- padding: 0.5em 1em;
- }
- .status_y:hover {
- box-shadow: #52c41a 1px 1px 10px 0px !important;
- }
- .status_n:hover {
- box-shadow: #fe4c61 1px 1px 10px 0px !important;
- }
- .status_w:hover {
- box-shadow: #ffa900 1px 1px 10px 0px !important;
- }
- .test-case {
- border-radius: 5px !important;
- }
- .test-case:hover {
- box-shadow: rgba(0, 0, 0, 0.3) 0px 10px 20px 3px !important;
- }
- .data[result-item] {
- border-bottom-left-radius: 5px;
- border-bottom-right-radius: 5px;
- }
- .software_list {
- width: unset !important;
- }
- .software_item {
- margin: 5px 10px !important;
- background-color: var(--bs-secondary-bg) !important;
- }
- .item-txt {
- color: var(--bs-emphasis-color) !important;
- }
- .cnt-row {
- justify-content: inherit;
- align-items: stretch;
- width: 100% !important;
- padding: 1rem 0;
- }
- .cnt-row-head {
- padding: 0.8em 1em;
- background-color: var(--bs-secondary-bg);
- border-radius: 0.3rem 0.3rem 0 0;
- width: 100%;
+ let Discussion = null;
+ if (UtilityEnabled("Discussion")) {
+ Discussion = document.createElement("li");
+ document.querySelector("#navbar > ul:nth-child(1)").appendChild(Discussion);
+ Discussion.innerHTML = "讨论";
}
- .cnt-row-body {
- padding: 1em;
- border: 1px solid var(--bs-secondary-bg);
- border-top: none;
- border-radius: 0 0 0.3rem 0.3rem;
+ if (UtilityEnabled("Translate")) {
+ document.querySelector("#navbar > ul:nth-child(1) > li:nth-child(2) > a").innerText = "题库";
}
- .refreshList {
- cursor: pointer;
- color: #6c757d;
- text-decoration: none;
- }`;
+ //send analytics
+ RequestAPI("SendData", {});
+ if (UtilityEnabled("ReplaceLinks")) {
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll(/\[([^<]*)<\/a>\]/g, "");
}
- if (UtilityEnabled("AddAnimation")) {
- Style.innerHTML += `.status, .test-case {
- transition: ${UtilityEnabled("MonochromeUI") ? "100ms ease" : "0.5s"} !important;
- }`;
+ if (UtilityEnabled("ReplaceXM")) {
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("我", "高老师");
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("小明", "高老师");
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("下海", "上海");
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("海上", "上海");
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("小红", "徐师娘");
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("小粉", "彩虹");
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("提交上节课的代码", "自动提交当年代码");
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("高老师们", "我们");
+ document.body.innerHTML = String(document.body.innerHTML).replaceAll("自高老师", "自我");
+ document.title = String(document.title).replaceAll("小明", "高老师");
}
- if (UtilityEnabled("AddColorText")) {
- Style.innerHTML += `.red {
- color: red !important;
+
+ if (UtilityEnabled("NewBootstrap")) {
+ // Remove any old Bootstrap/theme stylesheets that the browser's preload
+ // scanner may have fetched and applied before the document-start
+ // MutationObserver could intercept them. Removing a from the DOM
+ // un-applies its stylesheet from the CSSOM immediately.
+ let Temp = document.querySelectorAll("link");
+ for (var i = 0; i < Temp.length; i++) {
+ if (Temp[i].href.indexOf("bootstrap.min.css") != -1) {
+ Temp[i].remove();
+ } else if (Temp[i].href.indexOf("white.css") != -1) {
+ Temp[i].remove();
+ } else if (Temp[i].href.indexOf("semantic.min.css") != -1) {
+ Temp[i].remove();
+ } else if (Temp[i].href.indexOf("bootstrap-theme.min.css") != -1) {
+ Temp[i].remove();
+ } else if (Temp[i].href.indexOf("problem.css") != -1) {
+ Temp[i].remove();
+ }
+ }
+ if (UtilityEnabled("MonochromeUI")) {
+ let fontLink = document.createElement("link");
+ fontLink.rel = "stylesheet";
+ fontLink.href = "https://fonts.loli.net/css2?family=Playfair+Display:wght@400;700&family=Source+Serif+4:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap";
+ document.head.appendChild(fontLink);
+ }
+ var resources = [{
+ type: 'link',
+ href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css',
+ rel: 'stylesheet'
+ }, {
+ type: 'link',
+ href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/darcula.min.css',
+ rel: 'stylesheet'
+ }, {
+ type: 'link',
+ href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/merge/merge.min.css',
+ rel: 'stylesheet'
+ }];
+ // If the @resource wasn't cached yet (first install/update), the early
+ // block bailed out and Bootstrap CSS still needs to load from CDN.
+ if (!_earlyBootstrapInjected) {
+ resources.push({
+ type: 'link',
+ href: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css',
+ rel: 'stylesheet'
+ });
+ }
+ let loadResources = async () => {
+ let promises = resources.map(resource => {
+ return new Promise((resolve, reject) => {
+ let element;
+ if (resource.type === 'script') {
+ element = document.createElement('script');
+ element.src = resource.src;
+ if (resource.isModule) {
+ element.type = 'module';
+ }
+ element.onload = resolve;
+ element.onerror = reject;
+ } else if (resource.type === 'link') {
+ element = document.createElement('link');
+ element.href = resource.href;
+ element.rel = resource.rel;
+ resolve(); // Stylesheets don't have an onload event
+ }
+ document.head.appendChild(element);
+ });
+ });
+
+ await Promise.all(promises);
+ };
+ if (location.pathname == "/submitpage.php") {
+ await loadResources();
+ } else {
+ loadResources();
+ }
+ document.querySelector("nav").className = "navbar navbar-expand-lg bg-body-tertiary";
+ document.querySelector("#navbar > ul:nth-child(1)").classList = "navbar-nav me-auto mb-2 mb-lg-0";
+ document.querySelector("body > div > nav > div > div.navbar-header").outerHTML = `${UtilityEnabled("ReplaceXM") ? "高老师" : "小明"}的OJ`;
+ document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li").classList = "nav-item dropdown";
+ document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").className = "nav-link dropdown-toggle";
+ document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a > span.caret").remove();
+ Temp = document.querySelector("#navbar > ul:nth-child(1)").children;
+ for (var i = 0; i < Temp.length; i++) {
+ if (Temp[i].classList.contains("active")) {
+ Temp[i].classList.remove("active");
+ Temp[i].children[0].classList.add("active");
+ }
+ Temp[i].classList.add("nav-item");
+ Temp[i].children[0].classList.add("nav-link");
+ }
+ document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").setAttribute("data-bs-toggle", "dropdown");
+ document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").removeAttribute("data-toggle");
}
- .green {
- color: green !important;
+ if (UtilityEnabled("RemoveUseless") && document.getElementsByTagName("marquee")[0] != undefined) {
+ document.getElementsByTagName("marquee")[0].remove();
}
- .blue {
- color: blue !important;
- }`;
+ let Style = document.createElement("style");
+ document.body.appendChild(Style);
+ if (!_earlyBootstrapInjected) {
+ let _isMono = UtilityEnabled("MonochromeUI");
+ Style.innerHTML = _isMono ? MonochromeSkinCSS : NewBootstrapSkinCSS;
+ if (UtilityEnabled("AddAnimation")) Style.innerHTML += `.status, .test-case { transition: ${_isMono ? "100ms ease" : "0.5s"} !important; }`;
+ if (UtilityEnabled("AddColorText")) Style.innerHTML += `.red { color: red !important; } .green { color: green !important; } .blue { color: blue !important; }`;
}
if (UtilityEnabled("RemoveUseless")) {
@@ -3461,8 +4071,11 @@ async function main() {
}
} else if (location.pathname == "/submitpage.php") {
document.title = "提交代码: " + (SearchParams.get("id") != null ? "题目" + Number(SearchParams.get("id")) : "比赛" + Number(SearchParams.get("cid")));
- document.querySelector("body > div > div.mt-3").innerHTML = `` + `提交代码
` + (SearchParams.get("id") != null ? `题目${Number(SearchParams.get("id"))}` : `比赛${Number(SearchParams.get("cid")) + ` 题目` + String.fromCharCode(65 + parseInt(SearchParams.get("pid")))}`) + `
-
+ document.querySelector("body > div > div.mt-3").innerHTML = `
+
+
@@ -3472,29 +4085,107 @@ async function main() {
`;
+ (function() {
+ const _header = document.getElementById('_submitPageHeader');
+ const _h3 = document.createElement('h3');
+ _h3.textContent = '提交代码';
+ _header.appendChild(_h3);
+ if (SearchParams.get("id") != null) {
+ _header.appendChild(document.createTextNode('题目'));
+ const _idSpan = document.createElement('span');
+ _idSpan.className = 'blue';
+ _idSpan.textContent = String(Number(SearchParams.get("id")));
+ _header.appendChild(_idSpan);
+ } else {
+ _header.appendChild(document.createTextNode('比赛'));
+ const _cidSpan = document.createElement('span');
+ _cidSpan.className = 'blue';
+ _cidSpan.textContent = String(Number(SearchParams.get("cid")));
+ _header.appendChild(_cidSpan);
+ _header.appendChild(document.createTextNode('\u2003题目')); // \u2003 = em space
+ const _pidSpan = document.createElement('span');
+ _pidSpan.className = 'blue';
+ _pidSpan.textContent = String.fromCharCode(65 + parseInt(SearchParams.get("pid")));
+ _header.appendChild(_pidSpan);
+ }
+ })();
if (UtilityEnabled("AutoO2")) {
document.querySelector("#enable_O2").checked = true;
}
+ const getSubmitStorageKey = () => (SearchParams.get("id") != null ? ('XMOJ-Submit-id-' + SearchParams.get("id")) : ('XMOJ-Submit-cid-' + SearchParams.get("cid") + '-pid-' + SearchParams.get("pid")));
let CodeMirrorElement;
- (() => {
- CodeMirrorElement = CodeMirror.fromTextArea(document.querySelector("#CodeInput"), {
- lineNumbers: true,
- matchBrackets: true,
- mode: "text/x-c++src",
- indentUnit: 4,
- indentWithTabs: true,
- enterMode: "keep",
- tabMode: "shift",
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default"),
- extraKeys: {
- "Ctrl-Space": "autocomplete", "Ctrl-Enter": function (instance) {
+ const editorOptions = {
+ language: 'cpp',
+ value: '',
+ automaticLayout: true,
+ theme: (UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'),
+ minimap: { enabled: false },
+ lineNumbers: 'on',
+ tabSize: 4,
+ localStorageKey: getSubmitStorageKey(),
+ // enable filling from header bottom to window bottom by default
+ fitToViewport: true,
+ bottomOffset: 8
+ };
+ try {
+ CodeMirrorElement = await createMonacoEditor('MonacoEditor', editorOptions);
+ try {
+ CodeMirrorElement._monacoEditor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function() { Submit.click(); });
+ CodeMirrorElement._monacoEditor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Space, function() { CodeMirrorElement._monacoEditor.trigger('keyboard', 'editor.action.triggerSuggest', {}); });
+ } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+ if (!editorOptions.fitToViewport) CodeMirrorElement.setSize("100%", "550px");
+ CodeMirrorElement.getWrapperElement().style.border = UtilityEnabled("MonochromeUI") ? "2px solid var(--mono-black)" : "1px solid #ddd";
+ document.getElementById("loadEditor").remove();
+ } catch (e) {
+ const _fallbackTA = document.getElementById('CodeInput');
+ document.getElementById('MonacoEditor').style.display = 'none';
+ _fallbackTA.style.display = '';
+ _fallbackTA.style.width = '100%';
+ if (editorOptions.fitToViewport) {
+ try {
+ const header = document.querySelector('nav') || document.querySelector('#navbar') || document.querySelector('.navbar') || document.querySelector('header');
+ const top = header ? header.getBoundingClientRect().bottom : 0;
+ const bottomOffset = Number(editorOptions.bottomOffset || 0);
+ const available = Math.max(80, window.innerHeight - top - bottomOffset);
+ _fallbackTA.style.height = available + 'px';
+ } catch (err) { _fallbackTA.style.height = '550px'; }
+ } else {
+ _fallbackTA.style.height = '550px';
+ }
+ const _fallbackKey = getSubmitStorageKey();
+ try { const _saved = localStorage.getItem(_fallbackKey); if (_saved !== null && _saved !== 'null') _fallbackTA.value = _saved; } catch (_e) {}
+ let _fallbackTimer = null;
+ const _fallbackSave = () => { try { localStorage.setItem(_fallbackKey, _fallbackTA.value); } catch (_e) {} };
+ _fallbackTA.addEventListener('input', () => { if (_fallbackTimer) clearTimeout(_fallbackTimer); _fallbackTimer = setTimeout(_fallbackSave, 500); });
+ _fallbackTA.addEventListener('keydown', (e) => {
+ const key = e.key || e.keyCode;
+ const isEnter = key === 'Enter' || key === 13;
+ if (isEnter && e.ctrlKey) {
+ e.preventDefault();
+ if (typeof Submit !== 'undefined' && Submit && typeof Submit.click === 'function') {
Submit.click();
}
}
- })
- })();
- CodeMirrorElement.setSize("100%", "auto");
- CodeMirrorElement.getWrapperElement().style.border = UtilityEnabled("MonochromeUI") ? "2px solid var(--mono-black)" : "1px solid #ddd";
+ });
+ CodeMirrorElement = {
+ getValue: () => _fallbackTA.value,
+ setValue: (v) => { _fallbackTA.value = v; },
+ setSize: (w, h) => { if (w) _fallbackTA.style.width = w; if (h) _fallbackTA.style.height = h; },
+ getWrapperElement: () => _fallbackTA,
+ focus: () => _fallbackTA.focus(),
+ showFind: () => {},
+ goToLine: () => {},
+ selectRange: () => {},
+ saveToLocal: _fallbackSave,
+ localStorageKey: _fallbackKey,
+ _monacoEditor: null
+ };
+ }
if (SearchParams.get("sid") !== null) {
await fetch("https://www.xmoj.tech/getsource.php?id=" + SearchParams.get("sid"))
@@ -3557,6 +4248,12 @@ async function main() {
}
ErrorElement.style.display = "block";
ErrorMessage.style.color = "red";
+ try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
ErrorMessage.innerText = "比赛已结束, 正在尝试向题目 " + rPID + " 提交";
console.log("比赛已结束, 正在尝试向题目 " + rPID + " 提交");
let o2Switch = "&enable_O2=on";
@@ -3581,6 +4278,12 @@ async function main() {
}
ErrorElement.style.display = "block";
ErrorMessage.style.color = "red";
+ try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
ErrorMessage.innerText = "提交失败!请关闭脚本后重试!";
Submit.disabled = false;
Submit.value = "提交";
@@ -3607,6 +4310,12 @@ async function main() {
PassCheck.style.display = "";
ErrorElement.style.display = "block";
if (UtilityEnabled("DarkMode")) ErrorMessage.style.color = "yellow"; else ErrorMessage.style.color = "red";
+ try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
ErrorMessage.innerText = "此题输入输出文件名为" + IOFilename + ",请检查是否填错";
let freopenText = document.createElement('small');
@@ -3628,14 +4337,41 @@ async function main() {
}, 1500);
});
document.getElementById('ErrorMessage').appendChild(copyFreopenButton);
- let freopenCodeField = CodeMirror(document.getElementById('ErrorMessage'), {
- value: 'freopen("' + IOFilename + '.in", "r", stdin);\nfreopen("' + IOFilename + '.out", "w", stdout);',
- mode: 'text/x-c++src',
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default"),
- readOnly: true,
- lineNumbers: true
- });
- freopenCodeField.setSize("100%", "auto");
+ // create a small read-only Monaco editor or fallback text for the freopen snippet
+ let codeHost = document.createElement('div');
+ codeHost.style.width = '100%';
+ codeHost.style.height = '38px';
+ codeHost.style.marginTop = '10px';
+ document.getElementById('ErrorMessage').appendChild(codeHost);
+ if (typeof monaco !== 'undefined') {
+ try {
+ const _tmpErrEditor = monaco.editor.create(codeHost, {
+ value: 'freopen("' + IOFilename + '.in", "r", stdin);\nfreopen("' + IOFilename + '.out", "w", stdout);',
+ language: 'cpp',
+ readOnly: true,
+ theme: (UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'),
+ automaticLayout: true,
+ minimap: { enabled: false },
+ lineNumbers: 'on'
+ });
+ window._xmoj_temp_error_editors = window._xmoj_temp_error_editors || [];
+ window._xmoj_temp_error_editors.push(_tmpErrEditor);
+ try { codeHost._monacoEditor = _tmpErrEditor; codeHost.setAttribute('data-xmoj-error-editor', '1'); } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
+ } catch (e) {
+ const pre = document.createElement('pre');
+ pre.textContent = 'freopen("' + IOFilename + '.in", "r", stdin);\nfreopen("' + IOFilename + '.out", "w", stdout);';
+ codeHost.appendChild(pre);
+ }
+ } else {
+ const pre = document.createElement('pre');
+ pre.textContent = 'freopen("' + IOFilename + '.in", "r", stdin);\nfreopen("' + IOFilename + '.out", "w", stdout);';
+ codeHost.appendChild(pre);
+ }
document.querySelector("#Submit").disabled = false;
document.querySelector("#Submit").value = "提交";
return false;
@@ -3643,6 +4379,12 @@ async function main() {
PassCheck.style.display = "";
ErrorElement.style.display = "block";
if (UtilityEnabled("DarkMode")) ErrorMessage.style.color = "yellow"; else ErrorMessage.style.color = "red";
+ try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
ErrorMessage.innerText = "请不要注释freopen语句";
document.querySelector("#Submit").disabled = false;
document.querySelector("#Submit").value = "提交";
@@ -3653,6 +4395,12 @@ async function main() {
PassCheck.style.display = "";
ErrorElement.style.display = "block";
if (UtilityEnabled("DarkMode")) ErrorMessage.style.color = "yellow"; else ErrorMessage.style.color = "red";
+ try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
ErrorMessage.innerText = "源代码为空";
document.querySelector("#Submit").disabled = false;
document.querySelector("#Submit").value = "提交";
@@ -3675,6 +4423,12 @@ async function main() {
PassCheck.style.display = "";
ErrorElement.style.display = "block";
if (UtilityEnabled("DarkMode")) ErrorMessage.style.color = "yellow"; else ErrorMessage.style.color = "red";
+ try { _xmoj_disposeErrorMessageEditors(); } catch (e) {
+ console.error(e);
+ if (UtilityEnabled("DebugMode")) {
+ SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode.");
+ }
+ }
ErrorMessage.innerText = "编译错误:\n" + Response.stderr.trim();
document.querySelector("#Submit").disabled = false;
document.querySelector("#Submit").value = "提交";
@@ -4214,7 +4968,7 @@ async function main() {
- `;
+ `;
let LeftCode = "";
await fetch("https://www.xmoj.tech/getsource.php?id=" + SearchParams.get("left"))
@@ -4239,7 +4993,7 @@ async function main() {
mode: "text/x-c++src",
collapseIdentical: "true",
readOnly: true,
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default"),
+ theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default"),
revertButtons: false,
ignoreWhitespace: true
});
@@ -4511,7 +5265,7 @@ int main()
ApplyDiv.appendChild(CodeElement);
CodeMirror(CodeElement, {
value: Data,
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default"),
+ theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default"),
lineNumbers: true,
readOnly: true
}).setSize("100%", "auto");
@@ -4704,7 +5458,7 @@ int main()
lineNumbers: true,
mode: "text/x-c++src",
readOnly: true,
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default")
+ theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default")
}).setSize("100%", "auto");
}
} else if (location.pathname == "/open_contest.php") {
@@ -4779,7 +5533,7 @@ int main()
lineNumbers: true,
mode: "text/x-c++src",
readOnly: true,
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default")
+ theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default")
}).setSize("100%", "auto");
} else if (location.pathname == "/ceinfo.php") {
await fetch(location.href)
@@ -4787,39 +5541,46 @@ int main()
return Result.text();
}).then((Result) => {
let ParsedDocument = new DOMParser().parseFromString(Result, "text/html");
- document.querySelector("body > div > div.mt-3").innerHTML = "";
- let CodeElement = document.createElement("div");
- CodeElement.className = "mb-3";
- document.querySelector("body > div > div.mt-3").appendChild(CodeElement);
- CodeMirror(CodeElement, {
- value: ParsedDocument.getElementById("errtxt").innerHTML.replaceAll("<", "<").replaceAll(">", ">"),
- lineNumbers: true,
- mode: "text/x-c++src",
- readOnly: true,
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default")
- }).setSize("100%", "auto");
- });
- } else if (location.pathname == "/problem_std.php") {
- await fetch("https://www.xmoj.tech/problem_std.php?cid=" + SearchParams.get("cid") + "&pid=" + SearchParams.get("pid"))
- .then((Response) => {
- return Response.text();
- }).then((Response) => {
- let ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
- let Temp = ParsedDocument.getElementsByTagName("pre");
- document.querySelector("body > div > div.mt-3").innerHTML = "";
- for (let i = 0; i < Temp.length; i++) {
+ if (!ParsedDocument.getElementsByClassName("jumbotron")[0].innerHTML.includes('I am sorry, You could not view this message!')) {
+ document.querySelector("body > div > div.mt-3").innerHTML = "";
let CodeElement = document.createElement("div");
CodeElement.className = "mb-3";
document.querySelector("body > div > div.mt-3").appendChild(CodeElement);
CodeMirror(CodeElement, {
- value: Temp[i].innerText,
+ value: ParsedDocument.getElementById("errtxt").innerHTML.replaceAll("<", "<").replaceAll(">", ">"),
lineNumbers: true,
mode: "text/x-c++src",
readOnly: true,
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default")
+ theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default")
}).setSize("100%", "auto");
}
});
+ } else if (location.pathname == "/problem_std.php") {
+ await fetch("https://www.xmoj.tech/problem_std.php?cid=" + SearchParams.get("cid") + "&pid=" + SearchParams.get("pid"))
+ .then((Response) => {
+ return Response.text();
+ }).then((Response) => {
+ let ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
+ if (!ParsedDocument.getElementsByClassName("jumbotron")[0].innerHTML.includes('No such Problem!')) {
+ let Temp = ParsedDocument.getElementsByTagName("pre");
+ document.querySelector("body > div > div.mt-3").innerHTML = "";
+ for (let i = 0; i < Temp.length; i++) {
+ let CodeElement = document.createElement("div");
+ CodeElement.className = "mb-3";
+ document.querySelector("body > div > div.mt-3").appendChild(CodeElement);
+ CodeMirror(CodeElement, {
+ value: Temp[i].innerText,
+ lineNumbers: true,
+ mode: "text/x-c++src",
+ readOnly: true,
+ theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default")
+ }).setSize("100%", "auto");
+ }
+
+ const overlay = document.getElementById('overlay');
+ if (overlay && overlay.remove) overlay.remove();
+ }
+ });
} else if (location.pathname == "/mail.php") {
if (SearchParams.get("to_user") == null) {
document.querySelector("body > div > div.mt-3").innerHTML = `
@@ -5744,7 +6505,7 @@ int main()
CodeMirror(CodeElements[i].parentElement, {
value: CodeElements[i].innerText,
mode: ModeName,
- theme: (UtilityEnabled("DarkMode") ? "darcula" : "default"),
+ theme: (UtilityEnabled("DarkMode") ? "vs-dark" : "default"),
lineNumbers: true,
readOnly: true
}).setSize("100%", "auto");
@@ -6344,6 +7105,8 @@ int main()
}
}
-main().then(r => {
- console.log("XMOJ-Script loaded successfully!");
+await main();
+console.log("XMOJ-Script loaded successfully!");
+})().catch(e => {
+ console.error("[XMOJ-Script] Initialization error:", e);
});
diff --git a/package.json b/package.json
index 3cb8e34a..c257181d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "xmoj-script",
- "version": "3.5.0",
+ "version": "3.6.0",
"description": "an improvement script for xmoj.tech",
"main": "AddonScript.js",
"scripts": {